diff --git a/about/features.md b/about/features.md index fb0f76820c..433de2198c 100644 --- a/about/features.md +++ b/about/features.md @@ -114,8 +114,7 @@ Following is an index of the features currently covered by CAP, with status and | [Arrayed Elements](../cds/cdl#arrayed-types) | | | | | [Streaming & Media Types](../guides/providing-services#serving-media-data) | | | | | [Conflict Detection through _ETags_](../guides/providing-services#etag) | | | | -| [Authentication via JWT](../guides/security/authorization#prerequisite-authentication) | | | | -| [Basic Authentication](../guides/security/authorization#prerequisite-authentication) | | | | +| [Authentication](../guides/security/authentication) | | | |
diff --git a/get-started/troubleshooting.md b/get-started/troubleshooting.md index e3de313b8b..146600f8b5 100644 --- a/get-started/troubleshooting.md +++ b/get-started/troubleshooting.md @@ -252,7 +252,7 @@ A new option `privilegedUser()` can be leveraged when [defining](../java/event-h | | Explanation | | --- | ---- | -| _Root Cause_ | You've [explicitly configured a mock](../java/security#explicitly-defined-mock-users) user with a name that is already used by a [preconfigured mock user](../java/security#preconfigured-mock-users). +| _Root Cause_ | You've [explicitly configured a mock](../java/security#custom-mock-users) user with a name that is already used by a [preconfigured mock user](../java/security#preconfigured-mock-users). | _Solution_ | Rename the mock user and build your project again. ### Why do I get an "Error on server start"? diff --git a/guides/deployment/microservices.md b/guides/deployment/microservices.md index 60bdae144c..690651c9e5 100644 --- a/guides/deployment/microservices.md +++ b/guides/deployment/microservices.md @@ -370,7 +370,7 @@ Note that we use the *--ws-pack* option for some modules. It's important for nod ### Authentication -Add [security configuration](../security/authorization#xsuaa-configuration) using the command: +Add [security configuration](../security/authentication) using the command: ```shell cds add xsuaa --for production diff --git a/guides/deployment/to-cf.md b/guides/deployment/to-cf.md index 746d9aa672..d539b0da74 100644 --- a/guides/deployment/to-cf.md +++ b/guides/deployment/to-cf.md @@ -159,7 +159,7 @@ cds add xsuaa ``` ::: tip This will also generate an `xs-security.json` file -The roles/scopes are derived from authorization-related annotations in your CDS models. Ensure to rerun `cds compile --to xsuaa`, as documented in the [_Authorization_ guide](/guides/security/authorization#xsuaa-configuration) whenever there are changes to these annotations. +The roles/scopes are derived from authorization-related annotations in your CDS models. Ensure to rerun `cds compile --to xsuaa`, as documented in the [security guide](/guides/security/cap-users#xsuaa-roles) whenever there are changes to these annotations. ::: [Learn more about SAP Authorization and Trust Management/XSUAA.](https://discovery-center.cloud.sap/serviceCatalog/authorization-and-trust-management-service?region=all){.learn-more} diff --git a/guides/providing-services.md b/guides/providing-services.md index 1d75e2800e..355a7155fe 100644 --- a/guides/providing-services.md +++ b/guides/providing-services.md @@ -994,7 +994,7 @@ The remaining cases that need custom handlers, reduce to real custom logic, spec - Domain-specific programmatic [Validations](#input-validation) - Augmenting result sets, for example to add computed fields for frontends -- Programmatic [Authorization Enforcements](/guides/security/authorization#enforcement) +- Programmatic [Authorization Enforcements](/guides/security/cap-users#developing-with-users) - Triggering follow-up actions, for example calling other services or emitting outbound events in response to inbound events - And more... In general, all the things not (yet) covered by generic handlers diff --git a/guides/security/aspects.md b/guides/security/aspects.md index a9d3dce210..d131623179 100644 --- a/guides/security/aspects.md +++ b/guides/security/aspects.md @@ -118,7 +118,7 @@ CAP doesn't require any specific authentication strategy, but it provides out of On configured authentication, *all CAP endpoints are authenticated by default*. ::: warning -❗ **CAP applications need to ensure that an appropriate [authentication method](/guides/security/authorization#prerequisite-authentication) is configured**. +❗ **CAP applications need to ensure that an appropriate [authentication](./authentication) is configured**. It's highly recommended to establish integration tests to safeguard a valid configuration. ::: @@ -196,7 +196,7 @@ To verify CAP authorizations in your model, it's recommended to use [CDS lint ru The rules prepared by application developers are applied to business users according to grants given by the subscribers user administrator, that is, they're applied tenant-specific. -CAP authorizations can be defined dependently from [user claims](/guides/security/authorization#user-claims) such as [XSUAA scopes or attributes](https://help.sap.com/docs/btp/sap-business-technology-platform/application-security-descriptor-configuration-syntax) +CAP authorizations can be defined dependently from [user claims](/guides/security/cap-users#claims) such as [XSUAA scopes or attributes](https://help.sap.com/docs/btp/sap-business-technology-platform/application-security-descriptor-configuration-syntax) that are deployed by application developers and granted by the user administrator of the subscriber. Hence, CAP provides a seamless integration of central identity service without technical lock-in. diff --git a/guides/security/assets/add-api.png b/guides/security/assets/add-api.png new file mode 100644 index 0000000000..4f9d8e06c7 Binary files /dev/null and b/guides/security/assets/add-api.png differ diff --git a/guides/security/assets/ams-assignment.png b/guides/security/assets/ams-assignment.png new file mode 100644 index 0000000000..c3945bb4b1 Binary files /dev/null and b/guides/security/assets/ams-assignment.png differ diff --git a/guides/security/assets/ams-base-policies.jpg b/guides/security/assets/ams-base-policies.jpg new file mode 100644 index 0000000000..55efd3ccae Binary files /dev/null and b/guides/security/assets/ams-base-policies.jpg differ diff --git a/guides/security/assets/ams-custom-policy-filter.jpg b/guides/security/assets/ams-custom-policy-filter.jpg new file mode 100644 index 0000000000..9593ead749 Binary files /dev/null and b/guides/security/assets/ams-custom-policy-filter.jpg differ diff --git a/guides/security/assets/ams-custom-policy.jpg b/guides/security/assets/ams-custom-policy.jpg new file mode 100644 index 0000000000..0f8b19fa3c Binary files /dev/null and b/guides/security/assets/ams-custom-policy.jpg differ diff --git a/guides/security/assets/ams-dark.png b/guides/security/assets/ams-dark.png new file mode 100644 index 0000000000..043d6909ad Binary files /dev/null and b/guides/security/assets/ams-dark.png differ diff --git a/guides/security/assets/ams-policy-assignment.jpg b/guides/security/assets/ams-policy-assignment.jpg new file mode 100644 index 0000000000..58cba35539 Binary files /dev/null and b/guides/security/assets/ams-policy-assignment.jpg differ diff --git a/guides/security/assets/ams.excalidraw b/guides/security/assets/ams.excalidraw new file mode 100644 index 0000000000..c0894909e7 --- /dev/null +++ b/guides/security/assets/ams.excalidraw @@ -0,0 +1,5982 @@ +{ + "type": "excalidraw", + "version": 2, + "source": "https://excalidraw.com", + "elements": [ + { + "type": "rectangle", + "version": 628, + "versionNonce": 1269076189, + "index": "b3C", + "isDeleted": false, + "id": "CLQz447xlYODqsxfVtoFe", + "fillStyle": "hachure", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1695.6428571428564, + "y": -567.3928571428571, + "strokeColor": "#1e1e1e", + "backgroundColor": "#a5d8ff", + "width": 142.99999999999991, + "height": 280, + "seed": 4040323, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "boundElements": [], + "updated": 1732028516263, + "link": null, + "locked": false + }, + { + "type": "rectangle", + "version": 112, + "versionNonce": 2087756254, + "index": "b3D", + "isDeleted": false, + "id": "_w8SZsvQQMMcw4VeJ6XVA", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 912.6666870117188, + "y": 527.666748046875, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 213.3333740234375, + "height": 45.99993896484375, + "seed": 2115800158, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "boundElements": [ + { + "type": "text", + "id": "AOy--JvPpiFRdl8lpn9LF" + }, + { + "id": "MUkMzmR6mRd5LCNAmlyLr", + "type": "arrow" + }, + { + "id": "DM_FpJoMN3XDQyIIw9CdM", + "type": "arrow" + }, + { + "id": "K026ufvFyIg5TqTBlgOR8", + "type": "arrow" + } + ], + "updated": 1721913807396, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 44, + "versionNonce": 1309236226, + "index": "b3E", + "isDeleted": false, + "id": "AOy--JvPpiFRdl8lpn9LF", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 951.3734283447266, + "y": 538.1667175292969, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 135.91989135742188, + "height": 25, + "seed": 2083758786, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1721912955025, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 1, + "text": "Organisation 1", + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "_w8SZsvQQMMcw4VeJ6XVA", + "originalText": "Organisation 1", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "type": "rectangle", + "version": 139, + "versionNonce": 1234172126, + "index": "b3F", + "isDeleted": false, + "id": "OVMZNmul11YJNmqruU0_b", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1079.3333129882812, + "y": 642.6667785644531, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 213.3333740234375, + "height": 45.99993896484375, + "seed": 446066114, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "boundElements": [ + { + "type": "text", + "id": "2RwBnpkqRQ7Ohk18SUx9H" + }, + { + "id": "Sc6fRGgVOuUQnPMXC5xXC", + "type": "arrow" + }, + { + "id": "DM_FpJoMN3XDQyIIw9CdM", + "type": "arrow" + } + ], + "updated": 1721913799815, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 79, + "versionNonce": 1909731230, + "index": "b3G", + "isDeleted": false, + "id": "2RwBnpkqRQ7Ohk18SUx9H", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1144.6700286865234, + "y": 653.166748046875, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 82.65994262695312, + "height": 25, + "seed": 1992779138, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1721913125344, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 1, + "text": "Space 1.1", + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "OVMZNmul11YJNmqruU0_b", + "originalText": "Space 1.1", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "type": "rectangle", + "version": 198, + "versionNonce": 159138398, + "index": "b3H", + "isDeleted": false, + "id": "CDuhzzGQ2yvc672XDmbzj", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1082.6666870117188, + "y": 733.3334045410156, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 213.3333740234375, + "height": 45.99993896484375, + "seed": 456990914, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "boundElements": [ + { + "type": "text", + "id": "C5HE86j5Ww2PXDWsFPGK_" + }, + { + "id": "K026ufvFyIg5TqTBlgOR8", + "type": "arrow" + } + ], + "updated": 1721913807396, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 141, + "versionNonce": 36374274, + "index": "b3I", + "isDeleted": false, + "id": "C5HE86j5Ww2PXDWsFPGK_", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1143.593406677246, + "y": 743.8333740234375, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 91.47993469238281, + "height": 25, + "seed": 908048514, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1721912965445, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 1, + "text": "Space 1.2", + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "CDuhzzGQ2yvc672XDmbzj", + "originalText": "Space 1.2", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "type": "rectangle", + "version": 77, + "versionNonce": 1213257539, + "index": "b3J", + "isDeleted": false, + "id": "pfyfK216xyZaKHIMr_Anh", + "fillStyle": "hachure", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1480.333251953125, + "y": 378.6666564941406, + "strokeColor": "#1e1e1e", + "backgroundColor": "#ffec99", + "width": 188, + "height": 66.66668701171875, + "seed": 1111249602, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "boundElements": [ + { + "type": "text", + "id": "ViMymxROfJ1Ru7Xqlx0mu" + }, + { + "id": "MUkMzmR6mRd5LCNAmlyLr", + "type": "arrow" + }, + { + "id": "tN5LwAGRsKgnQl2DQj_RH", + "type": "arrow" + }, + { + "id": "NXOozLUuIHMinfBgqIGXf", + "type": "arrow" + } + ], + "updated": 1722495267550, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 69, + "versionNonce": 794490595, + "index": "b3K", + "isDeleted": false, + "id": "ViMymxROfJ1Ru7Xqlx0mu", + "fillStyle": "hachure", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1517.3132934570312, + "y": 399.5, + "strokeColor": "#1e1e1e", + "backgroundColor": "#ffec99", + "width": 114.0399169921875, + "height": 25, + "seed": 1159641758, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1722495267550, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 1, + "text": "OrgManager", + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "pfyfK216xyZaKHIMr_Anh", + "originalText": "OrgManager", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "type": "rectangle", + "version": 52, + "versionNonce": 1489561054, + "index": "b3L", + "isDeleted": false, + "id": "TQ90SZ_KJREdVJ4ONH66i", + "fillStyle": "hachure", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1476.666748046875, + "y": 507.00006103515625, + "strokeColor": "#1e1e1e", + "backgroundColor": "#ffec99", + "width": 188, + "height": 66.66668701171875, + "seed": 624015966, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "boundElements": [ + { + "type": "text", + "id": "lhtHYltl3-1VxuGewOjk0" + }, + { + "id": "Sc6fRGgVOuUQnPMXC5xXC", + "type": "arrow" + } + ], + "updated": 1721912981582, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 60, + "versionNonce": 1351851138, + "index": "b3M", + "isDeleted": false, + "id": "lhtHYltl3-1VxuGewOjk0", + "fillStyle": "hachure", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1495.6568222045898, + "y": 527.8334045410156, + "strokeColor": "#1e1e1e", + "backgroundColor": "#ffec99", + "width": 150.0198516845703, + "height": 25, + "seed": 1241955998, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1721912942404, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 1, + "text": "SpaceDeveloper", + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "TQ90SZ_KJREdVJ4ONH66i", + "originalText": "SpaceDeveloper", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "type": "rectangle", + "version": 68, + "versionNonce": 1901235458, + "index": "b3N", + "isDeleted": false, + "id": "-Xzx2qQspYp6E786HcHvg", + "fillStyle": "hachure", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1482.666748046875, + "y": 626.9999694824219, + "strokeColor": "#1e1e1e", + "backgroundColor": "#ffec99", + "width": 188, + "height": 66.66668701171875, + "seed": 742640222, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "boundElements": [ + { + "type": "text", + "id": "Ys1XnkFFx73rcgfw2o4U9" + }, + { + "id": "KzwZBboAiesOG5IrdqS9x", + "type": "arrow" + } + ], + "updated": 1721913011883, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 86, + "versionNonce": 371646110, + "index": "b3O", + "isDeleted": false, + "id": "Ys1XnkFFx73rcgfw2o4U9", + "fillStyle": "hachure", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1512.8868103027344, + "y": 647.8333129882812, + "strokeColor": "#1e1e1e", + "backgroundColor": "#ffec99", + "width": 127.55987548828125, + "height": 25, + "seed": 1146539678, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1721912939843, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 1, + "text": "SpaceAuditor", + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "-Xzx2qQspYp6E786HcHvg", + "originalText": "SpaceAuditor", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "type": "arrow", + "version": 177, + "versionNonce": 348052003, + "index": "b3P", + "isDeleted": false, + "id": "MUkMzmR6mRd5LCNAmlyLr", + "fillStyle": "hachure", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1477.6666259765625, + "y": 415.0313058518204, + "strokeColor": "#1e1e1e", + "backgroundColor": "#ffec99", + "width": 336.333251953125, + "height": 131.59772081641626, + "seed": 1899146882, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1722495267550, + "link": null, + "locked": false, + "startBinding": { + "elementId": "pfyfK216xyZaKHIMr_Anh", + "focus": 0.49612315862114903, + "gap": 2.6666259765625, + "fixedPoint": null + }, + "endBinding": { + "elementId": "_w8SZsvQQMMcw4VeJ6XVA", + "focus": 0.6750141336135725, + "gap": 15.33331298828125, + "fixedPoint": null + }, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + -336.333251953125, + 131.59772081641626 + ] + ] + }, + { + "type": "text", + "version": 7, + "versionNonce": 1168677122, + "index": "b3Q", + "isDeleted": false, + "id": "noiI1WPon5FpXqp0cqyId", + "fillStyle": "hachure", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1264, + "y": 459.66668701171875, + "strokeColor": "#1e1e1e", + "backgroundColor": "#ffec99", + "width": 54.179962158203125, + "height": 25, + "seed": 1241506434, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1721912974332, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 1, + "text": "Anton", + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "Anton", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "type": "arrow", + "version": 118, + "versionNonce": 1120164702, + "index": "b3R", + "isDeleted": false, + "id": "Sc6fRGgVOuUQnPMXC5xXC", + "fillStyle": "hachure", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1475.666748046875, + "y": 548.948578919285, + "strokeColor": "#1e1e1e", + "backgroundColor": "#ffec99", + "width": 181.0001220703125, + "height": 112.85562853797796, + "seed": 1899183198, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1721915268481, + "link": null, + "locked": false, + "startBinding": { + "elementId": "TQ90SZ_KJREdVJ4ONH66i", + "focus": 0.550538675784344, + "gap": 1, + "fixedPoint": null + }, + "endBinding": { + "elementId": "OVMZNmul11YJNmqruU0_b", + "focus": 0.7138181393599891, + "gap": 1.99993896484375, + "fixedPoint": null + }, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + -181.0001220703125, + 112.85562853797796 + ] + ] + }, + { + "type": "text", + "version": 8, + "versionNonce": 2130757726, + "index": "b3S", + "isDeleted": false, + "id": "sJ15vR2f0X66S8wNh4ze8", + "fillStyle": "hachure", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1342, + "y": 563.6666870117188, + "strokeColor": "#1e1e1e", + "backgroundColor": "#ffec99", + "width": 42.39996337890625, + "height": 25, + "seed": 642714270, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1721912986294, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 1, + "text": "Fred", + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "Fred", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "type": "rectangle", + "version": 166, + "versionNonce": 1338907458, + "index": "b3T", + "isDeleted": false, + "id": "hxL6jwoK5bSRYPWEQQQAU", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1889.3333129882812, + "y": 501.6666564941406, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 213.3333740234375, + "height": 35, + "seed": 1247923614, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "boundElements": [ + { + "type": "text", + "id": "OhpAO4TrXau-3wa3XZPUT" + }, + { + "id": "tN5LwAGRsKgnQl2DQj_RH", + "type": "arrow" + } + ], + "updated": 1721913024489, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 101, + "versionNonce": 1781859806, + "index": "b3U", + "isDeleted": false, + "id": "OhpAO4TrXau-3wa3XZPUT", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1923.6300582885742, + "y": 506.6666564941406, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 144.73988342285156, + "height": 25, + "seed": 1314023902, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1721912997569, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 1, + "text": "Organisation 2", + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "hxL6jwoK5bSRYPWEQQQAU", + "originalText": "Organisation 2", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "type": "rectangle", + "version": 189, + "versionNonce": 1957737602, + "index": "b3V", + "isDeleted": false, + "id": "cSWNHl1f5yMkqBOhGwfZ2", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1867.3333129882812, + "y": 646.6667175292969, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 213.3333740234375, + "height": 45.99993896484375, + "seed": 63826846, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "boundElements": [ + { + "type": "text", + "id": "UpgvXu4B0V4nO_MR0vBeb" + }, + { + "id": "KzwZBboAiesOG5IrdqS9x", + "type": "arrow" + } + ], + "updated": 1721913011883, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 135, + "versionNonce": 32203778, + "index": "b3W", + "isDeleted": false, + "id": "UpgvXu4B0V4nO_MR0vBeb", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1928.2600326538086, + "y": 657.1666870117188, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 91.47993469238281, + "height": 25, + "seed": 9095134, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1721913092464, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 1, + "text": "Space 2.1", + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "cSWNHl1f5yMkqBOhGwfZ2", + "originalText": "Space 2.1", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "type": "arrow", + "version": 48, + "versionNonce": 850892674, + "index": "b3X", + "isDeleted": false, + "id": "KzwZBboAiesOG5IrdqS9x", + "fillStyle": "hachure", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1671.6667480468752, + "y": 665.0235691263217, + "strokeColor": "#1e1e1e", + "backgroundColor": "#ffec99", + "width": 194.66656494140625, + "height": 4.58812085973716, + "seed": 532093570, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1721914244922, + "link": null, + "locked": false, + "startBinding": { + "elementId": "-Xzx2qQspYp6E786HcHvg", + "focus": 0.06895271525895426, + "gap": 1, + "fixedPoint": null + }, + "endBinding": { + "elementId": "cSWNHl1f5yMkqBOhGwfZ2", + "focus": -0.09730390131362829, + "gap": 1, + "fixedPoint": null + }, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + 194.66656494140625, + 4.58812085973716 + ] + ] + }, + { + "type": "text", + "version": 8, + "versionNonce": 1926515294, + "index": "b3Y", + "isDeleted": false, + "id": "WLDXVUwuafL6hLH5NjUHR", + "fillStyle": "hachure", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1748, + "y": 638.3333740234375, + "strokeColor": "#1e1e1e", + "backgroundColor": "#ffec99", + "width": 42.39996337890625, + "height": 25, + "seed": 191550530, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1721913015567, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 1, + "text": "Fred", + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "Fred", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "type": "arrow", + "version": 157, + "versionNonce": 1114828131, + "index": "b3Z", + "isDeleted": false, + "id": "tN5LwAGRsKgnQl2DQj_RH", + "fillStyle": "hachure", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1673, + "y": 408.57749189525543, + "strokeColor": "#1e1e1e", + "backgroundColor": "#ffec99", + "width": 252.11200871589108, + "height": 80.42250810474457, + "seed": 2003655170, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1722495267550, + "link": null, + "locked": false, + "startBinding": { + "elementId": "pfyfK216xyZaKHIMr_Anh", + "focus": -0.5513095922844917, + "gap": 4.666748046875, + "fixedPoint": null + }, + "endBinding": { + "elementId": "hxL6jwoK5bSRYPWEQQQAU", + "focus": 0.1465999342928739, + "gap": 12.666656494140625, + "fixedPoint": null + }, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + 252.11200871589108, + 80.42250810474457 + ] + ] + }, + { + "type": "text", + "version": 9, + "versionNonce": 1242526686, + "index": "b3a", + "isDeleted": false, + "id": "SnUPkqYKMBfeFXP-R7hPl", + "fillStyle": "hachure", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1774, + "y": 403, + "strokeColor": "#1e1e1e", + "backgroundColor": "#ffec99", + "width": 54.179962158203125, + "height": 25, + "seed": 1403872002, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1721913027834, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 1, + "text": "Anton", + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "Anton", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "type": "text", + "version": 13, + "versionNonce": 1096192834, + "index": "b3b", + "isDeleted": false, + "id": "Z_ZdF7byGV2GU1jiiCMXC", + "fillStyle": "hachure", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1679.3333740234375, + "y": 527.6666870117188, + "strokeColor": "#1e1e1e", + "backgroundColor": "#ffec99", + "width": 68.93997192382812, + "height": 25, + "seed": 1444538498, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1721913043777, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 1, + "text": "WRITE", + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "WRITE", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "type": "text", + "version": 8, + "versionNonce": 1948981342, + "index": "b3c", + "isDeleted": false, + "id": "3hP1y8j4poQ9lh2cDdk8W", + "fillStyle": "hachure", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1683.3333740234375, + "y": 680.3333740234375, + "strokeColor": "#1e1e1e", + "backgroundColor": "#ffec99", + "width": 55.759979248046875, + "height": 25, + "seed": 590325314, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1721913041193, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 1, + "text": "READ", + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "READ", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "type": "text", + "version": 76, + "versionNonce": 1613005982, + "index": "b3e", + "isDeleted": false, + "id": "MOA9-VImJp8lkEnHLbDxM", + "fillStyle": "hachure", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1517.3333740234375, + "y": 848.3333740234375, + "strokeColor": "#1e1e1e", + "backgroundColor": "#ffec99", + "width": 275.1197509765625, + "height": 100, + "seed": 1405257474, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1721913167418, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 1, + "text": "Entity Space {\n spaceDevelopers: Users;\n spaceAuditors: Users;\n}", + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "Entity Space {\n spaceDevelopers: Users;\n spaceAuditors: Users;\n}", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "type": "text", + "version": 114, + "versionNonce": 1714802498, + "index": "b3f", + "isDeleted": false, + "id": "3AQ8uFvCVRNFhRhehrwWT", + "fillStyle": "hachure", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1518.666748046875, + "y": 1012.3333740234375, + "strokeColor": "#1e1e1e", + "backgroundColor": "#ffec99", + "width": 283.69976806640625, + "height": 125, + "seed": 1521336002, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1721914300393, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 1, + "text": "Entity Authorizations {\n user: User;\n key: UUID;\n role: {SpaceDeveloper, ...}\n} ", + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "Entity Authorizations {\n user: User;\n key: UUID;\n role: {SpaceDeveloper, ...}\n} ", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "type": "rectangle", + "version": 161, + "versionNonce": 1009075650, + "index": "b3h", + "isDeleted": false, + "id": "fsha3Keyz0jru5C7YATqa", + "fillStyle": "hachure", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1474.666748046875, + "y": 231.66668701171875, + "strokeColor": "#1e1e1e", + "backgroundColor": "#ffec99", + "width": 188, + "height": 62.66668701171875, + "seed": 564680322, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "boundElements": [ + { + "type": "text", + "id": "SVAjeWs5agHFXesyDumtV" + }, + { + "id": "NXOozLUuIHMinfBgqIGXf", + "type": "arrow" + } + ], + "updated": 1721913564000, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 160, + "versionNonce": 1963193474, + "index": "b3i", + "isDeleted": false, + "id": "SVAjeWs5agHFXesyDumtV", + "fillStyle": "hachure", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1543.2267684936523, + "y": 250.50003051757812, + "strokeColor": "#1e1e1e", + "backgroundColor": "#ffec99", + "width": 50.87995910644531, + "height": 25, + "seed": 1025390146, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1721913559951, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 1, + "text": "Admin", + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "fsha3Keyz0jru5C7YATqa", + "originalText": "Admin", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "type": "arrow", + "version": 174, + "versionNonce": 640364707, + "index": "b3j", + "isDeleted": false, + "id": "NXOozLUuIHMinfBgqIGXf", + "fillStyle": "hachure", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1572.0757824731695, + "y": 295.3333740234375, + "strokeColor": "#1e1e1e", + "backgroundColor": "#ffec99", + "width": 0.21186410793393406, + "height": 77.99996948242188, + "seed": 525734978, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1722495267551, + "link": null, + "locked": false, + "startBinding": { + "elementId": "fsha3Keyz0jru5C7YATqa", + "focus": -0.04041367296209274, + "gap": 1, + "fixedPoint": null + }, + "endBinding": { + "elementId": "pfyfK216xyZaKHIMr_Anh", + "focus": -0.02736045776209609, + "gap": 5.33331298828125, + "fixedPoint": null + }, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + -0.21186410793393406, + 77.99996948242188 + ] + ] + }, + { + "type": "text", + "version": 15, + "versionNonce": 11153090, + "index": "b3k", + "isDeleted": false, + "id": "fitv6v1-bJdCMlckjQB9U", + "fillStyle": "hachure", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1684.666748046875, + "y": 249.66668701171875, + "strokeColor": "#1e1e1e", + "backgroundColor": "#ffec99", + "width": 10.319992065429688, + "height": 25, + "seed": 493099906, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1721913583354, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 1, + "text": "*", + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "*", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "type": "arrow", + "version": 31, + "versionNonce": 1313840094, + "index": "b3l", + "isDeleted": false, + "id": "DM_FpJoMN3XDQyIIw9CdM", + "fillStyle": "hachure", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1073.4007280651817, + "y": 574.6666870117188, + "strokeColor": "#1e1e1e", + "backgroundColor": "#ffec99", + "width": 74.59927193481826, + "height": 63.66668701171875, + "seed": 1537779230, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1721915268481, + "link": null, + "locked": false, + "startBinding": { + "elementId": "_w8SZsvQQMMcw4VeJ6XVA", + "focus": -0.1941847113209475, + "gap": 1, + "fixedPoint": null + }, + "endBinding": { + "elementId": "OVMZNmul11YJNmqruU0_b", + "focus": -0.04470306498300194, + "gap": 4.333404541015625, + "fixedPoint": null + }, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + 74.59927193481826, + 63.66668701171875 + ] + ] + }, + { + "type": "arrow", + "version": 34, + "versionNonce": 939308574, + "index": "b3m", + "isDeleted": false, + "id": "K026ufvFyIg5TqTBlgOR8", + "fillStyle": "hachure", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1000, + "y": 571.6666870117188, + "strokeColor": "#1e1e1e", + "backgroundColor": "#ffec99", + "width": 86, + "height": 166.66668701171875, + "seed": 1521850846, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1721913807396, + "link": null, + "locked": false, + "startBinding": { + "elementId": "_w8SZsvQQMMcw4VeJ6XVA", + "focus": 0.25451926428332955, + "gap": 1, + "fixedPoint": null + }, + "endBinding": { + "elementId": "CDuhzzGQ2yvc672XDmbzj", + "focus": -0.793399731093657, + "gap": 1, + "fixedPoint": null + }, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + 86, + 166.66668701171875 + ] + ] + }, + { + "type": "rectangle", + "version": 589, + "versionNonce": 1844997437, + "index": "b3o", + "isDeleted": false, + "id": "56sRB7V0asfVGDUFKEstT", + "fillStyle": "hachure", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1526.0833333333323, + "y": -568.8809523809523, + "strokeColor": "#1e1e1e", + "backgroundColor": "#a5d8ff", + "width": 142.99999999999991, + "height": 276.66666666666674, + "seed": 93876547, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "boundElements": [], + "updated": 1732028516263, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 508, + "versionNonce": 93052051, + "index": "b3q", + "isDeleted": false, + "id": "CGq8JhEE4wdd0iVr8esou", + "fillStyle": "hachure", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1892.7424325167995, + "y": -507.72393689557146, + "strokeColor": "#1e1e1e", + "backgroundColor": "#a5d8ff", + "width": 969.7179565429688, + "height": 114.0620958195481, + "seed": 1699730701, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1732103792217, + "link": null, + "locked": false, + "fontSize": 30.41655888521283, + "fontFamily": 1, + "text": "Entity Findings \n @restrict [{ grant: ['READ', 'WRITE'], to:FindingsReviewer}]\n} ", + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "Entity Findings \n @restrict [{ grant: ['READ', 'WRITE'], to:FindingsReviewer}]\n} ", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "type": "rectangle", + "version": 263, + "versionNonce": 1350316566, + "index": "b3r", + "isDeleted": false, + "id": "dNjKCJvmsjCWTEVvFJS_g", + "fillStyle": "hachure", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1514.499999999999, + "y": -918.5, + "strokeColor": "#1e1e1e", + "backgroundColor": "#b2f2bb", + "width": 338.9999999999999, + "height": 203.00000000000003, + "seed": 267718339, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "boundElements": [ + { + "type": "text", + "id": "-bPXdTmyEeA5iW-EppOcf" + } + ], + "updated": 1722604795798, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 78, + "versionNonce": 93945686, + "index": "b3s", + "isDeleted": false, + "id": "-bPXdTmyEeA5iW-EppOcf", + "fillStyle": "hachure", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1588.872039794921, + "y": -834.5, + "strokeColor": "#1e1e1e", + "backgroundColor": "#a5d8ff", + "width": 190.25592041015625, + "height": 35, + "seed": 1516973517, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1722604795798, + "link": null, + "locked": false, + "fontSize": 28, + "fontFamily": 1, + "text": "Business Role", + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "dNjKCJvmsjCWTEVvFJS_g", + "originalText": "Business Role", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "type": "text", + "version": 933, + "versionNonce": 1130249427, + "index": "b3u", + "isDeleted": false, + "id": "Uv5dFTYPbSoEQEd68ieUy", + "fillStyle": "hachure", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 4.715576599245854, + "x": 1507.9128271983582, + "y": -448.8945963910432, + "strokeColor": "#1e1e1e", + "backgroundColor": "#a5d8ff", + "width": 193.86334663030163, + "height": 44.93131288574775, + "seed": 1295134083, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1732103606282, + "link": null, + "locked": false, + "fontSize": 35.94505030859816, + "fontFamily": 1, + "text": "CAP Grant", + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "CAP Grant", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "type": "text", + "version": 901, + "versionNonce": 1794858045, + "index": "b3v", + "isDeleted": false, + "id": "VLjzT9VWHGXL84n0c1rg7", + "fillStyle": "hachure", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 4.705194859894398, + "x": 1673.7560745081366, + "y": -454.98373545871226, + "strokeColor": "#1e1e1e", + "backgroundColor": "#a5d8ff", + "width": 200.89242518752332, + "height": 46.56042810243335, + "seed": 1638353837, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1732103613709, + "link": null, + "locked": false, + "fontSize": 37.24834248194668, + "fontFamily": 1, + "text": "CAP Grant", + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "CAP Grant", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "type": "text", + "version": 464, + "versionNonce": 1123166675, + "index": "b3w", + "isDeleted": false, + "id": "80a9vQIiOVxTPs9oOVe2o", + "fillStyle": "hachure", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1902.5119047619037, + "y": -240.34523809523807, + "strokeColor": "#1e1e1e", + "backgroundColor": "#a5d8ff", + "width": 930.2437133789062, + "height": 175, + "seed": 2004505283, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1732103573721, + "link": null, + "locked": false, + "fontSize": 28, + "fontFamily": 1, + "text": "Entity Findings \n@restrict [{ grant: 'READ', to:FindingsReporter,\n \n where: (exists reporters[user = $user]) }]\n", + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "Entity Findings \n@restrict [{ grant: 'READ', to:FindingsReporter,\n \n where: (exists reporters[user = $user]) }]\n", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "type": "rectangle", + "version": 194, + "versionNonce": 2135751882, + "index": "b3x", + "isDeleted": false, + "id": "hj6T2SrOYKL3_lXQV6DYX", + "fillStyle": "hachure", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1217.999999999999, + "y": -1138, + "strokeColor": "#1e1e1e", + "backgroundColor": "#b2f2bb", + "width": 639, + "height": 200, + "seed": 698420685, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "boundElements": [ + { + "type": "text", + "id": "F54zdy8YQggyKKgknlV9r" + } + ], + "updated": 1722838631203, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 158, + "versionNonce": 451335050, + "index": "b3y", + "isDeleted": false, + "id": "F54zdy8YQggyKKgknlV9r", + "fillStyle": "hachure", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1463.7940216064444, + "y": -1055.5, + "strokeColor": "#1e1e1e", + "backgroundColor": "#b2f2bb", + "width": 147.41195678710938, + "height": 35, + "seed": 881001443, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1722838631203, + "link": null, + "locked": false, + "fontSize": 28, + "fontFamily": 1, + "text": "AMS Policy", + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "hj6T2SrOYKL3_lXQV6DYX", + "originalText": "AMS Policy", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "type": "rectangle", + "version": 141, + "versionNonce": 1275590835, + "index": "b3z", + "isDeleted": false, + "id": "MyAbHIGWZ5aUCVNFlImhc", + "fillStyle": "hachure", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 953.3333333333326, + "y": -1389, + "strokeColor": "#1e1e1e", + "backgroundColor": "#ffec99", + "width": 897.6666666666666, + "height": 215, + "seed": 2018329965, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "boundElements": [ + { + "type": "text", + "id": "Up1Tyayq9RFmuU5UX1JeR" + } + ], + "updated": 1732089929891, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 92, + "versionNonce": 344746579, + "index": "b40", + "isDeleted": false, + "id": "Up1Tyayq9RFmuU5UX1JeR", + "fillStyle": "hachure", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1325.7266871134432, + "y": -1299, + "strokeColor": "#1e1e1e", + "backgroundColor": "#ffec99", + "width": 152.8799591064453, + "height": 35, + "seed": 2021295459, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1732089929891, + "link": null, + "locked": false, + "fontSize": 28, + "fontFamily": 1, + "text": "IAG, IDDS", + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "MyAbHIGWZ5aUCVNFlImhc", + "originalText": "IAG, IDDS", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "type": "rectangle", + "version": 166, + "versionNonce": 77256348, + "index": "b41", + "isDeleted": false, + "id": "urk17AqhtlpCFrqDleJ3V", + "fillStyle": "hachure", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1218.999999999999, + "y": -913.75, + "strokeColor": "#1e1e1e", + "backgroundColor": "#b2f2bb", + "width": 273, + "height": 195, + "seed": 1345046403, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "boundElements": [ + { + "type": "text", + "id": "zArSfo7N97D5WZANeBCWt" + } + ], + "updated": 1726146194854, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 153, + "versionNonce": 951952156, + "index": "b41V", + "isDeleted": false, + "id": "zArSfo7N97D5WZANeBCWt", + "fillStyle": "hachure", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1224.2380676269522, + "y": -851.25, + "strokeColor": "#1e1e1e", + "backgroundColor": "#ffec99", + "width": 262.52386474609375, + "height": 70, + "seed": 1288512131, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1726146194854, + "link": null, + "locked": false, + "fontSize": 28, + "fontFamily": 1, + "text": "Business Attribute\nFilter", + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "urk17AqhtlpCFrqDleJ3V", + "originalText": "Business Attribute Filter", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "type": "line", + "version": 404, + "versionNonce": 1359011507, + "index": "b43", + "isDeleted": false, + "id": "rv8u4P7VPvQ08FUWgAEj9", + "fillStyle": "hachure", + "strokeWidth": 2, + "strokeStyle": "dotted", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 655.9999999999989, + "y": -688.6666666666667, + "strokeColor": "#1e1e1e", + "backgroundColor": "#b2f2bb", + "width": 2921.999999999999, + "height": 14.666666666666856, + "seed": 636187469, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1732089962928, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 2921.999999999999, + 14.666666666666856 + ] + ] + }, + { + "type": "text", + "version": 527, + "versionNonce": 639264669, + "index": "b45", + "isDeleted": false, + "id": "yUBTYKTpf7fBNLpRBA4VD", + "fillStyle": "hachure", + "strokeWidth": 2, + "strokeStyle": "dotted", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2968.333333333332, + "y": -1124.333333333333, + "strokeColor": "#1e1e1e", + "backgroundColor": "#b2f2bb", + "width": 551.418212890625, + "height": 414.8484848484845, + "seed": 2044584717, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1732103762896, + "link": null, + "locked": false, + "fontSize": 33.18787878787876, + "fontFamily": 1, + "text": "\nApplication Policies:\nRecombination of Business Roles, \nFilters on System Attributes\n\nUser Assignment\nProvisioning\n\nUser administrators\nProvisioning automation", + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "\nApplication Policies:\nRecombination of Business Roles, \nFilters on System Attributes\n\nUser Assignment\nProvisioning\n\nUser administrators\nProvisioning automation", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "type": "line", + "version": 197, + "versionNonce": 2107795885, + "index": "b46", + "isDeleted": false, + "id": "P3yMXQ2OGGDb7oMSWB1Jj", + "fillStyle": "hachure", + "strokeWidth": 4, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 3616.857142857142, + "y": 411.85714285714204, + "strokeColor": "#1e1e1e", + "backgroundColor": "#ffec99", + "width": 14.285714285713766, + "height": 808.5714285714283, + "seed": 1572187533, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1722499101469, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 14.285714285713766, + 808.5714285714283 + ] + ] + }, + { + "type": "line", + "version": 129, + "versionNonce": 472777859, + "index": "b47", + "isDeleted": false, + "id": "qt4dAjNPkQXCdSukwqHD4", + "fillStyle": "hachure", + "strokeWidth": 4, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2466.8571428571427, + "y": 507.5714285714279, + "strokeColor": "#1e1e1e", + "backgroundColor": "#ffec99", + "width": 2090.0000000000005, + "height": 1.4285714285715017, + "seed": 1258420813, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1722498482329, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 2090.0000000000005, + 1.4285714285715017 + ] + ] + }, + { + "type": "line", + "version": 119, + "versionNonce": 538944643, + "index": "b48", + "isDeleted": false, + "id": "NvolLkPRXRzXVUga4ZPeT", + "fillStyle": "hachure", + "strokeWidth": 4, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2878.2857142857138, + "y": 423.2857142857136, + "strokeColor": "#1e1e1e", + "backgroundColor": "#ffec99", + "width": 2.857142857143117, + "height": 795.7142857142858, + "seed": 115249645, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1722499094331, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 2.857142857143117, + 795.7142857142858 + ] + ] + }, + { + "type": "text", + "version": 63, + "versionNonce": 322369069, + "index": "b4A", + "isDeleted": false, + "id": "0R8zwy_QfRtJMY-fvy7Ii", + "fillStyle": "hachure", + "strokeWidth": 4, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 3686.8571428571427, + "y": 423.9999999999994, + "strokeColor": "#1e1e1e", + "backgroundColor": "#ffec99", + "width": 410.6718444824219, + "height": 45, + "seed": 2061227363, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1722579317181, + "link": null, + "locked": false, + "fontSize": 36, + "fontFamily": 1, + "text": "Dynamic (Domain) Roles", + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "Dynamic (Domain) Roles", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "type": "text", + "version": 262, + "versionNonce": 1576829724, + "index": "b4B", + "isDeleted": false, + "id": "P-Tp9BW1Mns2OgqUfIG5D", + "fillStyle": "hachure", + "strokeWidth": 4, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 3645.428571428571, + "y": 578.9999999999995, + "strokeColor": "#1e1e1e", + "backgroundColor": "#ffec99", + "width": 918.1636962890625, + "height": 180, + "seed": 1179729891, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1726060433862, + "link": null, + "locked": false, + "fontSize": 36, + "fontFamily": 1, + "text": "+ Nice user experience (UI)\n+ Authorizations automatically derived from domain\n+ Flexible rules\n", + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "+ Nice user experience (UI)\n+ Authorizations automatically derived from domain\n+ Flexible rules\n", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "type": "text", + "version": 545, + "versionNonce": 97909796, + "index": "b4C", + "isDeleted": false, + "id": "CAWm0di4AoZ_fL8z6M2ak", + "fillStyle": "hachure", + "strokeWidth": 4, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2936.6785714285716, + "y": 593.6428571428564, + "strokeColor": "#1e1e1e", + "backgroundColor": "#ffec99", + "width": 678.707763671875, + "height": 585, + "seed": 333855245, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1726060426270, + "link": null, + "locked": false, + "fontSize": 36, + "fontFamily": 1, + "text": "+ Low dev effort\n+ Low security risc\n+ High integration (SCIM, provisioning) \n+ overarching integration possible\n+ Flexibility for user admins (filters)\n+ implicit access control \n (scales with users and entities)\n+ further dimensions \n (environment, location, time)\n+ IAS + AMS = XSUAA 2.0 (comp.)\n\n\n", + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "+ Low dev effort\n+ Low security risc\n+ High integration (SCIM, provisioning) \n+ overarching integration possible\n+ Flexibility for user admins (filters)\n+ implicit access control \n (scales with users and entities)\n+ further dimensions \n (environment, location, time)\n+ IAS + AMS = XSUAA 2.0 (comp.)\n\n\n", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "type": "text", + "version": 164, + "versionNonce": 684110861, + "index": "b4D", + "isDeleted": false, + "id": "8xiZTfU_IY-tLVDXdTxor", + "fillStyle": "hachure", + "strokeWidth": 4, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 3003.6229291643413, + "y": 426.49999999999926, + "strokeColor": "#1e1e1e", + "backgroundColor": "#ffec99", + "width": 464.02386474609375, + "height": 45, + "seed": 1234874509, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1722579299209, + "link": null, + "locked": false, + "fontSize": 36, + "fontFamily": 1, + "text": "Static Roles / Attributes", + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "Static Roles / Attributes", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "type": "text", + "version": 183, + "versionNonce": 1118829596, + "index": "b4E", + "isDeleted": false, + "id": "zN1XqV54yQ9ELoMcqFvJs", + "fillStyle": "hachure", + "strokeWidth": 4, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 3664.2380952380954, + "y": 891.8571428571422, + "strokeColor": "#1e1e1e", + "backgroundColor": "#ffec99", + "width": 915.2356567382812, + "height": 405, + "seed": 306992653, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1726060450047, + "link": null, + "locked": false, + "fontSize": 36, + "fontFamily": 1, + "text": "\n- High dev effort\n- Security risc\n- No integration (SCIM, provisioning)\n- cross-sectional roles/attributes hard to maintain\n- no filters ootb\n- does not scale well (#users, domain changes)\n- Role check requires DB lookup (part of filter)\n- IAS only - not comp. to XSUAA", + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "\n- High dev effort\n- Security risc\n- No integration (SCIM, provisioning)\n- cross-sectional roles/attributes hard to maintain\n- no filters ootb\n- does not scale well (#users, domain changes)\n- Role check requires DB lookup (part of filter)\n- IAS only - not comp. to XSUAA", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "type": "text", + "version": 125, + "versionNonce": 1013675597, + "index": "b4F", + "isDeleted": false, + "id": "cMDRtgn8Ll3EiDfZRTn5d", + "fillStyle": "hachure", + "strokeWidth": 4, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2959.952380952381, + "y": 1079.2380952380945, + "strokeColor": "#1e1e1e", + "backgroundColor": "#ffec99", + "width": 509.52789306640625, + "height": 225, + "seed": 904574627, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1722584353439, + "link": null, + "locked": false, + "fontSize": 36, + "fontFamily": 1, + "text": "- Static view (static roles, \n static attributes)\n- Static UI (technical)\n- Extra tool to assign roles\n", + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "- Static view (static roles, \n static attributes)\n- Static UI (technical)\n- Extra tool to assign roles\n", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "type": "rectangle", + "version": 69, + "versionNonce": 240630285, + "index": "b4G", + "isDeleted": false, + "id": "0WzIbZY2NQoLy3H_Wnjal", + "fillStyle": "hachure", + "strokeWidth": 4, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 907.7499999999993, + "y": 1577.0357142857138, + "strokeColor": "#1e1e1e", + "backgroundColor": "#ffec99", + "width": 878.5714285714284, + "height": 841.4285714285713, + "seed": 1747854573, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "boundElements": [], + "updated": 1722499323124, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 134, + "versionNonce": 1190184013, + "index": "b4I", + "isDeleted": false, + "id": "DZZAUoKKPEuIU-WKjERjp", + "fillStyle": "hachure", + "strokeWidth": 4, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 963.3758380223821, + "y": 1608.464285714285, + "strokeColor": "#1e1e1e", + "backgroundColor": "#ffec99", + "width": 517.4618336511048, + "height": 43.57142857142867, + "seed": 1895291235, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1722501303882, + "link": null, + "locked": false, + "fontSize": 34.85714285714293, + "fontFamily": 1, + "text": "Domain (per event)/ permission", + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "Domain (per event)/ permission", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "type": "rectangle", + "version": 137, + "versionNonce": 1227938413, + "index": "b4J", + "isDeleted": false, + "id": "qgIobbD8gC2wr3jLm8bk-", + "fillStyle": "solid", + "strokeWidth": 4, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 957.7499999999993, + "y": 1984.1785714285716, + "strokeColor": "#1e1e1e", + "backgroundColor": "#a5d8ff", + "width": 374.285714285714, + "height": 392.85714285714243, + "seed": 1451810115, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "boundElements": [ + { + "type": "text", + "id": "bqT4iZQNdiJ4M1BNYJ-aq" + } + ], + "updated": 1722499374576, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 114, + "versionNonce": 73729229, + "index": "b4K", + "isDeleted": false, + "id": "bqT4iZQNdiJ4M1BNYJ-aq", + "fillStyle": "solid", + "strokeWidth": 4, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 1058.1108976091648, + "y": 2158.1071428571427, + "strokeColor": "#1e1e1e", + "backgroundColor": "#a5d8ff", + "width": 173.5639190673828, + "height": 45, + "seed": 1421304109, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1722499374576, + "link": null, + "locked": false, + "fontSize": 36, + "fontFamily": 1, + "text": "Permission", + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "qgIobbD8gC2wr3jLm8bk-", + "originalText": "Permission", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "type": "rectangle", + "version": 119, + "versionNonce": 1107924973, + "index": "b4L", + "isDeleted": false, + "id": "DWTMjpGS659DE3RE_hM1F", + "fillStyle": "solid", + "strokeWidth": 4, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 1369.8928571428567, + "y": 2214.892857142857, + "strokeColor": "#1e1e1e", + "backgroundColor": "#a5d8ff", + "width": 369.9999999999998, + "height": 164.28571428571377, + "seed": 499296611, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "boundElements": [ + { + "type": "text", + "id": "uzWFq6hdoLFF6HWWfzb0u" + } + ], + "updated": 1722499369942, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 96, + "versionNonce": 776606285, + "index": "b4M", + "isDeleted": false, + "id": "uzWFq6hdoLFF6HWWfzb0u", + "fillStyle": "solid", + "strokeWidth": 4, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 1468.1108976091652, + "y": 2274.5357142857138, + "strokeColor": "#1e1e1e", + "backgroundColor": "#a5d8ff", + "width": 173.5639190673828, + "height": 45, + "seed": 923632899, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1722499369942, + "link": null, + "locked": false, + "fontSize": 36, + "fontFamily": 1, + "text": "Permission", + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "DWTMjpGS659DE3RE_hM1F", + "originalText": "Permission", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "type": "rectangle", + "version": 168, + "versionNonce": 427737229, + "index": "b4N", + "isDeleted": false, + "id": "3KHYE-qW6FmESTATsUukG", + "fillStyle": "solid", + "strokeWidth": 4, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 1371.3214285714278, + "y": 2022.0357142857135, + "strokeColor": "#1e1e1e", + "backgroundColor": "#a5d8ff", + "width": 369.9999999999998, + "height": 164.28571428571377, + "seed": 99418637, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "boundElements": [ + { + "type": "text", + "id": "UfbDFeSdnOrZKQXVkcgKZ" + } + ], + "updated": 1722499371893, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 146, + "versionNonce": 2052138733, + "index": "b4O", + "isDeleted": false, + "id": "UfbDFeSdnOrZKQXVkcgKZ", + "fillStyle": "solid", + "strokeWidth": 4, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 1469.5394690377361, + "y": 2081.6785714285706, + "strokeColor": "#1e1e1e", + "backgroundColor": "#a5d8ff", + "width": 173.5639190673828, + "height": 45, + "seed": 310065261, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1722499371893, + "link": null, + "locked": false, + "fontSize": 36, + "fontFamily": 1, + "text": "Permission", + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "3KHYE-qW6FmESTATsUukG", + "originalText": "Permission", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "type": "rectangle", + "version": 228, + "versionNonce": 439380013, + "index": "b4P", + "isDeleted": false, + "id": "B3Sbgz3klNO8KNyg7k6S-", + "fillStyle": "solid", + "strokeWidth": 4, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 1369.1785714285706, + "y": 1687.7499999999998, + "strokeColor": "#1e1e1e", + "backgroundColor": "#a5d8ff", + "width": 377.1428571428571, + "height": 299.99999999999955, + "seed": 41369763, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "boundElements": [ + { + "type": "text", + "id": "aGJ3cIJd8GtmRqPLIDJx6" + } + ], + "updated": 1722501300017, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 205, + "versionNonce": 833411725, + "index": "b4Q", + "isDeleted": false, + "id": "aGJ3cIJd8GtmRqPLIDJx6", + "fillStyle": "solid", + "strokeWidth": 4, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 1470.9680404663077, + "y": 1815.2499999999995, + "strokeColor": "#1e1e1e", + "backgroundColor": "#a5d8ff", + "width": 173.5639190673828, + "height": 45, + "seed": 531444803, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1722501300017, + "link": null, + "locked": false, + "fontSize": 36, + "fontFamily": 1, + "text": "Permission", + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "B3Sbgz3klNO8KNyg7k6S-", + "originalText": "Permission", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "type": "rectangle", + "version": 294, + "versionNonce": 1686849485, + "index": "b4R", + "isDeleted": false, + "id": "_iM8mNFtEIEizwso1mJaw", + "fillStyle": "solid", + "strokeWidth": 4, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 964.8928571428567, + "y": 1689.178571428571, + "strokeColor": "#1e1e1e", + "backgroundColor": "#a5d8ff", + "width": 364.28571428571394, + "height": 259.9999999999997, + "seed": 357807555, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "boundElements": [ + { + "type": "text", + "id": "HnpFF6-Q-K1IBbeNi67NH" + } + ], + "updated": 1722516874132, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 272, + "versionNonce": 1921492867, + "index": "b4S", + "isDeleted": false, + "id": "HnpFF6-Q-K1IBbeNi67NH", + "fillStyle": "solid", + "strokeWidth": 4, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 1060.2537547520221, + "y": 1796.6785714285709, + "strokeColor": "#1e1e1e", + "backgroundColor": "#a5d8ff", + "width": 173.5639190673828, + "height": 45, + "seed": 1165886819, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1722516874132, + "link": null, + "locked": false, + "fontSize": 36, + "fontFamily": 1, + "text": "Permission", + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "_iM8mNFtEIEizwso1mJaw", + "originalText": "Permission", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "type": "rectangle", + "version": 351, + "versionNonce": 341403309, + "index": "b4a", + "isDeleted": false, + "id": "u2LkPyYegNqGSqGEA02Tn", + "fillStyle": "hachure", + "strokeWidth": 4, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 2377.0357142857138, + "y": 1590.6071428571415, + "strokeColor": "#1e1e1e", + "backgroundColor": "#ffec99", + "width": 874.2857142857139, + "height": 822.857142857143, + "seed": 1731328707, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "boundElements": [], + "updated": 1722499830596, + "link": null, + "locked": false + }, + { + "type": "rectangle", + "version": 401, + "versionNonce": 1107824579, + "index": "b4c", + "isDeleted": false, + "id": "EmbN_EH5pdwqx0cMdoqbn", + "fillStyle": "solid", + "strokeWidth": 4, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 2469.892857142857, + "y": 2112.749999999999, + "strokeColor": "#1e1e1e", + "backgroundColor": "#a5d8ff", + "width": 364.28571428571394, + "height": 259.9999999999997, + "seed": 1583631715, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "boundElements": [ + { + "type": "text", + "id": "x-Yxnp0ne69gv-FmiNaZx" + } + ], + "updated": 1722499747632, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 397, + "versionNonce": 1318526819, + "index": "b4d", + "isDeleted": false, + "id": "x-Yxnp0ne69gv-FmiNaZx", + "fillStyle": "solid", + "strokeWidth": 4, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 2512.943764822823, + "y": 2220.249999999999, + "strokeColor": "#1e1e1e", + "backgroundColor": "#a5d8ff", + "width": 278.18389892578125, + "height": 45, + "seed": 1004185859, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1722499747632, + "link": null, + "locked": false, + "fontSize": 36, + "fontFamily": 1, + "text": "Attribute value", + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "EmbN_EH5pdwqx0cMdoqbn", + "originalText": "Attribute value", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "type": "rectangle", + "version": 543, + "versionNonce": 865287779, + "index": "b4e", + "isDeleted": false, + "id": "EEcHjKvZq3WTspVQdjMeb", + "fillStyle": "solid", + "strokeWidth": 4, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 2455.607142857142, + "y": 1681.321428571429, + "strokeColor": "#1e1e1e", + "backgroundColor": "#a5d8ff", + "width": 375.7142857142851, + "height": 391.42857142857093, + "seed": 1191196259, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "boundElements": [ + { + "type": "text", + "id": "ETIOJcbqklWGx28BquLW1" + } + ], + "updated": 1722499746348, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 539, + "versionNonce": 1342405123, + "index": "b4f", + "isDeleted": false, + "id": "ETIOJcbqklWGx28BquLW1", + "fillStyle": "solid", + "strokeWidth": 4, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 2504.3723362513942, + "y": 1854.5357142857144, + "strokeColor": "#1e1e1e", + "backgroundColor": "#a5d8ff", + "width": 278.18389892578125, + "height": 45, + "seed": 1527869955, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1722499746348, + "link": null, + "locked": false, + "fontSize": 36, + "fontFamily": 1, + "text": "Attribute value", + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "EEcHjKvZq3WTspVQdjMeb", + "originalText": "Attribute value", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "type": "rectangle", + "version": 593, + "versionNonce": 1737460035, + "index": "b4g", + "isDeleted": false, + "id": "BPqIERsHmj4uZ_cIG7uVM", + "fillStyle": "solid", + "strokeWidth": 4, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 2854.1785714285706, + "y": 1677.0357142857135, + "strokeColor": "#1e1e1e", + "backgroundColor": "#a5d8ff", + "width": 108.57142857142766, + "height": 695.7142857142852, + "seed": 2014762467, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "boundElements": [ + { + "type": "text", + "id": "yBwFlY3HB6u6-eWC-x5mO" + } + ], + "updated": 1722499840350, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 599, + "versionNonce": 401828067, + "index": "b4h", + "isDeleted": false, + "id": "yBwFlY3HB6u6-eWC-x5mO", + "fillStyle": "solid", + "strokeWidth": 4, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 2860.0802982875266, + "y": 1957.3928571428562, + "strokeColor": "#1e1e1e", + "backgroundColor": "#a5d8ff", + "width": 96.76797485351562, + "height": 135, + "seed": 2133336451, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1722499840350, + "link": null, + "locked": false, + "fontSize": 36, + "fontFamily": 1, + "text": "Attri\nbute \nvalue", + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "BPqIERsHmj4uZ_cIG7uVM", + "originalText": "Attribute value", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "type": "rectangle", + "version": 734, + "versionNonce": 2027350093, + "index": "b4i", + "isDeleted": false, + "id": "iP1xy1DYFHD3osbhD9h3J", + "fillStyle": "solid", + "strokeWidth": 4, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 2989.1785714285716, + "y": 1679.1785714285709, + "strokeColor": "#1e1e1e", + "backgroundColor": "#a5d8ff", + "width": 232.8571428571424, + "height": 207.1428571428566, + "seed": 1914289037, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "boundElements": [ + { + "type": "text", + "id": "YFWDKZdJmP3UoAXItgqMU" + } + ], + "updated": 1722499853638, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 750, + "versionNonce": 1406031533, + "index": "b4j", + "isDeleted": false, + "id": "YFWDKZdJmP3UoAXItgqMU", + "fillStyle": "solid", + "strokeWidth": 4, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 3013.393168858119, + "y": 1737.749999999999, + "strokeColor": "#1e1e1e", + "backgroundColor": "#a5d8ff", + "width": 184.42794799804688, + "height": 90, + "seed": 466928109, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1722499853639, + "link": null, + "locked": false, + "fontSize": 36, + "fontFamily": 1, + "text": "Attribute \nvalue", + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "iP1xy1DYFHD3osbhD9h3J", + "originalText": "Attribute value", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "type": "rectangle", + "version": 812, + "versionNonce": 101622819, + "index": "b4k", + "isDeleted": false, + "id": "C6sfLhYnxV8ksbbpLZKUR", + "fillStyle": "solid", + "strokeWidth": 4, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 2988.4642857142853, + "y": 1909.1785714285706, + "strokeColor": "#1e1e1e", + "backgroundColor": "#a5d8ff", + "width": 234.28571428571396, + "height": 464.2857142857136, + "seed": 1879075299, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "boundElements": [ + { + "type": "text", + "id": "28djEhOxqvzfPX9mS4I_R" + } + ], + "updated": 1722499863176, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 829, + "versionNonce": 294397891, + "index": "b4l", + "isDeleted": false, + "id": "28djEhOxqvzfPX9mS4I_R", + "fillStyle": "solid", + "strokeWidth": 4, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 3013.3931688581188, + "y": 2096.3214285714275, + "strokeColor": "#1e1e1e", + "backgroundColor": "#a5d8ff", + "width": 184.42794799804688, + "height": 90, + "seed": 1824470403, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1722499863176, + "link": null, + "locked": false, + "fontSize": 36, + "fontFamily": 1, + "text": "Attribute \nvalue", + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "C6sfLhYnxV8ksbbpLZKUR", + "originalText": "Attribute value", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "type": "rectangle", + "version": 103, + "versionNonce": 1599368013, + "index": "b4m", + "isDeleted": false, + "id": "o239FaC8lQdyDbCEXYra6", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "dashed", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 2843.4642857142844, + "y": 1564.1785714285706, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 451.4285714285714, + "height": 898.5714285714281, + "seed": 557929219, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "boundElements": [], + "updated": 1722499892425, + "link": null, + "locked": false + }, + { + "type": "line", + "version": 30, + "versionNonce": 967572035, + "index": "b4n", + "isDeleted": false, + "id": "g32J7eA-jlQGKO33gj_ff", + "fillStyle": "solid", + "strokeWidth": 4, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 3294.892857142856, + "y": 1609.8928571428567, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 94.28571428571377, + "height": 24.28571428571422, + "seed": 61604771, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1722505038111, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 94.28571428571377, + -24.28571428571422 + ] + ] + }, + { + "type": "text", + "version": 43, + "versionNonce": 1425961219, + "index": "b4o", + "isDeleted": false, + "id": "M7wxfTpoRxW_ZtmfbZJa_", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 3406.3214285714275, + "y": 1558.464285714285, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 279.47589111328125, + "height": 45, + "seed": 1189284227, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1722499915790, + "link": null, + "locked": false, + "fontSize": 36, + "fontFamily": 1, + "text": "Attribute Filter", + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "Attribute Filter", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "type": "text", + "version": 45, + "versionNonce": 2129376077, + "index": "b4q", + "isDeleted": false, + "id": "xEXxC8FChbn2f5XQaOzwj", + "fillStyle": "solid", + "strokeWidth": 4, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 922.0357142857135, + "y": 1479.8928571428569, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 394.49981689453125, + "height": 45, + "seed": 420035277, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1722501213080, + "link": null, + "locked": false, + "fontSize": 36, + "fontFamily": 1, + "text": "Classes by permissions", + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "Classes by permissions", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "type": "text", + "version": 61, + "versionNonce": 428923661, + "index": "b4r", + "isDeleted": false, + "id": "JZ1KJ5YA2TuQ-kaoLx3sF", + "fillStyle": "solid", + "strokeWidth": 4, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 2376.3214285714275, + "y": 1487.0357142857138, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 544.0317993164062, + "height": 45, + "seed": 365071235, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1722501210561, + "link": null, + "locked": false, + "fontSize": 36, + "fontFamily": 1, + "text": "Classes by attribute dimension", + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "Classes by attribute dimension", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "type": "text", + "version": 85, + "versionNonce": 296306093, + "index": "b4s", + "isDeleted": false, + "id": "VMAOVJcRU5ELtGvAJ0TBY", + "fillStyle": "solid", + "strokeWidth": 4, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 2423.4642857142844, + "y": 1612.7499999999998, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 341.25592041015625, + "height": 45, + "seed": 663806211, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1722501288593, + "link": null, + "locked": false, + "fontSize": 36, + "fontFamily": 1, + "text": "Domain / Attribute", + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "Domain / Attribute", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "type": "rectangle", + "version": 289, + "versionNonce": 142319363, + "index": "b4t", + "isDeleted": false, + "id": "rDDz-qUAPm6YSVoNsiysM", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "dashed", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 1352.0357142857126, + "y": 2002.0357142857142, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 485.7142857142855, + "height": 479.99999999999983, + "seed": 1253044877, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "boundElements": [], + "updated": 1722505035014, + "link": null, + "locked": false + }, + { + "type": "line", + "version": 74, + "versionNonce": 1417080813, + "index": "b4u", + "isDeleted": false, + "id": "AnKSRFXriY-uDmm_hWbR2", + "fillStyle": "solid", + "strokeWidth": 4, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 1842.0357142857138, + "y": 2024.892857142857, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 94.28571428571377, + "height": 24.28571428571422, + "seed": 342787459, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1722505040963, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 94.28571428571377, + -24.28571428571422 + ] + ] + }, + { + "type": "text", + "version": 115, + "versionNonce": 1745879107, + "index": "b4v", + "isDeleted": false, + "id": "eGAZHEcJDItF_l1jxAnLs", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 1950.6439136323465, + "y": 1967.3928571428569, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 243.47190856933594, + "height": 45, + "seed": 2078364835, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1722508453520, + "link": null, + "locked": false, + "fontSize": 36, + "fontFamily": 1, + "text": "Business Role", + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "Business Role", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "type": "text", + "version": 88, + "versionNonce": 1274278986, + "index": "b4w", + "isDeleted": false, + "id": "Bfb6-RpCauj8GHnNCB9wK", + "fillStyle": "solid", + "strokeWidth": 4, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 544.7738095238094, + "y": 211.61904761904816, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 566.1397705078125, + "height": 45, + "seed": 1628647491, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1722838850553, + "link": null, + "locked": false, + "fontSize": 36, + "fontFamily": 1, + "text": "Dynamic Roles via Domain (ACL)", + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "Dynamic Roles via Domain (ACL)", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "type": "text", + "version": 48, + "versionNonce": 128094061, + "index": "b4y", + "isDeleted": false, + "id": "nEqgcxtai10WN8sU-f_C9", + "fillStyle": "solid", + "strokeWidth": 4, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 1672.333822341192, + "y": 1470.904761904763, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 97.37997436523438, + "height": 45, + "seed": 1126698467, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1722516876996, + "link": null, + "locked": false, + "fontSize": 36, + "fontFamily": 1, + "text": "RBAC", + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "RBAC", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "type": "text", + "version": 96, + "versionNonce": 852024099, + "index": "b4z", + "isDeleted": false, + "id": "g8HxOdNeLGg4eSDVHI5Hn", + "fillStyle": "solid", + "strokeWidth": 4, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 3185.190965198335, + "y": 1470.9047619047628, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 96.58798217773438, + "height": 45, + "seed": 1269651501, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1722516883031, + "link": null, + "locked": false, + "fontSize": 36, + "fontFamily": 1, + "text": "ABAC", + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "ABAC", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "type": "rectangle", + "version": 435, + "versionNonce": 2124897933, + "index": "b50", + "isDeleted": false, + "id": "BbkQPNpwf9RkwZqR734Gw", + "fillStyle": "hachure", + "strokeWidth": 4, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 4001.3763645717036, + "y": 1605.8043808710006, + "strokeColor": "#1e1e1e", + "backgroundColor": "#ffec99", + "width": 874.2857142857139, + "height": 822.857142857143, + "seed": 1066276451, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "boundElements": [], + "updated": 1722579686822, + "link": null, + "locked": false + }, + { + "type": "rectangle", + "version": 482, + "versionNonce": 1536362755, + "index": "b51", + "isDeleted": false, + "id": "3cfVk4lDzLGq0PdvBNj4b", + "fillStyle": "solid", + "strokeWidth": 4, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 4238.04303123837, + "y": 1693.899618966239, + "strokeColor": "#1e1e1e", + "backgroundColor": "#a5d8ff", + "width": 332.6190476190479, + "height": 251.66666666666623, + "seed": 1320660973, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "boundElements": [ + { + "type": "text", + "id": "xrGwbDncSIkcPxGPSZPE_" + } + ], + "updated": 1722579689543, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 523, + "versionNonce": 116453539, + "index": "b52", + "isDeleted": false, + "id": "xrGwbDncSIkcPxGPSZPE_", + "fillStyle": "solid", + "strokeWidth": 4, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 4325.06257702055, + "y": 1797.232952299572, + "strokeColor": "#1e1e1e", + "backgroundColor": "#a5d8ff", + "width": 158.5799560546875, + "height": 45, + "seed": 762653261, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1722579689543, + "link": null, + "locked": false, + "fontSize": 36, + "fontFamily": 1, + "text": "document", + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "3cfVk4lDzLGq0PdvBNj4b", + "originalText": "document", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "type": "rectangle", + "version": 658, + "versionNonce": 242740067, + "index": "b53", + "isDeleted": false, + "id": "WPAeLjlxK8lDHvDK_9c4U", + "fillStyle": "solid", + "strokeWidth": 4, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 4077.5668407621797, + "y": 1686.0424761090962, + "strokeColor": "#1e1e1e", + "backgroundColor": "#a5d8ff", + "width": 108.57142857142766, + "height": 695.7142857142852, + "seed": 855226115, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "boundElements": [ + { + "type": "text", + "id": "8I15fenmpFHwHVeN3QECq" + } + ], + "updated": 1722579680826, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 669, + "versionNonce": 989341891, + "index": "b54", + "isDeleted": false, + "id": "8I15fenmpFHwHVeN3QECq", + "fillStyle": "solid", + "strokeWidth": 4, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 4097.452561151409, + "y": 2011.399618966239, + "strokeColor": "#1e1e1e", + "backgroundColor": "#a5d8ff", + "width": 68.79998779296875, + "height": 45, + "seed": 1498537635, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1722579683885, + "link": null, + "locked": false, + "fontSize": 36, + "fontFamily": 1, + "text": "ACL", + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "WPAeLjlxK8lDHvDK_9c4U", + "originalText": "ACL", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "type": "rectangle", + "version": 569, + "versionNonce": 1973958755, + "index": "b55", + "isDeleted": false, + "id": "LozIuFWIz5B9izXInpBFv", + "fillStyle": "solid", + "strokeWidth": 4, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 4607.209697905037, + "y": 1699.7329522995728, + "strokeColor": "#1e1e1e", + "backgroundColor": "#a5d8ff", + "width": 240.95238095238176, + "height": 241.6666666666662, + "seed": 1647411149, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "boundElements": [ + { + "type": "text", + "id": "jlXUuAI7ebJXpwpyXATi7" + } + ], + "updated": 1722579752819, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 611, + "versionNonce": 963195907, + "index": "b56", + "isDeleted": false, + "id": "jlXUuAI7ebJXpwpyXATi7", + "fillStyle": "solid", + "strokeWidth": 4, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 4648.395910353884, + "y": 1798.0662856329059, + "strokeColor": "#1e1e1e", + "backgroundColor": "#a5d8ff", + "width": 158.5799560546875, + "height": 45, + "seed": 1961492013, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1722579752819, + "link": null, + "locked": false, + "fontSize": 36, + "fontFamily": 1, + "text": "document", + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "LozIuFWIz5B9izXInpBFv", + "originalText": "document", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "type": "rectangle", + "version": 750, + "versionNonce": 736056963, + "index": "b57", + "isDeleted": false, + "id": "5LasksffS1WGPkBgSaqAK", + "fillStyle": "solid", + "strokeWidth": 4, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 4248.0430312383705, + "y": 1984.7329522995733, + "strokeColor": "#1e1e1e", + "backgroundColor": "#a5d8ff", + "width": 234.28571428571573, + "height": 113.33333333333228, + "seed": 2068149411, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "boundElements": [ + { + "type": "text", + "id": "cjYoxkNFgC1-WgO41b3Vm" + } + ], + "updated": 1722579713954, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 792, + "versionNonce": 1935117859, + "index": "b58", + "isDeleted": false, + "id": "cjYoxkNFgC1-WgO41b3Vm", + "fillStyle": "solid", + "strokeWidth": 4, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 4285.895910353885, + "y": 2018.8996189662394, + "strokeColor": "#1e1e1e", + "backgroundColor": "#a5d8ff", + "width": 158.5799560546875, + "height": 45, + "seed": 1217718339, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1722579713954, + "link": null, + "locked": false, + "fontSize": 36, + "fontFamily": 1, + "text": "document", + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "5LasksffS1WGPkBgSaqAK", + "originalText": "document", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "type": "rectangle", + "version": 899, + "versionNonce": 2092594051, + "index": "b59", + "isDeleted": false, + "id": "loa17cO4XHMWyA-xLcC0D", + "fillStyle": "solid", + "strokeWidth": 4, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 4514.709697905035, + "y": 1982.2329522995728, + "strokeColor": "#1e1e1e", + "backgroundColor": "#a5d8ff", + "width": 337.61904761904975, + "height": 229.99999999999918, + "seed": 1532025421, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "boundElements": [ + { + "type": "text", + "id": "ECwEJ_rRmnZCn4zrdTK5f" + } + ], + "updated": 1722579750556, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 942, + "versionNonce": 348588835, + "index": "b5A", + "isDeleted": false, + "id": "ECwEJ_rRmnZCn4zrdTK5f", + "fillStyle": "solid", + "strokeWidth": 4, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 4604.229243687216, + "y": 2074.7329522995724, + "strokeColor": "#1e1e1e", + "backgroundColor": "#a5d8ff", + "width": 158.5799560546875, + "height": 45, + "seed": 1983514797, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1722579750556, + "link": null, + "locked": false, + "fontSize": 36, + "fontFamily": 1, + "text": "document", + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "loa17cO4XHMWyA-xLcC0D", + "originalText": "document", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "type": "rectangle", + "version": 980, + "versionNonce": 499599491, + "index": "b5B", + "isDeleted": false, + "id": "y27aHau_dD1S-xeLqf_SH", + "fillStyle": "solid", + "strokeWidth": 4, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 4258.043031238368, + "y": 2240.566285632906, + "strokeColor": "#1e1e1e", + "backgroundColor": "#a5d8ff", + "width": 574.2857142857162, + "height": 119.99999999999918, + "seed": 107421059, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "boundElements": [ + { + "type": "text", + "id": "uIxESl_sXTeE4R7SJ5oBE" + } + ], + "updated": 1722579734450, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 1024, + "versionNonce": 124862499, + "index": "b5C", + "isDeleted": false, + "id": "uIxESl_sXTeE4R7SJ5oBE", + "fillStyle": "solid", + "strokeWidth": 4, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 4465.895910353882, + "y": 2278.0662856329054, + "strokeColor": "#1e1e1e", + "backgroundColor": "#a5d8ff", + "width": 158.5799560546875, + "height": 45, + "seed": 1464759587, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1722579734450, + "link": null, + "locked": false, + "fontSize": 36, + "fontFamily": 1, + "text": "document", + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "y27aHau_dD1S-xeLqf_SH", + "originalText": "document", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "type": "rectangle", + "version": 828, + "versionNonce": 1671323875, + "index": "b5D", + "isDeleted": false, + "id": "C_HBEIj4YOQKp8Eh0g7nQ", + "fillStyle": "solid", + "strokeWidth": 4, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 4254.709697905036, + "y": 2118.899618966239, + "strokeColor": "#1e1e1e", + "backgroundColor": "#a5d8ff", + "width": 234.28571428571573, + "height": 98.33333333333309, + "seed": 265907971, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "boundElements": [ + { + "type": "text", + "id": "6YmWM9RnKl7ola2r7WWYZ" + } + ], + "updated": 1722579747652, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 871, + "versionNonce": 1199983747, + "index": "b5E", + "isDeleted": false, + "id": "6YmWM9RnKl7ola2r7WWYZ", + "fillStyle": "solid", + "strokeWidth": 4, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 4292.56257702055, + "y": 2145.5662856329054, + "strokeColor": "#1e1e1e", + "backgroundColor": "#a5d8ff", + "width": 158.5799560546875, + "height": 45, + "seed": 1903859363, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1722579747652, + "link": null, + "locked": false, + "fontSize": 36, + "fontFamily": 1, + "text": "document", + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "C_HBEIj4YOQKp8Eh0g7nQ", + "originalText": "document", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "type": "rectangle", + "version": 249, + "versionNonce": 1781479725, + "index": "b5F", + "isDeleted": false, + "id": "OKA5udmkoZqPd177yzqCE", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "dashed", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 4222.804936000274, + "y": 1676.2805713471917, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 754.7619047619046, + "height": 280.23809523809456, + "seed": 1968402147, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "boundElements": [], + "updated": 1722579770938, + "link": null, + "locked": false + }, + { + "type": "line", + "version": 87, + "versionNonce": 542779117, + "index": "b5G", + "isDeleted": false, + "id": "OIDbgZazlUIMQKqChYe2t", + "fillStyle": "solid", + "strokeWidth": 4, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 4974.709697905036, + "y": 1684.3758094424295, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 94.28571428571377, + "height": 24.28571428571422, + "seed": 1144098659, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1722579780804, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 94.28571428571377, + -24.28571428571422 + ] + ] + }, + { + "type": "text", + "version": 113, + "versionNonce": 912653389, + "index": "b5H", + "isDeleted": false, + "id": "uwa7_V7qacVDTQu7yEiN0", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 5072.114609491252, + "y": 1639.7329522995726, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 346.4359130859375, + "height": 45, + "seed": 878103971, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1722579799229, + "link": null, + "locked": false, + "fontSize": 36, + "fontFamily": 1, + "text": "ACL entry for User", + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "ACL entry for User", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "type": "text", + "version": 151, + "versionNonce": 265208333, + "index": "b5I", + "isDeleted": false, + "id": "xmgdTOIvzh3qC6MAM8zxK", + "fillStyle": "solid", + "strokeWidth": 4, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 4836.891897292358, + "y": 1474.7329522995724, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 68.79998779296875, + "height": 45, + "seed": 2062145091, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1722579806438, + "link": null, + "locked": false, + "fontSize": 36, + "fontFamily": 1, + "text": "ACL", + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "ACL", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "type": "text", + "version": 117, + "versionNonce": 623227181, + "index": "b5J", + "isDeleted": false, + "id": "Sv7X_wQm9kwz4bJIoXvv3", + "fillStyle": "solid", + "strokeWidth": 4, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 4029.8366553896894, + "y": 1524.7329522995724, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 385.7518615722656, + "height": 45, + "seed": 1550892163, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1722579872956, + "link": null, + "locked": false, + "fontSize": 36, + "fontFamily": 1, + "text": "Classes by documents", + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "Classes by documents", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "type": "text", + "version": 152, + "versionNonce": 893251533, + "index": "b5K", + "isDeleted": false, + "id": "_WLRKpyN0WqQMU29hglyI", + "fillStyle": "solid", + "strokeWidth": 4, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 4031.2245948428144, + "y": 1618.0662856329056, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 359.6959228515625, + "height": 45, + "seed": 1549086979, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1722579862676, + "link": null, + "locked": false, + "fontSize": 36, + "fontFamily": 1, + "text": "Domain / Documents", + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "Domain / Documents", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "type": "rectangle", + "version": 504, + "versionNonce": 363288691, + "index": "b5Q", + "isDeleted": false, + "id": "loEAMKsQuTyyJhcy_jRwD", + "fillStyle": "hachure", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1523.602555047893, + "y": -665.8741905575705, + "strokeColor": "#1e1e1e", + "backgroundColor": "#a5d8ff", + "width": 322.5714285714289, + "height": 78.83333333333353, + "seed": 1287022915, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "boundElements": [ + { + "type": "text", + "id": "WkLr-hwKuXHz0EQdBDYyp" + } + ], + "updated": 1732028458715, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 321, + "versionNonce": 2057949715, + "index": "b5R", + "isDeleted": false, + "id": "WkLr-hwKuXHz0EQdBDYyp", + "fillStyle": "hachure", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1620.5042819068497, + "y": -643.9575238909038, + "strokeColor": "#1e1e1e", + "backgroundColor": "#a5d8ff", + "width": 128.76797485351562, + "height": 35, + "seed": 1967016163, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1732028458715, + "link": null, + "locked": false, + "fontSize": 28, + "fontFamily": 1, + "text": "CAP Role", + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "loEAMKsQuTyyJhcy_jRwD", + "originalText": "CAP Role", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "type": "line", + "version": 340, + "versionNonce": 669264221, + "index": "b5S", + "isDeleted": false, + "id": "jvtrQ1NidUOBuA9RRNto9", + "fillStyle": "hachure", + "strokeWidth": 2, + "strokeStyle": "dotted", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 621.9750490129475, + "y": -1154.6941938854932, + "strokeColor": "#1e1e1e", + "backgroundColor": "#b2f2bb", + "width": 2962, + "height": 3.0000000000002274, + "seed": 1882935907, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1732089944090, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 2962, + 3.0000000000002274 + ] + ] + }, + { + "type": "text", + "version": 286, + "versionNonce": 1561273587, + "index": "b5T", + "isDeleted": false, + "id": "_Lz924JtCQ2n6E8QBe8qF", + "fillStyle": "hachure", + "strokeWidth": 4, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 634.3525550478926, + "y": -1364.5220550352615, + "strokeColor": "#1e1e1e", + "backgroundColor": "#a5d8ff", + "width": 197.04462687174464, + "height": 128.0050073348339, + "seed": 1219150915, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1732091395671, + "link": null, + "locked": false, + "fontSize": 51.202002933933564, + "fontFamily": 1, + "text": "SAP \nsystems", + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "SAP \nsystems", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "type": "text", + "version": 361, + "versionNonce": 2064190877, + "index": "b5W", + "isDeleted": false, + "id": "Zc1-RCvSuvQXIuGN2XX__", + "fillStyle": "hachure", + "strokeWidth": 4, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 2961.0192217145595, + "y": -1402.2136383895609, + "strokeColor": "#1e1e1e", + "backgroundColor": "#b2f2bb", + "width": 543.8650424238555, + "height": 181.11325735579965, + "seed": 1881255043, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1732103767803, + "link": null, + "locked": false, + "fontSize": 36.22265147115994, + "fontFamily": 1, + "text": "Composite Roles\nUser assignment API\nSystem-wide provisioning\n(System attributes: long term)", + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "Composite Roles\nUser assignment API\nSystem-wide provisioning\n(System attributes: long term)", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "type": "text", + "version": 24, + "versionNonce": 529633485, + "index": "b5d", + "isDeleted": false, + "id": "dKhQF7AoIEocZN-NTG6vj", + "fillStyle": "hachure", + "strokeWidth": 4, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 2067.352555047894, + "y": 1716.4829522995724, + "strokeColor": "#1e1e1e", + "backgroundColor": "#b2f2bb", + "width": 67.88398742675781, + "height": 118.01811613639542, + "seed": 1259986285, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1722592260951, + "link": null, + "locked": false, + "fontSize": 94.4144929091163, + "fontFamily": 1, + "text": "&", + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "&", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "type": "rectangle", + "version": 415, + "versionNonce": 691788498, + "index": "b5e", + "isDeleted": false, + "id": "tCJgRs6B1l9JlF7I6k5W-", + "fillStyle": "hachure", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1535.8763645717029, + "y": -257.9932381766181, + "strokeColor": "#1e1e1e", + "backgroundColor": "#d0bfff", + "width": 298.92857142857105, + "height": 165.714285714286, + "seed": 1980718538, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "boundElements": [ + { + "type": "text", + "id": "U6SqPa33aRA1htWjVS_ck" + } + ], + "updated": 1732176004224, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 379, + "versionNonce": 1985786002, + "index": "b5f", + "isDeleted": false, + "id": "U6SqPa33aRA1htWjVS_ck", + "fillStyle": "hachure", + "strokeWidth": 4, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 1568.4286858694845, + "y": -197.6360953194751, + "strokeColor": "#1e1e1e", + "backgroundColor": "#ffec99", + "width": 233.8239288330078, + "height": 45, + "seed": 16915661, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1732176004224, + "link": null, + "locked": false, + "fontSize": 36, + "fontFamily": 1, + "text": "Domain (ACL)", + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "tCJgRs6B1l9JlF7I6k5W-", + "originalText": "Domain (ACL)", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "type": "text", + "version": 150, + "versionNonce": 1060578013, + "index": "b5i", + "isDeleted": false, + "id": "fISdlV371nCIaPjWfFyei", + "fillStyle": "hachure", + "strokeWidth": 4, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 1923.0668407621793, + "y": -1093.8027619861418, + "strokeColor": "#1e1e1e", + "backgroundColor": "#ffec99", + "width": 795.4598388671875, + "height": 140, + "seed": 1227173078, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1732103696144, + "link": null, + "locked": false, + "fontSize": 28, + "fontFamily": 1, + "text": "POLICY QualityExpert {\n ASSIGN ROLE FindingsReviewer WHERE \n SystemId IS NOT RESTRICTED;\n}", + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "POLICY QualityExpert {\n ASSIGN ROLE FindingsReviewer WHERE \n SystemId IS NOT RESTRICTED;\n}", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "type": "text", + "version": 18, + "versionNonce": 1600079581, + "index": "b5j", + "isDeleted": false, + "id": "CN2Kkxzs216Y4YrbJnX-Q", + "fillStyle": "hachure", + "strokeWidth": 4, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 1939.4954121907504, + "y": -842.6122857956656, + "strokeColor": "#1e1e1e", + "backgroundColor": "#ffec99", + "width": 288.3998718261719, + "height": 35, + "seed": 1708931274, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1732103468301, + "link": null, + "locked": false, + "fontSize": 28, + "fontFamily": 1, + "text": "System, SystemType ", + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "System, SystemType ", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "type": "text", + "version": 54, + "versionNonce": 1010384915, + "index": "b5k", + "isDeleted": false, + "id": "bLSDmt_WWGqV4NPu09Brv", + "fillStyle": "hachure", + "strokeWidth": 4, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 1908.0668407621793, + "y": -636.4218096051894, + "strokeColor": "#1e1e1e", + "backgroundColor": "#ffec99", + "width": 456.09173583984375, + "height": 35, + "seed": 1324310474, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1732103637351, + "link": null, + "locked": false, + "fontSize": 28, + "fontFamily": 1, + "text": "FindingsReporter, FindingsReviewer", + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "FindingsReporter, FindingsReviewer", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "type": "text", + "version": 171, + "versionNonce": 1860508691, + "index": "b5l", + "isDeleted": false, + "id": "ZB1V0rCo4tvAa_UWTTy28", + "fillStyle": "hachure", + "strokeWidth": 4, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 2354.8726708548365, + "y": -838.9218096051894, + "strokeColor": "#1e1e1e", + "backgroundColor": "#ffec99", + "width": 427.01177978515625, + "height": 35, + "seed": 714424522, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1732103722698, + "link": null, + "locked": false, + "fontSize": 28, + "fontFamily": 1, + "text": "QualityEngineer, SecurityExpert", + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "QualityEngineer, SecurityExpert", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "type": "line", + "version": 397, + "versionNonce": 2079859219, + "index": "b5m", + "isDeleted": false, + "id": "yPoI7JKxhXH0DS--UVPJF", + "fillStyle": "hachure", + "strokeWidth": 2, + "strokeStyle": "dotted", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2850.191456126923, + "y": -1465.950801243709, + "strokeColor": "#1e1e1e", + "backgroundColor": "#b2f2bb", + "width": 4.66666666666697, + "height": 1411.3333333333333, + "seed": 1628533939, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1732089859061, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + -4.66666666666697, + 1411.3333333333333 + ] + ] + }, + { + "id": "UL0n--n5oF6DSY_dwJAny", + "type": "text", + "x": 647.9429011269215, + "y": -956.3492063492055, + "width": 122.07879412615735, + "height": 73.33333333333329, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "dashed", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "b5o", + "roundness": null, + "seed": 19423709, + "version": 122, + "versionNonce": 837080541, + "isDeleted": false, + "boundElements": [], + "updated": 1732089960488, + "link": null, + "locked": false, + "text": "AMS", + "fontSize": 58.66666666666663, + "fontFamily": 5, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "AMS", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "id": "CR7FoQtEzJLNh_9E5kIC0", + "type": "text", + "x": 663.5701707305097, + "y": -466.3492063492057, + "width": 116.79150390625, + "height": 73.33333333333329, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "dashed", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "b5p", + "roundness": null, + "seed": 1621307165, + "version": 185, + "versionNonce": 479700477, + "isDeleted": false, + "boundElements": [], + "updated": 1732089966075, + "link": null, + "locked": false, + "text": "CAP", + "fontSize": 58.66666666666663, + "fontFamily": 5, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "CAP", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "id": "x65TD1pgRju68SBmMoGWQ", + "type": "text", + "x": 2969.6095677935887, + "y": -548.0158730158723, + "width": 664.1820987654322, + "height": 371.6666666666666, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "dashed", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "b5q", + "roundness": null, + "seed": 745526301, + "version": 332, + "versionNonce": 463964957, + "isDeleted": false, + "boundElements": [], + "updated": 1732103760350, + "link": null, + "locked": false, + "text": "Domain access rules (grants):\nStatic Roles\nStatic Filters\nDynamic Rules (ACL like)\n\nEnforcement\n\nDeveloper / CDS Modeler", + "fontSize": 37.16666666666666, + "fontFamily": 5, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "Domain access rules (grants):\nStatic Roles\nStatic Filters\nDynamic Rules (ACL like)\n\nEnforcement\n\nDeveloper / CDS Modeler", + "autoResize": false, + "lineHeight": 1.25 + }, + { + "type": "rectangle", + "version": 843, + "versionNonce": 1364229411, + "index": "b5r", + "isDeleted": false, + "id": "BD6CHYOkOevwx_Cy04b6O", + "fillStyle": "hachure", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 6.281244875266019, + "x": 4817.816910119738, + "y": -871.7385395684298, + "strokeColor": "#1e1e1e", + "backgroundColor": "#a5d8ff", + "width": 416.3333333333336, + "height": 397.0602433325521, + "seed": 183615182, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "boundElements": [ + { + "id": "sq5YN9RTsWz1P95lS0Z9M", + "type": "arrow" + }, + { + "id": "QFPpiNkYlijtPfwUcYnbQ", + "type": "arrow" + }, + { + "id": "RJERC8cyigxXvhDzlN8xf", + "type": "arrow" + }, + { + "id": "XW93cMwR4fGB9Q4FktWXx", + "type": "arrow" + } + ], + "updated": 1734337744576, + "link": null, + "locked": false + }, + { + "type": "rectangle", + "version": 604, + "versionNonce": 1673676205, + "index": "b5s", + "isDeleted": false, + "id": "i2FPTD7QqnnA74TYwJaRq", + "fillStyle": "hachure", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 4823.895282079304, + "y": -337.95634920634836, + "strokeColor": "#1e1e1e", + "backgroundColor": "#a5d8ff", + "width": 403.09523809523796, + "height": 150.714285714286, + "seed": 1425493518, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "boundElements": [ + { + "type": "text", + "id": "LJ3GRsl1SuBVWlqGmyVZB" + }, + { + "id": "sq5YN9RTsWz1P95lS0Z9M", + "type": "arrow" + } + ], + "updated": 1734338110789, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 569, + "versionNonce": 2015823885, + "index": "b5t", + "isDeleted": false, + "id": "LJ3GRsl1SuBVWlqGmyVZB", + "fillStyle": "hachure", + "strokeWidth": 4, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 4998.316909305634, + "y": -285.09920634920536, + "strokeColor": "#1e1e1e", + "backgroundColor": "#ffec99", + "width": 54.251983642578125, + "height": 45, + "seed": 759061582, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1734338110789, + "link": null, + "locked": false, + "fontSize": 36, + "fontFamily": 1, + "text": "DB", + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "i2FPTD7QqnnA74TYwJaRq", + "originalText": "DB", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "type": "rectangle", + "version": 562, + "versionNonce": 1205919363, + "index": "b5u", + "isDeleted": false, + "id": "cBpIbI-Q6qGf1PpMlsTcQ", + "fillStyle": "hachure", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 5508.442901126923, + "y": -746.5992063492051, + "strokeColor": "#1e1e1e", + "backgroundColor": "#b2f2bb", + "width": 338.9999999999999, + "height": 116.33333333333348, + "seed": 487631118, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "boundElements": [ + { + "type": "text", + "id": "623Cr6q6Z_ACc6h7M-C9O" + } + ], + "updated": 1734337837151, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 387, + "versionNonce": 635967011, + "index": "b5v", + "isDeleted": false, + "id": "623Cr6q6Z_ACc6h7M-C9O", + "fillStyle": "hachure", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 5588.064925663056, + "y": -710.9325396825384, + "strokeColor": "#1e1e1e", + "backgroundColor": "#a5d8ff", + "width": 179.75595092773438, + "height": 45, + "seed": 2138330958, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1734337837151, + "link": null, + "locked": false, + "fontSize": 36, + "fontFamily": 1, + "text": "IAS Plugin", + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "cBpIbI-Q6qGf1PpMlsTcQ", + "originalText": "IAS Plugin", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "type": "rectangle", + "version": 574, + "versionNonce": 124632835, + "index": "b5w", + "isDeleted": false, + "id": "uVY-CzwiwiyTJLpHGvTFq", + "fillStyle": "hachure", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 5506.359567793591, + "y": -615.7658730158719, + "strokeColor": "#1e1e1e", + "backgroundColor": "#b2f2bb", + "width": 338.9999999999999, + "height": 116.33333333333348, + "seed": 1419231630, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "boundElements": [ + { + "type": "text", + "id": "uCyOdszgWZ-15HvjI375c" + }, + { + "id": "RJERC8cyigxXvhDzlN8xf", + "type": "arrow" + }, + { + "id": "kJagA2Vs7UWBb4PfOrVSP", + "type": "arrow" + } + ], + "updated": 1734337872951, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 402, + "versionNonce": 234871267, + "index": "b5x", + "isDeleted": false, + "id": "uCyOdszgWZ-15HvjI375c", + "fillStyle": "hachure", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 5582.003595503552, + "y": -580.0992063492051, + "strokeColor": "#1e1e1e", + "backgroundColor": "#a5d8ff", + "width": 187.71194458007812, + "height": 45, + "seed": 1468293070, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1734337872951, + "link": null, + "locked": false, + "fontSize": 36, + "fontFamily": 1, + "text": "AMS Plugin", + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "uVY-CzwiwiyTJLpHGvTFq", + "originalText": "AMS Plugin", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "id": "O_RRLbOLLelPRlqMX8FQT", + "type": "rectangle", + "x": 5494.192901126923, + "y": -338.0158730158719, + "width": 361.6666666666671, + "height": 151.66666666666697, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "b5y", + "roundness": { + "type": 3 + }, + "seed": 2070259602, + "version": 257, + "versionNonce": 262518787, + "isDeleted": false, + "boundElements": [ + { + "type": "text", + "id": "ar3SPP0ccuwUncR2hNpvs" + }, + { + "id": "kJagA2Vs7UWBb4PfOrVSP", + "type": "arrow" + } + ], + "updated": 1734337978454, + "link": null, + "locked": false + }, + { + "id": "ar3SPP0ccuwUncR2hNpvs", + "type": "text", + "x": 5580.2022522214875, + "y": -284.6825396825384, + "width": 189.64796447753906, + "height": 45, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "dashed", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "b5z", + "roundness": null, + "seed": 1687872210, + "version": 215, + "versionNonce": 1565180675, + "isDeleted": false, + "boundElements": [], + "updated": 1734337890105, + "link": null, + "locked": false, + "text": "IAS / AMS", + "fontSize": 36, + "fontFamily": 5, + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "O_RRLbOLLelPRlqMX8FQT", + "originalText": "IAS / AMS", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "id": "dTXM1_i6ESoW9qEbUOK0t", + "type": "arrow", + "x": 5020.026234460256, + "y": -1009.2658730158719, + "width": 1.25, + "height": 136.25, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "dashed", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "b60", + "roundness": { + "type": 2 + }, + "seed": 1938040398, + "version": 157, + "versionNonce": 442404227, + "isDeleted": false, + "boundElements": [], + "updated": 1734337754651, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + -1.25, + 136.25 + ] + ], + "lastCommittedPoint": null, + "startBinding": null, + "endBinding": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "elbowed": false + }, + { + "id": "4n7OnXDaqajX_iAd3txlq", + "type": "text", + "x": 5046.692901126922, + "y": -968.8492063492051, + "width": 79.77998352050781, + "height": 45, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "dashed", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "b61", + "roundness": null, + "seed": 1892552206, + "version": 105, + "versionNonce": 1998076579, + "isDeleted": false, + "boundElements": [], + "updated": 1734338073477, + "link": null, + "locked": false, + "text": "JWT", + "fontSize": 36, + "fontFamily": 5, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "JWT", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "id": "sq5YN9RTsWz1P95lS0Z9M", + "type": "arrow", + "x": 5027.771928970494, + "y": -473.68139077230416, + "width": 0.7529780339364152, + "height": 127.67722565466153, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "dashed", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "b62", + "roundness": { + "type": 2 + }, + "seed": 1223464206, + "version": 301, + "versionNonce": 2072280685, + "isDeleted": false, + "boundElements": [], + "updated": 1734338110789, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 0.7529780339364152, + 127.67722565466153 + ] + ], + "lastCommittedPoint": null, + "startBinding": { + "elementId": "BD6CHYOkOevwx_Cy04b6O", + "focus": -0.007419237541381339, + "gap": 1, + "fixedPoint": null + }, + "endBinding": { + "elementId": "i2FPTD7QqnnA74TYwJaRq", + "focus": 0.017693210517091173, + "gap": 8.047815911294265, + "fixedPoint": null + }, + "startArrowhead": null, + "endArrowhead": "arrow", + "elbowed": false + }, + { + "id": "QFPpiNkYlijtPfwUcYnbQ", + "type": "arrow", + "x": 5500.442901126923, + "y": -686.7709284863648, + "width": 323.7612047282091, + "height": 1.3266619950030645, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "dashed", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "b63", + "roundness": { + "type": 2 + }, + "seed": 1412631122, + "version": 326, + "versionNonce": 1080602147, + "isDeleted": false, + "boundElements": [], + "updated": 1734337827171, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + -323.7612047282091, + -1.3266619950030645 + ] + ], + "lastCommittedPoint": null, + "startBinding": null, + "endBinding": { + "elementId": "v8LThFEYy6lBql6nI1rVQ", + "focus": -0.14583118074854268, + "gap": 13.384084697921026, + "fixedPoint": null + }, + "startArrowhead": null, + "endArrowhead": "arrow", + "elbowed": false + }, + { + "id": "RJERC8cyigxXvhDzlN8xf", + "type": "arrow", + "x": 5505.359567793591, + "y": -564.3989397678943, + "width": 324.60199419677156, + "height": 5.758313914620089, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "dashed", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "b65", + "roundness": { + "type": 2 + }, + "seed": 1200166930, + "version": 320, + "versionNonce": 302311075, + "isDeleted": false, + "boundElements": [], + "updated": 1734337872951, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + -324.60199419677156, + -5.758313914620089 + ] + ], + "lastCommittedPoint": null, + "startBinding": { + "elementId": "uVY-CzwiwiyTJLpHGvTFq", + "focus": 0.06171180979360312, + "gap": 1, + "fixedPoint": null + }, + "endBinding": { + "elementId": "jDK4dSGNezMikjE3qn8ri", + "focus": -0.1521713873312393, + "gap": 1.5059366274717831, + "fixedPoint": null + }, + "startArrowhead": null, + "endArrowhead": "arrow", + "elbowed": false + }, + { + "id": "XJy29-Y7ArzEfJg4lajRe", + "type": "text", + "x": 5283.776234460256, + "y": -617.1825396825386, + "width": 190.91993713378906, + "height": 45, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "dashed", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "b66", + "roundness": null, + "seed": 1247242834, + "version": 43, + "versionNonce": 2072067331, + "isDeleted": false, + "boundElements": [], + "updated": 1734337859228, + "link": null, + "locked": false, + "text": "User Roles", + "fontSize": 36, + "fontFamily": 5, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "User Roles", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "id": "HDuUhjuKkuDJtKlz16nvg", + "type": "text", + "x": 5277.10956779359, + "y": -553.4325396825386, + "width": 210.1439208984375, + "height": 45, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "dashed", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "b67", + "roundness": null, + "seed": 760794450, + "version": 65, + "versionNonce": 932976611, + "isDeleted": false, + "boundElements": [ + { + "id": "RJERC8cyigxXvhDzlN8xf", + "type": "arrow" + } + ], + "updated": 1734337861157, + "link": null, + "locked": false, + "text": "User Filters", + "fontSize": 36, + "fontFamily": 5, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "User Filters", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "id": "kJagA2Vs7UWBb4PfOrVSP", + "type": "arrow", + "x": 5670.701610637358, + "y": -498.4325396825384, + "width": 1.4203595798344395, + "height": 159.41666666666652, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "dashed", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "b68", + "roundness": { + "type": 2 + }, + "seed": 1253115150, + "version": 309, + "versionNonce": 765553315, + "isDeleted": false, + "boundElements": [], + "updated": 1734337890105, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 1.4203595798344395, + 159.41666666666652 + ] + ], + "lastCommittedPoint": null, + "startBinding": { + "elementId": "uVY-CzwiwiyTJLpHGvTFq", + "focus": 0.0077922973653715135, + "gap": 1, + "fixedPoint": null + }, + "endBinding": { + "elementId": "O_RRLbOLLelPRlqMX8FQT", + "focus": -0.012229146085728775, + "gap": 1, + "fixedPoint": null + }, + "startArrowhead": null, + "endArrowhead": "arrow", + "elbowed": false + }, + { + "id": "MCu2KWfHsaBdeAv95kZ5O", + "type": "text", + "x": 5061.276234460256, + "y": -443.0158730158719, + "width": 264.2159118652344, + "height": 45, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "dashed", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "b6D", + "roundness": null, + "seed": 1786284494, + "version": 23, + "versionNonce": 1593026706, + "isDeleted": false, + "boundElements": [], + "updated": 1732177147958, + "link": null, + "locked": false, + "text": "Query w filters", + "fontSize": 36, + "fontFamily": 5, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "Query w filters", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "id": "jDK4dSGNezMikjE3qn8ri", + "type": "rectangle", + "x": 4884.251636969348, + "y": -616.5753968253966, + "width": 295, + "height": 101.25, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "#a5d8ff", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "b6E", + "roundness": { + "type": 3 + }, + "seed": 92003149, + "version": 256, + "versionNonce": 1215592227, + "isDeleted": false, + "boundElements": [ + { + "id": "RJERC8cyigxXvhDzlN8xf", + "type": "arrow" + } + ], + "updated": 1734338014743, + "link": null, + "locked": false + }, + { + "id": "xAsLeGwH_dXXLy1RCLhYZ", + "type": "text", + "x": 4911.751636969348, + "y": -587.8253968253966, + "width": 240.93991088867188, + "height": 45, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "#a5d8ff", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "b6F", + "roundness": null, + "seed": 404327555, + "version": 190, + "versionNonce": 1155415747, + "isDeleted": false, + "boundElements": null, + "updated": 1734337721370, + "link": null, + "locked": false, + "text": "Authorization", + "fontSize": 36, + "fontFamily": 5, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "Authorization", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "id": "BlTQ5GF_PAuM0FH2Ur8-q", + "type": "text", + "x": 4880.501636969348, + "y": -841.5753968253966, + "width": 290.919921875, + "height": 95.76037030951987, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "#a5d8ff", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "b6G", + "roundness": null, + "seed": 1503953965, + "version": 139, + "versionNonce": 703284109, + "isDeleted": false, + "boundElements": null, + "updated": 1734337749162, + "link": null, + "locked": false, + "text": "CAP Application\n ", + "fontSize": 38.30414812380795, + "fontFamily": 5, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "CAP Application\n ", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "id": "rYSvlT2rx-5ctDLaxq5w0", + "type": "rectangle", + "x": 4878.001636969348, + "y": -734.7003968253966, + "width": 295, + "height": 95.00000000000003, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "#a5d8ff", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "b6H", + "roundness": { + "type": 3 + }, + "seed": 800196803, + "version": 312, + "versionNonce": 363808109, + "isDeleted": false, + "boundElements": [], + "updated": 1734338014030, + "link": null, + "locked": false + }, + { + "id": "v8LThFEYy6lBql6nI1rVQ", + "type": "text", + "x": 4903.781681525012, + "y": -707.8253968253966, + "width": 259.51593017578125, + "height": 45, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "#a5d8ff", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "b6I", + "roundness": null, + "seed": 439986691, + "version": 243, + "versionNonce": 2113325357, + "isDeleted": false, + "boundElements": [ + { + "id": "QFPpiNkYlijtPfwUcYnbQ", + "type": "arrow" + } + ], + "updated": 1734337817714, + "link": null, + "locked": false, + "text": "Authentication", + "fontSize": 36, + "fontFamily": 5, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "Authentication", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "id": "oTv_-p5-pjuDvhOQQkjMF", + "type": "text", + "x": 5261.1402334087825, + "y": -776.5278504001826, + "width": 231.22280712112965, + "height": 87.40490714957203, + "angle": 0.007613297783697348, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "dashed", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "b6L", + "roundness": null, + "seed": 1183558019, + "version": 318, + "versionNonce": 1168279373, + "isDeleted": false, + "boundElements": [], + "updated": 1734338088263, + "link": null, + "locked": false, + "text": "\nUser Context", + "fontSize": 34.96196285982883, + "fontFamily": 5, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "\nUser Context", + "autoResize": true, + "lineHeight": 1.25 + } + ], + "appState": { + "gridSize": 20, + "gridStep": 5, + "gridModeEnabled": false, + "viewBackgroundColor": "#ffffff" + }, + "files": {} +} \ No newline at end of file diff --git a/guides/security/assets/ams.png b/guides/security/assets/ams.png new file mode 100644 index 0000000000..cdc487d499 Binary files /dev/null and b/guides/security/assets/ams.png differ diff --git a/guides/security/assets/authentication.drawio.svg b/guides/security/assets/authentication.drawio.svg new file mode 100644 index 0000000000..b3348f9768 --- /dev/null +++ b/guides/security/assets/authentication.drawio.svg @@ -0,0 +1,138 @@ + + + + + + + + + + + + + + + + + +
+
+
+ + + Authentication + + +
+
+
+
+ + Authentication + +
+
+
+ + + + + + + +
+
+
+ + + Cred +
+ ential +
+
+
+
+
+
+ + Cred... + +
+
+
+ + + + + + + + + +
+
+
+ + Authorization + +
+
+
+
+ + Authorization + +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + +
+
+
+ + + CAP User + + +
+
+
+
+ + CAP User + +
+
+
+
+ + + + + Text is not SVG - cannot display + + + +
\ No newline at end of file diff --git a/guides/security/assets/authorization.drawio.svg b/guides/security/assets/authorization.drawio.svg new file mode 100644 index 0000000000..c6bdb5e9b1 --- /dev/null +++ b/guides/security/assets/authorization.drawio.svg @@ -0,0 +1,99 @@ + + + + + + + + + + + + +
+
+
+ + + Authorization + + +
+
+
+
+ + Authorization + +
+
+
+ + + + + + + + + +
+
+
+ + Authentication + +
+
+
+
+ + Authentication + +
+
+
+ + + + + + + + + + + + + + + + +
+
+
+ + + CAP User + + +
+
+
+
+ + CAP User + +
+
+
+
+ + + + + Text is not SVG - cannot display + + + +
\ No newline at end of file diff --git a/guides/security/assets/cap-users.drawio.svg b/guides/security/assets/cap-users.drawio.svg new file mode 100644 index 0000000000..96ae7e418f --- /dev/null +++ b/guides/security/assets/cap-users.drawio.svg @@ -0,0 +1,181 @@ + + + + + + + + + + + + +
+
+
+ + Authorization + +
+
+
+
+ + Authorization + +
+
+
+ + + + + + + + + +
+
+
+ + Authentication + +
+
+
+
+ + Authentication + +
+
+
+ + + + + + + + + +
+
+
+ + + Remote Authentication + + +
+
+
+
+ + Remote Authentication + +
+
+
+ + + + + + + + + + + + + + + + + + + + +
+
+
+ + + CAP User + + +
+
+
+
+ + CAP User + +
+
+
+ + + + + + + +
+
+
+ + <<propagate>> + +
+
+
+
+ + <<propagat... + +
+
+
+ + + + + + + + + + + + +
+
+
+ + + CAP User + + +
+
+
+
+ + CAP User + +
+
+
+
+ + + + + Text is not SVG - cannot display + + + +
\ No newline at end of file diff --git a/guides/security/assets/co-located-services.drawio.svg b/guides/security/assets/co-located-services.drawio.svg new file mode 100644 index 0000000000..7a56f5e599 --- /dev/null +++ b/guides/security/assets/co-located-services.drawio.svg @@ -0,0 +1,242 @@ + + + + + + + + + + + + + + + + + + +
+
+
+ + Service A + +
+
+
+
+ + Service A + +
+
+
+ + + + + + + + + +
+
+
+ + Token + +
+
+
+
+ + Token + +
+
+
+ + + + + + + + + + + + + + + + +
+
+
+ + Service B + +
+
+
+
+ + Service B + +
+
+
+ + + + + + + + + + + + + +
+
+
+ + Token + +
+
+
+
+ + Token + +
+
+
+ + + + + + + + + +
+
+
+ + service instance +
+ + identity + +
+
+
+
+
+ + service instance... + +
+
+
+ + + + + + + + + + + + + + + +
+
+
+ + + landscape X + + +
+
+
+
+ + landscap... + +
+
+
+ + + + + + + +
+
+
+ + + <<binds>> + + +
+
+
+
+ + <<binds>> + +
+
+
+ + + + + + + +
+
+
+ + + <<binds>> + + +
+
+
+
+ + <<binds>> + +
+
+
+
+ + + + + Text is not SVG - cannot display + + + +
\ No newline at end of file diff --git a/guides/security/assets/create-role-collection.png b/guides/security/assets/create-role-collection.png new file mode 100644 index 0000000000..ad3cd4bb7c Binary files /dev/null and b/guides/security/assets/create-role-collection.png differ diff --git a/guides/security/assets/custom-auth.drawio.svg b/guides/security/assets/custom-auth.drawio.svg new file mode 100644 index 0000000000..1622d083c8 --- /dev/null +++ b/guides/security/assets/custom-auth.drawio.svg @@ -0,0 +1,237 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+ + CAP identity +
+ integration +
+
+
+
+
+ + CAP iden... + +
+
+
+ + + + + + + +
+
+
+ + CAP +
+ endpoints +
+
+
+
+
+ + CAP... + +
+
+
+ + + + + + + +
+
+
+ + + Custom + +
+ + endpoints +
+ (same auth) +
+
+
+
+
+
+ + Custom... + +
+
+
+ + + + + + + +
+
+
+ + Security Middleware + +
+
+
+
+ + Security... + +
+
+
+ + + + + + + +
+
+
+ + + Custom + +
+ + endpoints + +
+
+ + + (diff auth) + + +
+
+
+
+
+
+
+
+ + Custom... + +
+
+
+ + + + + + + + + + + + + + + + + +
+
+
+ + Framework + +
+
+
+
+ + Framework + +
+
+
+ + + + + + + +
+
+
+ + Application + +
+
+
+
+ + Applicat... + +
+
+
+
+ + + + + Text is not SVG - cannot display + + + +
\ No newline at end of file diff --git a/guides/security/assets/external-services.drawio.svg b/guides/security/assets/external-services.drawio.svg new file mode 100644 index 0000000000..5ff2f15409 --- /dev/null +++ b/guides/security/assets/external-services.drawio.svg @@ -0,0 +1,324 @@ + + + + + + + + + + + + + + + + + + + + + +
+
+
+ + Service A + +
+
+
+
+ + Service A + +
+
+
+ + + + + + + + + +
+
+
+ + Token + +
+
+
+
+ + Token + +
+
+
+ + + + + + + + + + + + + + + + +
+
+
+ + Service B + +
+
+
+
+ + Service B + +
+
+
+ + + + + + + + + + + + + +
+
+
+ + Token + +
+
+
+
+ + Token + +
+
+
+ + + + + + + + + +
+
+
+ + service instance +
+ identity A +
+
+
+
+
+ + service instance... + +
+
+
+ + + + + + + + + + + + + +
+
+
+ + service instance +
+ identity B +
+
+
+
+
+ + service instance... + +
+
+
+ + + + + + + +
+
+
+ + + landscape X + + +
+
+
+
+ + landscap... + +
+
+
+ + + + + + + +
+
+
+ + + landscape Y + + +
+
+
+
+ + landscap... + +
+
+
+ + + + + + + +
+
+
+ + + <<binds>> + + +
+
+
+
+ + <<binds>> + +
+
+
+ + + + + + + +
+
+
+ + + <<dependency>> + + +
+
+
+
+ + <<depend... + +
+
+
+ + + + + + + + + + + + + + + +
+
+
+ + + <<binds>> + + +
+
+
+
+ + <<binds>> + +
+
+
+
+ + + + + Text is not SVG - cannot display + + + +
\ No newline at end of file diff --git a/guides/security/assets/ias-cli-setup.drawio.svg b/guides/security/assets/ias-cli-setup.drawio.svg new file mode 100644 index 0000000000..9ffb9090b4 --- /dev/null +++ b/guides/security/assets/ias-cli-setup.drawio.svg @@ -0,0 +1,389 @@ + + + + + + + + + + + + + +
+
+
+ IAS +
+
+
+
+ + IAS + +
+
+
+ + + + + + + +
+
+
+ CAP Application +
+
+
+
+ + CAP Application + +
+
+
+ + + + + + + +
+
+
+ IAS +
+ service instance +
+
+
+
+ + IAS... + +
+
+
+ + + + + + + +
+
+
+ IAS +
+ service binding +
+
+
+
+ + IAS... + +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+ Application Deployment +
+
+
+
+ + Application Deployment + +
+
+
+ + + + + + + +
+
+
+ + + mTLS / +
+ OAuth 2 +
+
+
+
+
+
+ + mTLS /... + +
+
+
+ + + + R + + + + + + + + R + + + + + + + + + + + +
+
+
+ + + mTLS + + +
+
+
+
+ + mTLS + +
+
+
+ + + + + + + +
+
+
+ IAS +
+ service key +
+
+
+
+ + IAS... + +
+
+
+ + + + + + + + + + + +
+
+
+ Local CLI +
+
+
+
+ + Local CLI + +
+
+
+ + + + + + + + + + R + + + + + + + + + + + +
+
+
+ + + mTLS + + +
+
+
+
+ + mTLS + +
+
+
+ + + + + + + + + + + +
+
+
+ + + + 1 + + + +
+
+
+
+ + 1 + +
+
+
+ + + + + + + +
+
+
+ + + + 2 + + + +
+
+
+
+ + 2 + +
+
+
+ + + + + + + +
+
+
+ + + + 3 + + + +
+
+
+
+ + 3 + +
+
+
+
+ + + + + Text is not SVG - cannot display + + + +
\ No newline at end of file diff --git a/guides/security/assets/ias-cli-setup.svg b/guides/security/assets/ias-cli-setup.svg new file mode 100644 index 0000000000..1253e6c433 --- /dev/null +++ b/guides/security/assets/ias-cli-setup.svg @@ -0,0 +1 @@ +
IAS
IAS
CAP Application
CAP Application
IAS
service instance
IAS...
CLI client
CLI client
IAS
service binding
IAS...
service key
service key
Application Deployment
Application Deployment
mTLS /
OAuth 2
mTLS /...
RR
mTLS
mTLS
Text is not SVG - cannot display
\ No newline at end of file diff --git a/guides/security/assets/ias-dependencies.png b/guides/security/assets/ias-dependencies.png new file mode 100644 index 0000000000..4afe5d029a Binary files /dev/null and b/guides/security/assets/ias-dependencies.png differ diff --git a/guides/security/assets/ias-ui-setup.drawio b/guides/security/assets/ias-ui-setup.drawio new file mode 100644 index 0000000000..8b6d4b0ad3 --- /dev/null +++ b/guides/security/assets/ias-ui-setup.drawio @@ -0,0 +1,142 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/guides/security/assets/ias-ui-setup.svg b/guides/security/assets/ias-ui-setup.svg new file mode 100644 index 0000000000..5941891df4 --- /dev/null +++ b/guides/security/assets/ias-ui-setup.svg @@ -0,0 +1 @@ +
IAS
IAS
CAP Application
CAP Application
IAS
service instance
IAS...
App Router
App Router
IAS
service binding
IAS...
Application Deployment
Application Deployment
mTLS /
OAuth 2
mTLS /...
RR
mTLS
mTLS
IAS
service binding
IAS...
Browser
Browser
R
mTLS
mTLS
Text is not SVG - cannot display
\ No newline at end of file diff --git a/guides/security/assets/ingress-auth.drawio.svg b/guides/security/assets/ingress-auth.drawio.svg new file mode 100644 index 0000000000..ab7a534b2b --- /dev/null +++ b/guides/security/assets/ingress-auth.drawio.svg @@ -0,0 +1,174 @@ + + + + + + + + + + + + + + + +
+
+
+ + Ingress Gateway +
+ (authentication) +
+
+
+
+
+ + Ingress... + +
+
+
+ + + + + + + + + + + + +
+
+
+ + CAP srv1 + +
+
+
+
+
+
+
+ + CAP srv1 + +
+
+
+ + + + + + + + + + + + +
+
+
+ + CAP srv2 + +
+
+
+
+
+
+
+ + CAP srv2 + +
+
+
+ + + + + + + + + + + + +
+
+
+ + CAP srv3 + +
+
+
+
+
+
+
+ + CAP srv3 + +
+
+
+ + + + + + + + + + + + + + + + + + + +
+
+
+ + mTLS tunnel + +
+
+
+
+ + mTLS tun... + +
+
+
+ + + +
+ + + + + Text is not SVG - cannot display + + + +
\ No newline at end of file diff --git a/guides/security/assets/nameduser-node.drawio.svg b/guides/security/assets/nameduser-node.drawio.svg new file mode 100644 index 0000000000..0499f5dc62 --- /dev/null +++ b/guides/security/assets/nameduser-node.drawio.svg @@ -0,0 +1 @@ +
OData Adapter
OData Adapter
Custom ON Handler
Custom ON Handler
Custom AFTER Handler
Custom AFTER Handler
Technical Service
Technical Service
tenant1
tenant1
John Doe
John Doe
Technical User
Technical...
User: John Doe
Tenant: tenant1
User: John Doe...
JWT token
JWT token
Text is not SVG - cannot display
\ No newline at end of file diff --git a/guides/security/assets/nameduser.drawio.svg b/guides/security/assets/nameduser.drawio.svg new file mode 100644 index 0000000000..f802c3142d --- /dev/null +++ b/guides/security/assets/nameduser.drawio.svg @@ -0,0 +1,197 @@ + + + + + + + + + + +
+
+
+ OData Adapter +
+
+
+
+ + OData Adapter + +
+
+ + + + + + +
+
+
+ Custom ON Handler +
+
+
+
+ + Custom ON Handler + +
+
+ + + + + + + +
+
+
+ Custom AFTER Handler +
+
+
+
+ + Custom AFTER Handler + +
+
+ + + + + +
+
+
+ systemUser() +
+
+
+
+ + systemUser() + +
+
+ + + + +
+
+
+ Technical Service +
+
+
+
+ + Technical Service + +
+
+ + + + + +
+
+
+ tenant1 +
+
+
+
+ + tenant1 + +
+
+ + + + + +
+
+
+ John Doe +
+
+
+
+ + John Doe + +
+
+ + + + + +
+
+
+ Technical User +
+
+
+
+ + Technical... + +
+
+ + + + +
+
+
+ User: John Doe +
+ Tenant: tenant1 +
+
+
+
+ + User: John Doe... + +
+
+ + + + +
+
+
+ JWT token +
+
+
+
+ + JWT token + +
+
+
+ + + + + Text is not SVG - cannot display + + + +
\ No newline at end of file diff --git a/guides/security/assets/remote-service-stack.drawio.svg b/guides/security/assets/remote-service-stack.drawio.svg new file mode 100644 index 0000000000..460c28f8e1 --- /dev/null +++ b/guides/security/assets/remote-service-stack.drawio.svg @@ -0,0 +1,411 @@ + + + + + + + + + + + + + + + +
+
+
+ + Connectivity + +
+
+
+
+ + Connecti... + +
+
+
+ + + + + + + + + + + + +
+
+
+ + Remote Service + +
+
+
+
+ + Remote S... + +
+
+
+ + + + + + + + + + + + +
+
+
+ + + HTTP + + +
+
+
+
+ + HTTP + +
+
+
+ + + + + + + + + +
+
+
+ + CAP User + +
+
+
+
+ + CAP User + +
+
+
+ + + + + + + + + +
+
+
+ + Dest +
+ ination +
+
+
+
+
+ + Dest... + +
+
+
+ + + + + + + + + + + + + + + + +
+
+
+ + Authentica +
+ tion +
+
+
+
+
+ + Authenti... + +
+
+
+ + + + + + + + + + + + +
+
+
+ + User +
+ propagation +
+
+
+
+
+ + User... + +
+
+
+ + + + + + + + + + + + +
+
+
+ + Destination + +
+
+
+
+ + Destinat... + +
+
+
+ + + + + + + + + + +
+
+
+ + Outbound Protocol +
+ Adapter +
+ (OData, hcql, ...) +
+
+
+
+
+ + Outbound... + +
+
+
+ + + + + + + + + + +
+
+
+ + + CQN + + +
+
+
+
+ + CQN + +
+
+
+ + + + + + + + + + + + +
+
+
+ + + Protocol level + + +
+
+
+
+ + Protocol... + +
+
+
+ + + + + + + + + + + + +
+
+
+ + + Connectivity level + + +
+
+
+
+ + Connecti... + +
+
+
+ + + + + + + + + + + +
+
+
+ + Request + +
+
+
+
+ + Request + +
+
+
+ + + + + + + +
+
+
+ + Config + +
+
+
+
+ + Config + +
+
+
+
+ + + + + Text is not SVG - cannot display + + + +
\ No newline at end of file diff --git a/guides/security/assets/requestcontext-node.drawio.svg b/guides/security/assets/requestcontext-node.drawio.svg new file mode 100644 index 0000000000..bb1f291d27 --- /dev/null +++ b/guides/security/assets/requestcontext-node.drawio.svg @@ -0,0 +1 @@ +
Named User
Named User
Named User

or

System User  Subscriber
Named User...
System User
Subscriber
System User...
System User
Provider
System User...
System User
Provider
System User...
System User
Subscriber
System User...
Switching to technical user
Switching to technical user
Switching to provider tenant
Switching to provider tenant
Switching to a specific tenant
Switching to a specific tenant
Text is not SVG - cannot display
\ No newline at end of file diff --git a/guides/security/assets/requestcontext.drawio.svg b/guides/security/assets/requestcontext.drawio.svg new file mode 100644 index 0000000000..d514dff739 --- /dev/null +++ b/guides/security/assets/requestcontext.drawio.svg @@ -0,0 +1,237 @@ + + + + + + + + + + +
+
+
+ Named User +
+
+
+
+ + Named User + +
+
+ + + + +
+
+
+ Named User +
+
+ or +
+
+ System User  Subscriber +
+
+
+
+ + Named User... + +
+
+ + + + +
+
+
+ System User +
+ Subscriber +
+
+
+
+ + System User... + +
+
+ + + + +
+
+
+ System User +
+ Provider +
+
+
+
+ + System User... + +
+
+ + + + +
+
+
+ System User +
+ Provider +
+
+
+
+ + System User... + +
+
+ + + + +
+
+
+ System User +
+ Subscriber +
+
+
+
+ + System User... + +
+
+ + + + +
+
+
+ Switching to technical user +
+
+
+
+ + Switching to technical user + +
+
+ + + + +
+
+
+ Switching to provider tenant +
+
+
+
+ + Switching to provider tenant + +
+
+ + + + +
+
+
+ Switching to a specific tenant +
+
+
+
+ + Switching to a specific tenant + +
+
+ + + + + +
+
+
+ systemUserProvider() +
+
+
+
+ + systemUser... + +
+
+ + + + + +
+
+
+ systemUser() +
+
+
+
+ + systemUser... + +
+
+ + + + + +
+
+
+ systemUser(tenant) +
+
+
+
+ + systemUser... + +
+
+
+ + + + + Text is not SVG - cannot display + + + +
\ No newline at end of file diff --git a/guides/security/assets/reuse-services.drawio.svg b/guides/security/assets/reuse-services.drawio.svg new file mode 100644 index 0000000000..1ca1bcdf20 --- /dev/null +++ b/guides/security/assets/reuse-services.drawio.svg @@ -0,0 +1,305 @@ + + + + + + + + + + + + + + + + + + +
+
+
+ + Service A + +
+
+
+
+ + Service A + +
+
+
+ + + + + + + + + +
+
+
+ + Token + +
+
+
+
+ + Token + +
+
+
+ + + + + + + + + + + + + + + + +
+
+
+ + Service B + +
+
+
+
+ + Service B + +
+
+
+ + + + + + + + + + + + + +
+
+
+ + Token + +
+
+
+
+ + Token + +
+
+
+ + + + + + + + + +
+
+
+ + service instance +
+ identity A +
+
+
+
+
+ + service instance... + +
+
+
+ + + + + + + + + + + + + + + + + +
+
+
+ + service instance +
+ identity B +
+
+
+
+
+ + service instance... + +
+
+
+ + + + + + + +
+
+
+ + + landscape X + + +
+
+
+
+ + landscap... + +
+
+
+ + + + + + + + + +
+
+
+ + service instance + + reuse service B + + +
+
+
+
+ + service instance reuse service B + +
+
+
+ + + + + + + + + + + + + + + +
+
+
+ + + <<binds>> + + +
+
+
+
+ + <<binds>> + +
+
+
+ + + + + + + +
+
+
+ + + <<exposes>> +
+ via broker +
+
+
+
+
+
+ + <<expose... + +
+
+
+
+ + + + + Text is not SVG - cannot display + + + +
\ No newline at end of file diff --git a/guides/security/assets/security-components.drawio.svg b/guides/security/assets/security-components.drawio.svg new file mode 100644 index 0000000000..51f3360a6f --- /dev/null +++ b/guides/security/assets/security-components.drawio.svg @@ -0,0 +1,223 @@ + + + + + + + + + + + + + + + + + +
+
+
+ + + Authorization + + +
+
+
+
+ + Authorization + +
+
+
+ + + + + + + + + +
+
+
+ + + Authentication + + +
+
+
+
+ + Authentication + +
+
+
+ + + + + + + + + +
+
+
+ + + + Remote Authentication + + + +
+
+
+
+ + Remote Authentication + +
+
+
+ + + + + + + + + + + + + + + +
+
+
+ + + CAP User + + +
+
+
+
+ + CAP User + +
+
+
+ + + + + + + + + + + + +
+
+
+ + + CAP User + + +
+
+
+
+ + CAP User + +
+
+
+ + + + + + + +
+
+
+ + <<propagate>> + +
+
+
+
+ + <<propagat... + +
+
+
+ + + + + + + + + + + + + + + + +
+
+
+ + + Cred +
+ ential +
+
+
+
+
+
+ + Cred... + +
+
+
+
+ + + + + Text is not SVG - cannot display + + + +
\ No newline at end of file diff --git a/guides/security/assets/security-customizable.drawio.svg b/guides/security/assets/security-customizable.drawio.svg new file mode 100644 index 0000000000..0b9bc7fad1 --- /dev/null +++ b/guides/security/assets/security-customizable.drawio.svg @@ -0,0 +1,354 @@ + + + + + + + + + + + + +
+
+
+ + CAP User + +
+
+
+
+ + CAP User + +
+
+
+ + + + + + + + + +
+
+
+ + Authorization + +
+
+
+
+ + Authorization + +
+
+
+ + + + + + + + + + + + + + + + + + + +
+
+
+ + + Remote Authentication + + +
+
+
+
+ + Remote Authentication + +
+
+
+ + + + + + + + + + + + + + + +
+
+
+ + + Custom endpoints (auth/protocol) + + +
+
+
+
+ + Custom end... + +
+
+
+ + + + + + + +
+
+
+ + + Custom endpoints (auth) + + +
+
+
+
+ + Custom end... + +
+
+
+ + + + + + + +
+
+
+ + + Modifying CAP Users + + +
+
+
+
+ + Modifying... + +
+
+
+ + + + + + + +
+
+
+ + + Switching Users + + +
+
+
+
+ + Switching... + +
+
+
+ + + + + + + +
+
+
+ + + Defining Restrictions + + +
+
+
+
+ + Defining R... + +
+
+
+ + + + + + + + + + + + +
+
+
+ + customization + +
+
+
+
+ + customization + +
+
+
+ + + + + + + +
+
+
+ + out of the box + +
+
+
+
+ + out of the box + +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+ + CAP User + +
+
+
+
+ + CAP User + +
+
+
+ + + + + + + + + +
+
+
+ + Authentication + +
+
+
+
+ + Authentication + +
+
+
+
+ + + + + Text is not SVG - cannot display + + + +
\ No newline at end of file diff --git a/guides/security/assets/security-overview.drawio b/guides/security/assets/security-overview.drawio new file mode 100644 index 0000000000..78c3a33e0a --- /dev/null +++ b/guides/security/assets/security-overview.drawio @@ -0,0 +1,203 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/guides/security/assets/security-platform-integration.drawio.svg b/guides/security/assets/security-platform-integration.drawio.svg new file mode 100644 index 0000000000..030639417d --- /dev/null +++ b/guides/security/assets/security-platform-integration.drawio.svg @@ -0,0 +1,235 @@ + + + + + + + + + + + + +
+
+
+ + + IAS / XSUAA + + +
+
+
+
+ + IAS / XSUAA + +
+
+
+ + + + + + + + + +
+
+
+ + + AMS / XSUAA + + +
+
+
+
+ + AMS / XSUAA + +
+
+
+ + + + + + + + + +
+
+
+ + + BTP Connectivity + + +
+
+
+
+ + BTP Connectivity + +
+
+
+ + + + + + + + + + + + +
+
+
+ + Platform + +
+
+
+
+ + Platform + +
+
+
+ + + + + + + + + + + + +
+
+
+ + CAP + +
+
+
+
+ + CAP + +
+
+
+ + + + + + + + + +
+
+
+ + Authorization + +
+
+
+
+ + Authorization + +
+
+
+ + + + + + + + + +
+
+
+ + Authentication + +
+
+
+
+ + Authentication + +
+
+
+ + + + + + + + + +
+
+
+ + + Remote Authentication + + +
+
+
+
+ + Remote Authentication + +
+
+
+ + + + + + + + +
+ + + + + Text is not SVG - cannot display + + + +
\ No newline at end of file diff --git a/guides/security/assets/switchprovidertenant-node.drawio.svg b/guides/security/assets/switchprovidertenant-node.drawio.svg new file mode 100644 index 0000000000..427d595c5a --- /dev/null +++ b/guides/security/assets/switchprovidertenant-node.drawio.svg @@ -0,0 +1 @@ +
Technical User
Technical...
OData Adapter
OData Adapter
ON Handler for OData Action
ON Handler for OData...
Sidecar
CAP Service
Sidecar...
@requires: 'internal-user'
@requires:...
User: John Doe
Tenant: tenant1
User: John Doe...
JWT token
JWT token
John Doe
John Doe
tenant1
tenant1
provider tenant
provider tenant
Text is not SVG - cannot display
\ No newline at end of file diff --git a/guides/security/assets/switchprovidertenant.drawio.svg b/guides/security/assets/switchprovidertenant.drawio.svg new file mode 100644 index 0000000000..dfd0ac29e7 --- /dev/null +++ b/guides/security/assets/switchprovidertenant.drawio.svg @@ -0,0 +1,216 @@ + + + + + + + + + + +
+
+
+ Technical User +
+
+
+
+ + Technical... + +
+
+ + + + + + +
+
+
+ OData Adapter +
+
+
+
+ + OData Adapter + +
+
+ + + + +
+
+
+ ON Handler for OData Action +
+
+
+
+ + ON Handler for OData... + +
+
+ + + + + + + + +
+
+
+ systemUserProvider() +
+
+
+
+ + systemUserProvider() + +
+
+ + + + + +
+
+
+ Sidecar +
+ CAP Service +
+
+
+
+ + Sidecar... + +
+
+ + + + + +
+
+
+ @requires: 'internal-user' +
+
+
+
+ + @requires:... + +
+
+ + + + +
+
+
+ User: John Doe +
+ Tenant: tenant1 +
+
+
+
+ + User: John Doe... + +
+
+ + + + +
+
+
+ JWT token +
+
+
+
+ + JWT token + +
+
+ + + + + +
+
+
+ John Doe +
+
+
+
+ + John Doe + +
+
+ + + + +
+
+
+ tenant1 +
+
+
+
+ + tenant1 + +
+
+ + + + +
+
+
+ provider tenant +
+
+
+
+ + provider tenant + +
+
+
+ + + + + Text is not SVG - cannot display + + + +
\ No newline at end of file diff --git a/guides/security/assets/switchtenant-node.drawio.svg b/guides/security/assets/switchtenant-node.drawio.svg new file mode 100644 index 0000000000..3162b1aeca --- /dev/null +++ b/guides/security/assets/switchtenant-node.drawio.svg @@ -0,0 +1 @@ +
Technical User
Technical...
Technical User
Technical...
Background Job
Background Job
Job Scheduler
Job Scheduler
Tenant specific processing
Tenant specific proc...
provider tenant
provider tenant
tenant1
tenant1
Text is not SVG - cannot display
\ No newline at end of file diff --git a/guides/security/assets/switchtenant.drawio.svg b/guides/security/assets/switchtenant.drawio.svg new file mode 100644 index 0000000000..25fac6f1f1 --- /dev/null +++ b/guides/security/assets/switchtenant.drawio.svg @@ -0,0 +1,158 @@ + + + + + + + + + + +
+
+
+ Technical User +
+
+
+
+ + Technical... + +
+
+ + + + + + + +
+
+
+ Technical User +
+
+
+
+ + Technical... + +
+
+ + + + +
+
+
+ Background Job +
+
+
+
+ + Background Job + +
+
+ + + + +
+
+
+ Job Scheduler +
+
+
+
+ + Job Scheduler + +
+
+ + + + + +
+
+
+ Tenant specific processing +
+
+
+
+ + Tenant specific proc... + +
+
+ + + + + +
+
+
+ systemUser("tenant1") +
+
+
+
+ + systemUser("tenant1") + +
+
+ + + + + +
+
+
+ provider tenant +
+
+
+
+ + provider tenant + +
+
+ + + + +
+
+
+ tenant1 +
+
+
+
+ + tenant1 + +
+
+
+ + + + + Text is not SVG - cannot display + + + +
\ No newline at end of file diff --git a/guides/security/authentication.md b/guides/security/authentication.md new file mode 100644 index 0000000000..443a96fdc8 --- /dev/null +++ b/guides/security/authentication.md @@ -0,0 +1,1349 @@ +--- +# layout: cookbook +label: Authentication +synopsis: > + This guide explains how to authenticate CAP services to resolve CAP users. +status: released +--- + + + + + +# Authentication { #authentication } + + + +This guide explains how to authenticate CAP services to resolve CAP users. + +[[toc]] + +## Pluggable Authentication + +In essence, authentication verifies the user's identity and validates the presented claims, such as granted roles and tenant membership. +Briefly, **authentication ensures _who_ is going to use the service**, in contrast to [authorization](../security/authorization#authorization) which determines _how_ the user can interact with the application's resources based on the defined access rules. +As access control relies on verified claims, authentication is a mandatory prerequisite for authorization. + +![Authentication with CAP](./assets/authentication.drawio.svg){width="550px" } + +According to key concept [Pluggable Building Blocks](./overview#key-concept-pluggable), the authentication method can be configured freely. +CAP [leverages platform services](overview#key-concept-platform-services) to provide proper authentication strategies to cover all relevant scenarios: + +- For _local development_ and _unit testing_, [Mock User Authentication](#mock-user-auth) is an appropriate built-in authentication feature. + +- For _cloud deployments_, in particular deployments for production, CAP provides integration of several identity services out of the box: + - [Identity Authentication Service (IAS)](#ias-auth) provides a full-fledged [OpenId Connect](https://openid.net/connect/) compliant, cross-landscape identity management as first choice for applications. + - [XS User Authentication and Authorization Service (XSUAA)](https://help.sap.com/docs/CP_AUTHORIZ_TRUST_MNG) is an [OAuth 2.0](https://oauth.net/2/)-based authorization server to support existing applications and services in the scope of individual BTP landscapes. + - CAP applications can run IAS and XSUAA in [hybrid mode](#hybrid-auth) to support a smooth migration from XSUAA to IAS. + + +## Mock User Authentication { #mock-user-auth } + +In non-production profile, by default, CAP creates a security configuration which accepts _mock users_. +As this authentication strategy is a built-in feature which does not require any platform service, it is perfect for **unit testing and local development scenarios**. + +Setup and start a simple sample application: + +
+ +```sh +cds init bookshop --java --add sample +cd ./bookshop +mvn spring-boot:run +``` + +::: tip +CAP Java requires some Maven [dependencies](../../java/security#maven-dependencies) to enable authentication middleware support. +Platform starter bundles `cds-starter-cf` and `cds-starter-k8s` ensure all required dependencies out of the box. +::: + +
+ + +
+ +```sh +cds init bookshop --add sample && cd ./bookshop +cds watch +``` + +
+ +In the application startup trace you can find a log message indicating mock user configuration is active: + +
+ +```sh +MockUsersSecurityConfig : * Security configuration based on mock users found in active profile. * +``` + +
+ +
+ +```sh +[cds] - using auth strategy { + kind: 'mocked', + … +} +``` + +
+ +
+ +Also notice the log output prints all recognized mock users such as +```sh +MockUsersSecurityConfig : Added mock user {"name":"admin","password":"admin", ...} +``` + +
+ +
+ +The CAP runtime will automatically authenticate all CAP endpoints - **you are not required to manually configure authentication for CAP endpoints!** + +::: tip +In non-production profile, you may set cds.security.authentication.mode = "model-relaxed" to deactivate authentication of endpoints derived from unrestricted CDS services. +::: + +Sending OData request `curl http://localhost:8080/odata/v4/CatalogService/Books --verbose` +results in a `401` error response from the server indicating that the anonymous user has been rejected due to missing authentication. +This is true for all endpoints including the web application page at `/index.html`. + +Mock users require **basic authentication**, hence sending the same request on behalf of mock user `admin` (password: `admin`) with curl `http://admin:admin@localhost:8080/odata/v4/CatalogService/Books` returns successfully (HTTP response `200`). + +
+ +
+ +The CAP runtime will automatically authenticate all CAP endpoints - **you are not required to manually configure authentication for CAP endpoints!** + +::: tip +In non-production profile, endpoints derived from unrestricted CDS services are not authenticated to simplify the development scenario. +::: + +Sending OData request + +```sh +curl http://localhost:4004/odata/v4/admin/Books --verbose +``` + +results in a `401` error response from the server indicating that the anonymous user has been rejected due to missing authentication. +This is true for all endpoints including the web application page at `/index.html`. + +Mock users require **basic authentication**, hence sending the same request on behalf of mock user `alice` (password: `basic`) with +```sh +curl http://alice:basic@localhost:4004/odata/v4/admin/Books +``` +returns successfully (HTTP response `200`). + +
+ + +::: tip +Mock users are deactivated in production profile by default ❗ +::: + +
+ +[Learn more about authentication options](../../java/security#spring-boot){.learn-more} + +
+ +
+ +[Learn more about authentication options](../../node.js/authentication#strategies){.learn-more} + +
+ + + +### Preconfigured Mock Users { #preconfigured-mock-users } + +For convenience, the runtime creates default mock users reflecting typical types of users suitable for test combinations, e.g. privileged users passing all security checks or restricted users which just pass authentication only. +The predefined users are merged with mock users [defined by the application](#custom-mock-users). +The effective list of mock users is traced to startup log if mock user configuration is active. + +You can opt out the preconfiguration of these users by setting `cds.security.mock.defaultUsers = false`. +{ .java } + + +
+ +[Learn more about predefined mock users in CAP Java](../../java/security#preconfigured-mock-users){.learn-more} + +
+ +
+ +[Learn more about predefined mock users in CAP Node.js](../../node.js/authentication#mock-users){.learn-more} + +
+ +### Customization { #custom-mock-users } + +You can define custom mock users to simulate any type of [end users](./cap-users#claims) that will interact with your application at production time. +Hence, you can use the mock users, to test your authorization settings or custom handlers, fully decoupled from the actual execution environment. + +
+ +::: details How to define a custom mock user with name `viewer-user` +```yaml [srv/src/main/resources/application.yaml] +spring: + config.activate.on-profile: default +cds: + security: + mock: + users: + # [... other users ...] + viewer-user: + password: pass + tenant: CrazyCars + roles: + - Viewer + attributes: + Country: [GER, FR] + features: + - cruise + - park + additional: + email: myviewer@crazycars.com +``` +::: + +
+ +
+ +::: details How to add a custom mock user with name `viewer-user` +```yaml [package.json] +"cds": { + "requires": { + "auth": { + "kind": "mocked", + "users": { + "viewer-user": { + "password": "pass", + "tenant": "CrazyCars", + "roles": ["Viewer"], + "attr": { ... } + } + }, + "tenants": { + "name" : "CrazyCars", + "features": [ "cruise", "park" ] + } + } + } +} +``` +::: + +
+ +In mock user configuration you can specify: +- name (mandatory) and tenant +- CAP roles (including pseudo-roles) and attributes affecting authorization +- additional attributes +- [feature toggles](../extensibility/feature-toggles#feature-toggles) +which influence request processing. + +To verify the user properties, activate [user tracing](./cap-users#user-tracing) and send a request using the mock user (`viewer-user` for example). +In the application log you will find information about the resolved user after successful authentication: + +
+ +```sh +MockedUserInfoProvider: Resolved MockedUserInfo [id='mock/viewer-user', name='viewer-user', roles='[Viewer]', attributes='{Country=[GER, FR], tenant=[CrazyCars]}' +``` + +
+ +
+ +``` +[basic] - authenticated: { user: 'viewer-user', tenant: 'CrazyCars', features: [ 'cruise', 'park' ] } +``` + +
+ +
+ +[Learn more about custom mock users](../../java/security#custom-mock-users){.learn-more} + +
+ +
+ +[Learn more about custom mock users](../../node.js/authentication#mocked){.learn-more} + +
+ + +### Automated Testing { #mock-user-testing } + +Mock users provide an ideal foundation for automated **unit tests, which are essential for ensuring application security**. +The flexibility in defining various types of mock users and the seamless integration into testing code significantly reduces the burden of covering all relevant test combinations. + +
+ +::: details How to use @WithMockUser in Spring-MVC to use CAP mock users +```java [srv/src/test/java/customer/bookshop/handlers/CatalogServiceTest.java] +@RunWith(SpringRunner.class) +@SpringBootTest +@AutoConfigureMockMvc +public class BookServiceOrdersTest { + + String BOOKS_URL = "/odata/v4/CatalogService/Books"; + + @Autowired + private MockMvc mockMvc; + + @Test + @WithMockUser(username = "viewer-user", password = "pass") + public void testViewer() throws Exception { + mockMvc.perform(get(BOOKS_URL)).andExpect(status().isOk()); + } + @Test + public void testUnauthorized() throws Exception { + mockMvc.perform(get(BOOKS_URL)).andExpect(status().isUnauthorized()); + } +} +``` +::: + +
+ +
+ +[Learn more about testing with authenticated endpoints](../../node.js/cds-test#authenticated-endpoints){.learn-more} + +
+ + +
+ +[Learn more about unit testing](../../java/developing-applications/testing#testing-cap-java-applications){.learn-more} + +
+ +
+ +[Learn more about unit testing](../../node.js/cds-test#testing-with-cds-test){.learn-more} + +
+ + +## IAS Authentication { #ias-auth } + +[SAP Identity Authentication Service (IAS)](https://help.sap.com/docs/cloud-identity-services) is the preferred platform service for identity management which provides: + - best of breed authentication mechanisms (single sign-on, multi-factor enforcement) + - federation of corporate identity providers (multiple user stores) + - cross-landscape user propagation (including on-premise) + - streamlined SAP and non-SAP system [integration](https://help.sap.com/docs/cloud-identity-services/cloud-identity-services/integrating-service) (due to [OpenId Connect](https://openid.net/connect/) compliance) + +IAS authentication is best configured and tested in the Cloud, so we're going to enhance the sample with a deployment descriptor for SAP BTP, Cloud Foundry Runtime (CF). + + +### Get Ready with IAS { #ias-ready } + +Before working with IAS on CF, you need to + +- Prepare an IAS (test) tenant. If not available yet, you need to [create](https://help.sap.com/docs/cloud-identity-services/cloud-identity-services/get-your-tenant) it now. + +- [Establish trust](https://help.sap.com/docs/btp/sap-business-technology-platform/establish-trust-and-federation-between-uaa-and-identity-authentication) +towards your IAS tenant to use it as identity provider for applications in your subaccount. + +- Ensure your development environment is [prepared for deploying](https://pages.github.tools.sap/cap/docs/guides/deployment/to-cf#prerequisites) on CF, +in particular you require a `cf` CLI session targeting a CF space in the test subaccount (test with `cf target`). + +You can continue with the sample [already created](#mock-user-auth). In the project root folder, execute + +```sh +cds add mta +``` + +to make your application ready for deployment to CF. + +
+ +::: tip +Command `add mta` will enhance the project with `cds-starter-cloudfoundry` and therefore all [dependencies required](../../java/security#maven-dependencies) for security are added transitively. +::: + +
+ +You also need to configure DB support: + +```sh [SAP HANA] +cds add hana +``` + + + +### Adding IAS + +Now the application is ready to for adding IAS-support by executing + +```sh +cds add ias +``` + +which automatically adds a service instance named `bookshop-ias` of type `identity` (plan: `application`) and binds the CAP application to it. + +::: details Generated deployment descriptor for IAS instance and binding +```yaml [mta.yaml] +modules: + - name: bookshop-srv + # [...] + requires: + - name: bookshop-ias + parameters: + config: + credential-type: X509_GENERATED + app-identifier: srv + +resources: + - name: bookshop-ias + type: org.cloudfoundry.managed-service + parameters: + service: identity + service-name: bookshop-ias + service-plan: application + config: + display-name: bookshop +``` +::: + +
+ +::: tip +Command `add ias` enhances the project with [required binding](../../java/security#bindings) to service instance identity and therefore activates IAS authentiaction automatically. +::: + +
+ +Whereas the service instance represents the IAS application itself, the binding provides access to the identity services on behalf of a client. +**CAP applications can have at most one binding to an IAS instance.** Conversely, multiple CAP applications can share the same IAS intstance. + +Following properties are available: + +| Property | Artifact | Description | +|-------------------|:-------------------:|:---------------------:| +| `name` | _instance_ | _Name for the IAS application - unique in the tenant_ | +| `display-name` | _instance_ | _Human-readable name for the IAS application as it appears in the Console UI for IAS administrators | +| `multi-tenant` | _instance_ | _Specifies application mode: `false` for single tenant (default), `true` for multiple subscriber tenants (SAAS)_ | +| `credential-type` | _binding_ | _`X509_GENERATED` generates a private-key and a signed certificate which is added to IAS application_ | +| `app-identifier` | _binding_ | _Ensures stable subject in generated certificate (required for credential rotation)_ | + + +[Lean more about IAS service instance and binding creation options](https://help.sap.com/docs/cloud-identity-services/cloud-identity-services/reference-information-for-identity-service-of-sap-btp){.learn-more} + +
+ +Now let's pack and deploy the application with +```sh +cds up +``` + +and wait until the application is up and running. +You can test the status with `cf apps` or in BTP Cockpit, alternatively. + +The following trace in the application log confirms the activated IAS authentication: +
+ +```sh +... : Loaded feature 'IdentityUserInfoProvider' (IAS: bookshop-ias, XSUAA: ) +``` + +
+ +
+TODO +
+ +At startup, the CAP runtime checks the available bindings and activates IAS authentication accordingly. +**Therefore, the local setup (no IAS binding in the environment) is still runnable**. + +For mTLS support which is mandatory for IAS, the CAP application has a second route configured with the `cert.*` domain. + +::: details Application routes with `cert.*`-domain +```yaml +modules: + - name: bookshop-srv + # [...] + parameters: + routes: + - route: "${default-url}" + - route: "${default-host}.cert.${default-domain}" + +::: tip +Platform-level TLS termination is provided on CF out of the box via `cert.*`-domains. +By default, the validated certificate is forwarded via HTTP header `X-Forwarded-Client-Cert` to the CAP endpoint. +::: + +::: warning +On SAP BTP Kyma Runtime, you might need to adapt configuration parameter `cds.security.authentication.clientCertificateHeader` to match the header used by the component terminating TLS you configured. +::: + + +#### Administrative Console for IAS { #ias-admin } + +In the [Administrative Console for Cloud Identity Services](https://help.sap.com/docs/cloud-identity-services/cloud-identity-services/accessing-administration-console?version=Cloud) +you can see and manage the deployed IAS application. You need a user with administrative privileges in the IAS tenant to access the services at `.accounts400.ondemand.com/admin`. + +In the Console you can manage the IAS tenant and IAS applications, for example: +- create (test) users in `Users & Authorizations` -> `User Management` +- deactivate users +- configure the authentication strategy (password policies, MFA etc.) in `Applications & Resources` -> `Applications` (IAS instances listed with their display-name) +- inspect logs in `Monitoring & Reporting` -> `Troubleshooting` + +::: tip +In BTP Cockpit, service instance `bookshop-ias` appears as a link that allows direct navigation to the IAS application in the Administrative Console for IAS. +::: + + +### CLI Level Testing + +Due to CAP's autoconfiguration, all CAP endpoints are authenticated and expect valid ID tokens generated for the IAS application. +Sending the test request +```sh +curl https://--bookshop-srv./odata/v4/CatalogService/Books --verbose +``` + +as anonymous user without a token results in a `401 Unauthorized` as expected. + +Now we want to fetch a token to prepare a fully authenticated test request. +As first step we add a new client for the IAS application by creating an appropriate service key: + +```sh +cf create-service-key bookshop-ias bookshop-ias-key \ + -c '{"credential-type": "X509_GENERATED"}' +``` + +The overall setup with local CLI client and the Cloud services is sketched in the diagram: + +![CLI-level Testing of IAS Endpoints](./assets/ias-cli-setup.drawio.svg){width="500px"} + +As IAS requires mTLS-protected channels, **client certificates are mandatory** for all of the following requests: +- Token request to IAS in order to fetch a valid IAS token (1) +- Business request to the CAP application presenting the token (2) +- Initial proof token request to IAS - not required for all business requests (3) + +The client certificates are presented in the IAS binding and hence can be examined via a service key accordingly. + +::: details How to create and retrieve service key credentials + +```sh +cf service-key bookshop-ias bookshop-ias-key + +{ + "credentials": { + [...] + "certificate": "-----BEGIN CERTIFICATE----- [...] -----END CERTIFICATE-----", + "clientid": "2a92c297-8603-4157-9aa9-ca758582abcd", + "credential-type": "X509_GENERATED", + "key": "-----BEGIN RSA PRIVATE KEY----- [...] -----END RSA PRIVATE KEY-----", + "url": "https://.accounts400.ondemand.com", + [...] + } +} +``` + +::: + +::: warning +❗ **Never share service keys or tokens** ❗ +::: + +From the credentials, you can prepare local files containing the certificate used to initiate the HTTP request. + +::: details How to prepare client X.509 certificate files + +Copy the public X.509-certificate in property `certificate` into a file `cert-raw.pem` and `key` into a file `key-raw.pem`, accordingly. +Both files need to be post-processed to transform the single-line representation into a standard multi-line representation: + +```sh +awk '{gsub(/\\n/,"\n")}1' .pem > .pem +``` +Finally, ensure correct format of both files with +```sh +openssl x509 -in .pem -text -noout +``` +All the steps can be executed in a single script as shown in the [example](https://cap.cloud.sap/resources/examples/fetch-ias-certs.sh). +::: + +To fetch a token - either as technical or as named user - the request needs to provide the **client certificate** being send to `/oauth2/token` endpoint of IAS service with URI given in `url` property of the binding: + +::: code-group + +```sh [Token for technical user] +curl --cert cert.pem --key key.pem \ + -d "grant_type=client_credentials"\ + -d "client_id=" \ + https:///oauth2/token +``` + +```sh [Token for named user] +curl --cert cert.pem --key key.pem \ + -H "Content-Type: application/x-www-form-urlencoded" \ + -d "grant_type=password" \ + -d "client_id=" \ + -d "username=" \ + -d "password=" \ + -X POST https:///oauth2/token +``` + +::: + + +The request returns with a valid IAS token which is suitable for authentication in the CAP application: +```sh +{"access_token":"[...]","token_type":"Bearer","expires_in":3600} +``` + +The final test request needs to provide the **client certificate and the token** being send to the application's route with `cert.*`-domain: + +```sh +curl --cert cert.pem --key key.pem -H "Authorization: Bearer " \ + https://--bookshop-srv.cert./odata/v4/CatalogService/Books +``` + +Don't forget to delete the service key after your tests: +```sh +cf delete-service-key bookshop-ias bookshop-ias-key +``` + + +### UI Level Testing + +In the UI scenario, adding an AppRouter as an ingress proxy to the deployment simplifies testing a lot. +It will take care of fetching the required IAS tokens when forwarding requests to the backend service. + +Enhancing the project with [SAP Cloud Portal](../deployment/to-cf#option-a-sap-cloud-portal) configuration adds an AppRouter component as well as HTML5 Application Repository: + +```sh +cds add portal +``` + +The resulting setup is sketched in the diagram: + +![UI-level Testing of IAS Endpoints](./assets/ias-ui-setup.svg){width="500px"} + +To be able to fetch the token, the AppRouter needs a binding to the IAS instance as well. +In addition, property `forwardAuthCertificates` needs to be `true` to support the mTLS connection with the service backend which is called by the route with the cert-domain. + +::: details AppRouter component with IAS binding +```yaml + - name: bookshop + [...] + requires: + - name: srv-api + group: destinations + properties: + name: srv-api + url: ~{srv-cert-url} + forwardAuthToken: true + forwardAuthCertificates: true + - name: bookshop-ias + parameters: + config: + credential-type: X509_GENERATED + app-identifier: approuter +``` +::: + +As the login flow is based on an HTTP redirect between the CAP application and IAS login page, +IAS needs to know a valid callback URI which is offered by the AppRouter out-of-the-box. +The same is true for the logout flow. + +::: details Redirect URIs for login and logout +```yaml + - name: bookshop-ias + [...] + parameters: + [...] + config: + [...] + oauth2-configuration: + redirect-uris: + - ~{app-api/app-protocol}://~{app-api/app-uri}/login/callback + post-logout-redirect-uris: + - ~{app-api/app-protocol}://~{app-api/app-uri}/*/logout.html +``` +::: + + +Npw re-deploy the solution by running: + +```sh +cds up +``` + + +## XSUAA Authentication { #xsuaa-auth } + +[SAP Authorization and Trust Management Service (XSUAA)](https://help.sap.com/docs/btp/sap-business-technology-platform/sap-authorization-and-trust-management-service-in-cloud-foundry-environment) is a platform service for identity and access management which provides: + - authentication mechanisms (single sign-on, multi-factor enforcement) + - federation of corporate identity providers (multiple user stores) + - create and assign access roles + +::: tip Info +In contrast to [IAS](#ias-auth), XSUAA does not allow cross-landscape user propagation out of the box. +::: + +XSUAA authentication is best configured and tested in the Cloud, so we're going to enhance the sample with a deployment descriptor for SAP BTP, Cloud Foundry Runtime (CF). + + +### Get Ready with XSUAA { #xsuaa-ready } + +Before working with XSUAA on CF, you need to ensure +- your development environment is [prepared for deploying](https://pages.github.tools.sap/cap/docs/guides/deployment/to-cf#prerequisites) to CF. +In particular, you require a `cf` CLI session targeting a CF space in the test subaccount (test with `cf target`). + +- https://help.sap.com/docs/application-frontend-service/application-frontend-service/enabling-service + +You can continue with the sample create for the [mock users](#mock-user-auth) or, alternatively, you can also enhance the [IAS-based](#ias-auth) application. + +If there is no deployment descriptor yet, in the project root folder, execute + +```sh +cds add mta +``` + +
+ +::: tip +Command `add mta` will enhance the project with `cds-starter-cloudfoundry` and therefore all [dependencies required](../../java/security#maven-dependencies) for security are added transitively. +::: + +
+ +to make your application ready for deployment to CF. + +You also need to configure DB support: + +```sh [SAP HANA] +cds add hana +``` + + +### Adding XSUAA { #adding-xsuaa } + +Now the application is ready to for adding XSUAA-support by executing + +
+ +```sh +cds add xsuaa +``` + +
+ +
+ +```sh +cds add xsuaa --for production +``` + +
+ + +which automatically adds a service instance named `bookshop-auth` of type `xsuaa` (plan: `application`) and binds the CAP application to it. + +
+ +::: tip Notice +Command `cds add xsuaa` enhances the project with [required binding](../../java/security#bindings) to service instance identity and therefore activates XSUAA authentication automatically. +::: + +
+ +```yaml [mta.yaml] +modules: + - name: bookshop-srv + # [...] + requires: + - name: bookshop-auth + +resources: + - name: bookshop-auth + type: org.cloudfoundry.managed-service + parameters: + service: xsuaa + service-plan: application + path: ./xs-security.json + config: + xsappname: bookshop-${org}-${space} + tenant-mode: dedicated + role-collections: + - name: 'admin (bookshop ${org}-${space})' + description: 'generated' + role-template-references: + - '$XSAPPNAME.admin' +``` + +**CAP applications should have at most one binding to an XSUAA instance.** Conversely, multiple CAP applications can share the same XSUAA instance. + +
+ +::: tip +In case your application has multiple XSUAA bindings you need to [pin the binding](../../java/security#bindings). +::: + +
+ +There are some mandatory configuration parameters: + +| Property | Description | +|-------------------|:-------------------:| +|`service-plan` | The plan type reflecting various application scenarios. UI applications without API access use plan `application`. All others should use plan `broker`. | +|`path` | File system path to the [application security descriptor](#xsuaa-security-descriptor). | +|`xsappname` | A unique application name within the subaccount. All XSUAA artifacts are prefixed with it (wildcard `$XSAPPNAME`). | +|`tenant-mode` | `dedicated` is suitable for a single-tenant application. Mode `shared` is mandatory for a [multitenant application](../../guides/multitenancy#multitenancy). | + +::: warning +Upgrading the `service-plan` from type `application` to `broker` is not supported. +Hence, start with `broker` if you plan to provide technical APIs. +::: + +[Learn more about XSUAA application security descriptor configuration syntax](https://help.sap.com/docs/hana-cloud-database/sap-hana-cloud-sap-hana-database-developer-guide-for-cloud-foundry-multitarget-applications-sap-web-ide-full-stack/application-security-descriptor-configuration-syntax){.learn-more} + +#### Security Descriptor { #xsuaa-security-descriptor } + +The security descriptor in the `xs-security.json` file contains the [XSUAA authorization artifacts](https://help.sap.com/docs/btp/sap-business-technology-platform/authorization-entities). +In general, XSUAA artifacts are managed in a hierarchy with role collections as root elements that can be assigned to users. +For convenience, when adding XSUAA facet, these artifacts are initially derived from the CDS model: + +- **XSUAA Scopes**: For every [CAP role](./cap-users#roles) in the CDS model, a dedicated scope is generated with the exact name of the CDS role. +- **XSUAA attributes** For every [CAP attribute](./authorization#user-attrs) in the CDS model, one attribute is generated. +- **XSUAA role templates** For every scope, a dedicated role template with the exact name is generated. The role templates are building blocks for concrete role collections that finally can be assigned to users. + +```json +{ + "scopes": [ + { + "name": "$XSAPPNAME.admin", + "description": "admin" + } + ], + "attributes": [], + "role-templates": [ + { + "name": "admin", + "description": "generated", + "scope-references": [ + "$XSAPPNAME.admin" + ], + "attribute-references": [] + } + ] +} +``` +[Learn more about XSUAA attributes](https://help.sap.com/docs/btp/sap-business-technology-platform/setting-up-instance-based-authorizations){.learn-more} +[Lean more about XSUAA security descriptor](https://help.sap.com/docs/btp/sap-business-technology-platform/application-security-descriptor-configuration-syntax){.learn-more} +[Learn how to setup mTLS for XSUAA](https://help.sap.com/docs/btp/sap-business-technology-platform/enable-mtls-authentication-to-sap-authorization-and-trust-management-service-for-your-application){.learn-more} + +After successful authentication, the scope prefix `$XSAPPNAME`is removed by the CAP integration to match the corresponding CAP role. + +In the [deplyoment descriptor](#adding-xsuaa), the optional property `role-collections` contains a list of preconfigured role collections. +In general, role collections are [created manually](./cap-users#xsuaa-assign) at runtime by user administrators. +But in case the underlying role template has no reference to an attribute, a corresponding role collection is prepared already. +In the example, role collection `admin (bookshop ${org}-${space})` containing the role template `admin` is defined and can be directly assigned to users. + + +::: tip Re-generate on model changes +You can have such a file re-generated via +```sh +cds compile srv --to xsuaa > xs-security.json +``` +::: + +See [Application Security Descriptor Configuration Syntax](https://help.sap.com/docs/btp/sap-business-technology-platform/application-security-descriptor-configuration-syntax) in the SAP Help documentation for the syntax of the _xs-security.json_ and advanced configuration options. + + +::: warning Avoid invalid characters in your models +Roles modeled in CDS may contain characters considered invalid by the XSUAA service. +::: + +::: warning +If you modify the _xs-security.json_ manually, make sure that the scope names in the file exactly match the role names in the CDS model, as these scope names will be checked at runtime. +::: + +#### Start and Check the Deployment + +Now let's pack and deploy the application with + +
+ +```sh +npm install +cds up +``` + +
+ +
+ +```sh +cds up +``` + +
+ +and wait until the application is up and running. +You can test the status with `cf apps` or in BTP Cockpit, alternatively. + +
+The following trace in the application log confirms the activated XSUAA authentication: + +```sh +... : Loaded feature 'IdentityUserInfoProvider' (IAS: , XSUAA: bookshop-auth) +``` + +
+ +
+ +run `cf logs bookshop-srv --recent` to confirm the activated XSUAA authentication: + +```sh +... : "using auth strategy { kind: 'xsuaa' … } +``` + +
+ +At startup, the CAP runtime checks the available bindings and activates XSUAA authentication accordingly. +**Therefore, the local setup (no XSUAA binding in the environment) is still runnable**. + + + +### CLI Level Testing + +Due to CAP's autoconfiguration, all CAP endpoints are authenticated and expect valid ID tokens generated for the XSUAA application. +Send the test request: + +
+ +```sh +curl https://--bookshop-srv./odata/v4/CatalogService/Books --verbose +``` + +
+ +
+ +```sh +curl https://--bookshop-srv./odata/v4/catalog/Books --verbose +``` + +
+ +…as anonymous user without a token the request results in a `401 Unauthorized` as expected. + +Now we want to fetch a token to prepare a fully authenticated test request. +As first step we add a new client for the XSUAA application by creating an appropriate service key: + +```sh +cf create-service-key bookshop-auth bookshop-auth-key +``` + + +```sh +cf service-key bookshop-auth bookshop-auth-key +``` + +```json +{ + "credentials": { + [...] + "clientid": "sb-bookshop-...", + "clientsecret": "...", + "url": "https://.authentication.sap.hana.ondemand.com", + [...] + } +} +``` + +::: warning +❗ **Never share service keys or tokens** ❗ +::: + +As second step, assign the generated role collection with name `admin (bookshop ${org}-${space})` to your **test user**. +Follow the instructions from step 4 onwards of [Assign Roles in SAP BTP Cockpit Step](./cap-users#xsuaa-assign). + +With the credentials, you can send an HTTP request to fetch the token from XSUAA `/oauth/token` endpoint: + +::: code-group + +```sh [Token for technical user] +curl -X POST \ + -H "Content-Type: application/x-www-form-urlencoded" \ + -d 'grant_type=client_credentials' \ + -d 'client_id=' \ + -d 'client_secret=' \ + /oauth/token +``` + +```sh [Token for named user] +curl -X POST \ + -H "Content-Type: application/x-www-form-urlencoded" \ + -d 'grant_type=password' \ + -d 'client_id=' \ + -d 'client_secret=' \ + -d 'username=' \ + -d 'password=' \ + /oauth/token +``` + +::: + +The request returns with a valid XSUAA token which is suitable to pass authentication in the CAP application: +```sh +{"access_token":"", "token_type":"bearer","expires_in":43199, [...]} +``` + + +
+The final test request needs to provide the token being send to the application's route: + +```sh +curl -H "Authorization: Bearer " \ + https://--bookshop-srv./odata/v4/CatalogService/Books +``` + +
+ +
+ +With the token for the technical user, you should be able to access any endpoint, which has no specific role requirements: + +```sh +curl -H "Authorization: Bearer " \ + https://--bookshop-srv./odata/v4/catalog/Books +``` + +If you also want to access the `AdminService` which requires the role `admin`, +you need to fetch the token for the named user instead. That is the user which you have assigned the `admin (bookshop ${org}-${space})` role collection to. + +With the token for the named user, the following request should succeed: + +```sh +curl -H "Authorization: Bearer " \ + https://--bookshop-srv./odata/v4/admin/Books +``` + +::: tip +Try out sending a request to the `admin` endpoint with the technical user token to see the expected `403 Forbidden` response: + +```sh +{ "error": { "message":"Forbidden","code":"403", … } } +``` + +::: + +
+ +Don't forget to delete the service key after your tests: +```sh +cf delete-service-key bookshop-auth bookshop-auth-key +``` + + +### UI Level Testing + +In the UI scenario, adding an AppRouter as an ingress proxy to the deployment simplifies testing a lot. +It will take care of fetching the required IAS tokens when forwarding requests to the backend service. + +Enhancing the project with [SAP Cloud Portal](../deployment/to-cf#option-a-sap-cloud-portal) configuration adds an AppRouter component as well as HTML5 Application Repository: + +```sh +cds add portal +``` + +The resulting architecture is very similar to the [IAS scenario](#ui-level-testing), but only with XSUAA service instances instead of IAS service instances. +There is one more difference: By default, XSUAA does not enforce mTLS. + +To be able to fetch the token, the AppRouter needs a binding to the XSUAA instance. + +::: details AppRouter component with XSUAA binding +```yaml +modules: +- name: bookshop + type: approuter.nodejs + path: app/router + [...] + requires: + - name: srv-api + group: destinations + properties: + name: srv-api # must be used in xs-app.json as well + url: ~{srv-url} + forwardAuthToken: true + - name: bookshop-auth + [...] + provides: + - name: app-api + properties: + app-protocol: ${protocol} + app-uri: ${default-uri} + url: ${default-url} +``` +::: + +As the login flow is based on an HTTP redirect between the CAP application and XSUAA login page, +XSUAA needs to know a valid callback URI which is offered by the AppRouter out-of-the-box. +The same is true for the logout flow. + +::: details Redirect URIs for login and logout +```yaml + - name: bookshop-auth + [...] + parameters: + [...] + config: + [...] + oauth2-configuration: + redirect-uris: + - https://*~{app-api/app-uri}/** + requires: + - name: app-api +``` +::: + + +Now update the Cloud deployment with + +```sh +cds up +``` + +and verify it by running `cf apps` in the targeted space: + +```sh +name requested state processes routes +bookshop-potal started web:1/1 --bookshop. +bookshop-potal-db-deployer stopped web:0/1 +bookshop-potal-srv started web:1/1 --bookshop-srv. +``` + +and open the route exposed by the `bookshop` UI application in a new browser session. + +
+ +E.g. `https://--bookshop.>/odata/v4/AdminService/Books` + +
+ +
+ +E.g. `https://--bookshop./odata/v4/admin/Books` + +
+ + + +## Hybrid Authentication { #hybrid-auth } + +will come soon + + +## Custom Authentication { #custom-auth } + +
+ +There are multiple reasons why customization might be required: +1. Endpoints for non-business requests often require specific authentication methods (e.g. health check, technical services). +2. The application is deployed in the context of a service mesh with ingress authentication (e.g. Istio). +3. The application needs to integrate with a 3rd party authentication service. + +![Endpoints with different authentication strategy](./assets/custom-auth.drawio.svg){width="380px"} + + +[Advanced configuration options](../../java/security#spring-boot) allow you to control the behaviour of CAP's authentication behaviour according to your needs: + + +- For CAP endpoints you are fine to go with the [automatic authentication](#model-auth) fully derived from the CAP model. +- For custom endpoints that should be protected by the same authentication strategy you are also fine with automatc authentication as CAP will cover these endpoints by default. +- For custom endpoints that should have a different kind of authentication strategy (e.g. X.509, basic or none) you can add a security configuration that [partially overrules](#partially-auth) the CAP integration partially for exactly these endpoints. +- In case the authentiaction is delegated to a different component, just [fully overrule](#fully-auth) CAP authentication and replace by any suitable strategy. + +::: tip Secure by Default +**By default, CAP authenticates all endpoints of the microservice, including the endpoints which are not served by CAP itself**. +This is the safe baseline on which minor customization steps can be applied on top. +::: + +
+ +
+ +Ideally, all authentication use-cases should be covered by the generic implementations CAP provides. +However, your application's specific requirements may make it necessary to customize authentication. +For these scenarios, the CAP Node.js runtime allows to specify an implementation of a custom authentication middleware in cds.requires.auth.impl, by providing a path relative to the project root. + +:::warning +Be **very** careful when creating your own `auth` implementation. +This should be a last resort for when every other possible solution (e.g. through [modelling](./authorization.md#restrictions) or by [configuration](#pluggable-authentication)) has been investigated and dismissed. +::: + +Like any other [custom middleware](../../node.js/cds-serve.md#custom-middlewares), the auth middleware you create needs to accept express's `req`, `res` and `next` and end up by sending a response, throwing an error or calling `next()`. +Additionally, a custom auth middleware in CAP needs to set `cds.context.user` and, in a multitenant applications, `cds.context.tenant`. + +```js +module.exports = function custom_auth (req, res, next) { + + // do your custom authentication + + cds.context.user = new cds.User({ + id: '', + roles: ['', ''], + attr: { + : '', + : '' + } + }) + cds.context.tenant = '' +} +``` + + + +:::tip +In case you want to customize the `cds.context.user`, check out [this example](../../node.js/cds-serve#customization-of-cds-context-user). +::: + +
+ + +### Automatic Authentication { #model-auth } + +As the auto-configuration authenticates all service endpoints found in the CDS model by default, +you don't need to explicitly activate authentication for these endpoints. + +Endpoints that should be public can be explicitly annotated with [pseudo-role](cap-users#pseudo-roles) `any`: + +```cds +service BooksService @(requires: 'any') { + @readonly entity Books @(requires: 'any') {...} + entity Reviews {...} + entity Orders @(requires: 'Customer') {...} +} +``` + +| Path | Authenticated ? | +|:--------------------------|:----------------:| +| `/BooksService` and `/BooksService/$metadata` | | +| `/BooksService/Books` | | +| `/BooksService/Reviews` | | +| `/BooksService/Orders` | | + +::: tip +In multitenant applications, anonymous requests to public endpoints are missing the tenant information and hence this gap needs to be filled by custom code. +::: + +By default, if a CAP service `MyService` is authenticated, also `/MyService/$metadata` is authenticated. + +
+ +With `cds.security.authentication.authenticateMetadataEndpoints: false` you can switch off this behaviour on a global level. + +[Learn more about authentication options](../../java/security#spring-boot){.learn-more} + +
+ +
+Automatic authentication enforcement can be disabled via feature flag cds.requires.auth.restrict_all_services: false, or by using [mocked authentication](#mock-user-auth) explicitly in production. +
+ +### Overrule Partially { #partially-auth .java } + +If you want to explicitly define the authentication for specific endpoints, **you can add an _additional_ Spring security configuration on top** overriding the default configuration given by CAP: + +```java +@Configuration +@EnableWebSecurity +public class CustomSecurityConfig { + + @Bean + @Order(1) // needs to have higher priority than CAP security config + public SecurityFilterChain customFilterChain(HttpSecurity http) throws Exception { + return http + .securityMatcher(AntPathRequestMatcher.antMatcher("/public/**")) + .csrf(c -> c.disable()) // don't insist on csrf tokens in put, post etc. + .authorizeHttpRequests(r -> r.anyRequest().permitAll()) + .build(); + } + +} +``` +Due to the custom configuration, all URLs matching `/public/**` are opened for public access in this example. + +Ensure your custom configuration has higher priority than CAP's default security configuration by decorating the bean with a low order. + +::: warning _❗ Warning_ +Be cautious with the configuration of the `HttpSecurity` instance in your custom configuration. Make sure that only the intended endpoints are affected. +::: + +[Learn more about overruling Spring security configuration in CAP Java](../../java/security#custom-spring-security-config){.learn-more} + + +### Overrule Fully { #fully-auth } + +In services meshes such as [Istio](https://istio.io/) the authentication is usually fully delegated to a central ingress gateway and the internal communication with the services is protercted by a secure channel: + +![Service Mesh with Ingress Gateway](./assets/ingress-auth.drawio.svg){width="500px"} + +::: tip +User propagation should be done by forwarding the request token in `Authorization`-header accordingly. +This will make standard CAP authorization work properly. +::: + +::: warning +If you switch off CAP authentication, make sure that the internal communication channels are secured by the given infrastructure. +::: + +
+In such architectures, CAP authentication is obsolete and can be deactivated entirely with `cds.security.authentication.mode="never"`. + +[Learn more about how to switch off authentication in CAP Java](../../java/security#custom-spring-security-alone){.learn-more} + +
+ +
+TODO +
+ + +## Pitfalls +- **Don't miss to configure security middleware.** + Endpoints of (CAP) applications deployed on SAP BTP are, by default, accessible from the public network. + Without security middleware configured, CDS services are exposed to the public. + +- **Don't rely on AppRouter authentication**. AppRouter as a frontend proxy does not shield the backend from incoming traffic. Therefore, the backend must be secured independently. + +- **Don't deviate from security defaults**. Only when absolutely necessary should experts make the decision to add modifications or replace parts of the standard authentication mechanisms. + +- **Don't forget to add authentication tests** to ensure properly configured security in your deployed application that rejects unauthenticated requests. + diff --git a/guides/security/authorization.md b/guides/security/authorization.md index 1a53ce52c8..43045bc60c 100644 --- a/guides/security/authorization.md +++ b/guides/security/authorization.md @@ -21,38 +21,46 @@ uacp: Used as link target from SAP Help Portal at https://help.sap.com/products/ -# CDS-based Authorization +# CAP Authorization { #authorization } -Authorization means restricting access to data by adding respective declarations to CDS models, which are then enforced in service implementations. By adding such declarations, we essentially revoke all default access and then grant individual privileges. + -[[toc]] +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. +[[toc]] +## Declarative Access Control { #restrictions} -## Authentication as Prerequisite { #prerequisite-authentication} +In essence, [authentication](./authentication#authentication) verifies the user's identity and the presented claims. Briefly, authentication reveals _who_ is using the service. +In contrast, **authorization controls _how_ the user may interact with the application's resources**. +As access control depends on user information, authentication is a prerequisite for authorization. -In essence, authentication verifies the user's identity and the presented claims such as granted roles and tenant membership. Briefly, **authentication** reveals _who_ uses the service. In contrast, **authorization** controls _how_ the user can interact with the application's resources according to granted privileges. As the access control needs to rely on verified claims, authentication is a prerequisite to authorization. +![Authorization with CAP](./assets/authorization.drawio.svg){width="500px"} -From perspective of CAP, the authentication method is freely customizable. For convenience, a set of authentication methods is supported out of the box to cover most common scenarios: +CAP authorization modeling means restricting user access to application resources in a declarative way. +The decisive point here is that the application logic does not need to contribute any security-critical code for this, but can rely on the generic framework. -- [XS User and Authentication and Authorization service](https://help.sap.com/docs/CP_AUTHORIZ_TRUST_MNG) (XSUAA) is a full-fleged [OAuth 2.0](https://oauth.net/2/) authorization server which allows to protect your endpoints in productive environments. JWT tokens issued by the server not only contain information about the user for authentication, but also assigned scopes and attributes for authorization. -- [Identity Authentication Service](https://help.sap.com/docs/IDENTITY_AUTHENTICATION) (IAS) is an [OpenId Connect](https://openid.net/connect/) compliant service for next-generation identity and access management. As of today, CAP provides IAS authentication for incoming requests only. Authorization has to be explicitly managed by the application. -- For _local development_ and _test_ scenario mock user authentication is provided as built-in feature. +There are several ways to define access rules on CDS resources: +- [Static access control](#static-access-control) limits access to CDS services on a general level independently of the request user. +- [Role-based access control](#role-based-access-control) derives resource access rules from roles granted by user administrators. +- [Instance-based access control](#instance-based-auth) allows entity-level filters that usually depend on user criteria. -Find detailed instructions for setting up authentication in these runtime-specific guides: +**By default, CDS services have no access control**, which means that without authorization modeling, authenticated users have access to all entities. -- [Set up authentication in Node.js.](/node.js/authentication) -- [Set up authentication in Java.](/java/security#authentication) +::: warning +**Applications must implement proper authorization.** CAP cannot enforce this automatically as it depends entirely on the specific domain model. +::: +Finally, according to the key concept [Customizable Security](./overview#key-concept-customizable), applications can implement custom authorization logic for exceptional scenarios when declarative approaches are insufficient. -In _productive_ environment with security middleware activated, **all protocol adapter endpoints are authenticated by default**1, even if no [restrictions](#restrictions) are configured. Multi-tenant SaaS-applications require authentication to provide tenant isolation out of the box. In case there is the business need to expose open endpoints for anonymous users, it's required to take extra measures depending on runtime and security middleware capabilities. -> 1 Starting with CAP Node.js 6.0.0 resp. CAP Java 1.25.0. _In previous versions endpoints without restrictions are public in single-tenant applications_. +## Static Access Control { #static-access-control } -### Defining Internal Services +### Internal Services -CDS services which are only meant for *internal* usage, shouldn't be exposed via protocol adapters. In order to prevent access from external clients, annotate those services with `@protocol: 'none'`: +CDS services that are only meant for *internal* usage shouldn't be exposed via protocol adapters. +In order to prevent access from *any* external clients, annotate those services with `@protocol: 'none'`: ```cds @protocol: 'none' @@ -60,107 +68,8 @@ service InternalService { ... } ``` -The `InternalService` service can only receive events sent by in-process handlers. - -## User Claims { #user-claims} - -CDS authorization is _model-driven_. This basically means that it binds access rules for CDS model elements to user claims. For instance, access to a service or entity is dependent on the role a user has been assigned to. Or you can even restrict access on an instance level, for example, to the user who created the instance.
-The generic CDS authorization is built on a _CAP user concept_, which is an _abstraction_ of a concrete user type determined by the platform's identity service. This design decision makes different authentication strategies pluggable to generic CDS authorization.
-After successful authentication, a (CAP) user is represented by the following properties: - -- Unique (logon) _name_ identifying the user. Unnamed users have a fixed name such as `system` or `anonymous`. -- _Tenant_ for multitenant applications. -- _Roles_ that the user has been granted by an administrator (see [User Roles](#roles)) or that are derived by the authentication level (see [Pseudo Roles](#pseudo-roles)). -- _Attributes_ that the user has been assigned by an administrator. - -In the CDS model, some of the user properties can be referenced with the `$user` prefix: - -| User Property | Reference | -|-------------------------------|---------------------| -| Name | `$user` | -| Attribute (name \) | `$user.` | - -> A single user attribute can have several different values. For instance, the `$user.language` attribute can contain `['DE','FR']`. - - -### User Roles { #roles} - -As a basis for access control, you can design conceptual roles that are application specific. Such a role should reflect how a user can interact with the application. For instance, the role `Vendor` could describe users who are allowed to read sales articles and update sales figures. In contrast, a `ProcurementManager` can have full access to sales articles. Users can have several roles, that are assigned by an administrative user in the platform's authorization management solution. -::: tip -CDS-based authorization deliberately refrains from using technical concepts, such as _scopes_ as in _OAuth_, in favor of user roles, which are closer to the conceptual domain of business applications. This also results in much **smaller JWT tokens**. -::: - - -### Pseudo Roles { #pseudo-roles} - -It's frequently required to define access rules that aren't based on an application-specific user role, but rather on the _authentication level_ of the request. For instance, a service could be accessible not only for identified, but also for anonymous (for example, unauthenticated) users. Such roles are called pseudo roles as they aren't assigned by user administrators, but are added at runtime automatically. - -The following predefined pseudo roles are currently supported by CAP: - -* `authenticated-user` refers to named or unnamed users who have presented a valid authentication claim such as a logon token. -* [`system-user` denotes an unnamed user used for technical communication.](#system-user) -* [`internal-user` is dedicated to distinguish application internal communication.](#internal-user) -* `any` refers to all users including anonymous ones (that means, public access without authentication). - -#### system-user -The pseudo role `system-user` allows you to separate access by _technical_ users from access by _business_ users. Note that the technical user can come from a SaaS or the PaaS tenant. Such technical user requests typically run in a _privileged_ mode without any restrictions on an instance level. For example, an action that implements a data replication into another system needs to access all entities of subscribed SaaS tenants and can’t be exposed to any business user. Note that `system-user` also implies `authenticated-user`. - -::: tip -For XSUAA or IAS authentication, the request user is attached with the pseudo role `system-user` if the presented JWT token has been issued with grant type `client_credentials` or `client_x509` for a trusted client application. -::: - -#### internal-user -Pseudo-role `internal-user` allows to define application endpoints that can be accessed exclusively by the own PaaS tenant (technical communication). The advantage is that similar to `system-user` no technical CAP roles need to be defined to protect such internal endpoints. However, in contrast to `system-user`, the endpoints protected by this pseudo-role do not allow requests from any external technical clients. Hence is suitable for **technical intra-application communication**, see [Security > Application Zone](/guides/security/overview#application-zone). - -::: tip -For XSUAA or IAS authentication, the request user is attached with the pseudo role `internal-user` if the presented JWT token has been issued with grant type `client_credentials` or `client_x509` on basis of the **identical** XSUAA or IAS service instance. -::: - -::: warning -All technical clients that have access to the application's XSUAA or IAS service instance can call your service endpoints as `internal-user`. -**Refrain from sharing this service instance with untrusted clients**, for instance by passing services keys or [SAP BTP Destination Service](https://help.sap.com/docs/connectivity/sap-btp-connectivity-cf/create-destinations-from-scratch) instances. -::: - -### Mapping User Claims - -Depending on the configured [authentication](#prerequisite-authentication) strategy, CAP derives a *default set* of user claims containing the user's name and attributes: - -| CAP User Property | XSUAA JWT Property | IAS JWT Property | -|---------------------|----------------------------------|-------------------------| -| `$user` | `user_name` | `sub` | -| `$user.` | `xs.user.attributes.` | All non-meta attributes | - -::: tip -CAP does not make any assumptions on the presented claims given in the token. String values are copied as they are. -::: - -In most cases, CAP's default mapping will match your requirements, but CAP also allows you to customize the mapping according to specific needs. For instance, `user_name` in XSUAA tokens is generally not unique if several customer IdPs are connected to the underlying identity service. -Here a combination of `user_name` and `origin` mapped to `$user` might be a feasible solution that you implement in a custom adaptation. Similarly, attribute values can be normalized and prepared for [instance-based authorization](#instance-based-auth). Find details and examples how to programmatically redefine the user mapping here: - -- [Set up Authentication in Node.js.](/node.js/authentication) -- [Custom Authentication in Java.](/java/security#custom-authentication) - -::: warning Be very careful when redefining `$user` -The user name is frequently stored with business data (for example, `managed` aspect) and might introduce migration efforts. Also consider data protection and privacy regulations when storing user data. -::: - -## Restrictions { #restrictions} - -According to [authentication](#prerequisite-authentication), CAP endpoints are closed to anonymous users. But **by default, CDS services have no access control** which means that authenticated users are not restricted. To protect resources according to your business needs, you can define [restrictions](#restrict-annotation) that make the runtime enforce proper access control. Alternatively, you can add custom authorization logic by means of an [authorization enforcement API](#enforcement). - -Restrictions can be defined on *different CDS resources*: - -- Services -- Entities -- (Un)bound actions and functions +`InternalService` can only receive events sent by in-process handlers. -You can influence the scope of a restriction by choosing an adequate hierarchy level in the CDS model. For instance, a restriction on the service level applies to all entities in the service. Additional restrictions on entities or actions can further limit authorized requests. See [combined restrictions](#combined-restrictions) for more details. - -Beside the scope, restrictions can limit access to resources with regards to *different dimensions*: - -- The [event](#restricting-events) of the request, that is, the type of the operation (what?) -- The [roles](#roles) of the user (who?) -- [Filter-condition](#instance-based-auth) on instances to operate on (which?) ### @readonly and @insertonly { #restricting-events} @@ -190,7 +99,7 @@ service SomeService { #### Events to Auto-Exposed Entities { #events-and-auto-expose} -In general, entities can be exposed in services in different ways: they can be **explicitly exposed** by the modeler (for example, by a projection), or they can be **auto-exposed** by the CDS compiler due to some reason. +In general, entities can be exposed in services in different ways: they can be **explicitly exposed** by the modeler (for example, by a projection), or they can be **auto-exposed** by the CDS compiler for some reason. Access to auto-exposed entities needs to be controlled in a specific way. Consider the following example: ```cds @@ -221,7 +130,7 @@ As a result, the `IssuesService` service actually exposes *all* three entities f * `db.Issues` is implicitly auto-exposed by the compiler as it is a composition entity of `Components`. * `db.Categories` is explicitly auto-exposed due to the `@cds.autoexpose` annotation. -In general, **implicitly auto-exposed entities cannot be accessed directly**, that means, only access via a navigation path (starting from an explicitly exposed entity) is allowed. +In general, **implicitly auto-exposed entities cannot be accessed directly**, which means only access via a navigation path (starting from an explicitly exposed entity) is allowed. In contrast, **explicitly auto-exposed entities can be accessed directly, but only as `@readonly`**. The rationale behind that is that entities representing value lists need to be readable at the service level, for instance to support value help lists. @@ -241,6 +150,27 @@ This results in the following access matrix: CodeLists such as `Languages`, `Currencies`, and `Countries` from `sap.common` are annotated with `@cds.autoexpose` and so are explicitly auto-exposed. ::: +## Role-Based Access Control { #role-based-access-control } + +To protect resources according to your business needs, you can declaratively restrict access according to a [CAP role](./cap-users#roles) by adding [@requires](#requires) or [@restrict](#restrict-annotation) annotations. + +Restrictions can be defined on *different CDS resources*: + +- Services +- Entities +- (Un)bound actions and functions + +You can influence the scope of a restriction by choosing an adequate hierarchy level in the CDS model. +For instance, a restriction on the service level applies to all entities in the service. +Additional restrictions on entities or actions can further limit authorized requests. +See [combined restrictions](#combined-restrictions) for more details. + +Beside the scope, restrictions can limit access to resources with regards to *different dimensions*: + +- The [event](#restricting-events) of the request, that is, the type of the operation (what?) +- The [roles](cap-users#roles) of the user (who?) +- [Filter-condition](#instance-based-auth) on instances to operate on (which?) + ### @requires { #requires} You can use the `@requires` annotation to control which (pseudo-)role a user requires to access a resource: @@ -253,7 +183,7 @@ annotate ShopService.ReplicationAction with @(requires: 'system-user'); In this example, the `BrowseBooksService` service is open for authenticated but not for anonymous users. A user who has the `Vendor` _or_ `ProcurementManager` role is allowed to access the `ShopService.Books` entity. Unbound action `ShopService.ReplicationAction` can only be triggered by a technical user. ::: tip -When restricting service access through `@requires`, the service's metadata endpoints (that means, `/$metadata` as well as the service root `/`) are restricted by default as well. If you require public metadata, you can disable the check with [a custom express middleware](../../node.js/cds-serve#add-mw-pos) using the [privileged user](../../node.js/authentication#privileged-user) (Node.js) or through config cds.security.authentication.authenticateMetadataEndpoints = false (Java), respectively. Please be aware that the `/$metadata` endpoint is *not* checking for authorizations implied by `@restrict` annotation. +When restricting service access through `@requires`, the service's metadata endpoints (that is, `/$metadata` as well as the service root `/`) are restricted by default as well. If you require public metadata, you can disable the check with [a custom express middleware](../../node.js/cds-serve#add-mw-pos) using the [privileged user](../../node.js/authentication#privileged-user) (Node.js) or through config cds.security.authentication.authenticateMetadataEndpoints = false (Java), respectively. Please be aware that the `/$metadata` endpoint is *not* checking for authorizations implied by `@restrict` annotation. ::: @@ -270,15 +200,15 @@ The building block of such a restriction is a single **privilege**, which has th whereas the properties are: * `grant`: one or more events that the privilege applies to -* `to`: one or more [user roles](#roles) that the privilege applies to (optional) +* `to`: one or more [user roles](cap-users#roles) that the privilege applies to (optional) * `where`: a filter condition that further restricts access on an instance level (optional). The following values are supported: - `grant` accepts all standard [CDS events](../../about/best-practices#events) (such as `READ`, `CREATE`, `UPDATE`, and `DELETE`) as well as action and function names. `WRITE` is a virtual event for all standard CDS events with write semantic (`CREATE`, `DELETE`, `UPDATE`, `UPSERT`) and `*` is a wildcard for all events. -- The `to` property lists all [user roles](#roles) or [pseudo roles](#pseudo-roles) that the privilege applies to. Note that the `any` pseudo-role applies for all users and is the default if no value is provided. +- The `to` property lists all [user roles](cap-users#roles) or [pseudo roles](cap-users#pseudo-roles) that the privilege applies to. Note that the `any` pseudo-role applies for all users and is the default if no value is provided. -- The `where`-clause can contain a Boolean expression in [CQL](/cds/cql)-syntax that filters the instances that the event applies to. As it allows user values (name, attributes, etc.) and entity data as input, it's suitable for *dynamic authorizations based on the business domain*. Supported expressions and typical use cases are presented in [instance-based authorization](#instance-based-auth). +- The `where` clause can contain a Boolean expression in [CQL](/cds/cql) syntax that filters the instances that the event applies to. As it allows user values (name, attributes, etc.) and entity data as input, it's suitable for *dynamic authorizations based on the business domain*. Supported expressions and typical use cases are presented in [instance-based authorization](#instance-based-auth). A privilege is met, if and only if **all properties are fulfilled** for the current request. In the following example, orders can only be read by an `Auditor` who meets `AuditBy` element of the instance: @@ -324,11 +254,6 @@ Here an `Auditor` user can read all orders with matching `country` or that they - `@requires: 'Viewer'` is equivalent to `@restrict: [{grant:'*', to: 'Viewer'}]` - `@readonly` is the same as `@restrict: [{ grant:'READ' }]` -Currently, the security annotations **are only evaluated on the target entity of the request**. Restrictions on associated entities touched by the operation aren't regarded. This has the following implications: -- Restrictions of (recursively) expanded or inlined entities of a `READ` request aren't checked. -- Deep inserts and updates are checked on the root entity only. - -See [solution sketches](#limitation-deep-authorization) for information about how to deal with that.{.learn-more} #### Supported Combinations with CDS Resources @@ -341,7 +266,7 @@ Restrictions can be defined on different types of CDS resources, but there are s | entity | | | 1 | | | action/function | | | 2 | = `@requires` | -> 1For bound actions and functions that aren't 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`. +> 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`. 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: @@ -397,6 +322,47 @@ The resulting authorizations are illustrated in the following access matrix: 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. +### Propagation of Restrictions { #propagated-restrictions } + +Service entities inherit the restriction from the database entity, on which they define a projection. +An explicit restriction defined on a service entity *replaces* inherited restrictions from the underlying entity. + +Entity `Books` on a database level: + +```cds +namespace db; +entity Books @(restrict: [ + { grant: 'READ', to: 'Buyer' }, +]) {/*...*/} +``` + +Services `BuyerService` and `AdminService` on a service level: + +```cds +service BuyerService @(requires: 'authenticated-user'){ + entity Books as projection on db.Books; /* inherits */ +} + +service AdminService @(requires: 'authenticated-user'){ + entity Books @(restrict: [ + { grant: '*', to: 'Admin'} /* overrides */ + ]) as projection on db.Books; +} +``` + +| Events | `Buyer` | `Admin` | `authenticated-user` | +|-------------------------------|:-------:|:-------:|:--------------------:| +| `BuyerService.Books` (`READ`) | | | | +| `AdminService.Books` (`*`) | | | | + +::: tip +We recommend defining restrictions on a database entity level only in exceptional cases. Inheritance and override mechanisms can lead to an unclear situation. +::: + +::: warning _Warning_ +A service level entity can't inherit a restriction with a `where` condition that doesn't match the projected entity. The restriction has to be overridden in this case. +::: + ### Draft Mode {#restrictions-and-draft-mode} @@ -443,57 +409,46 @@ So, the authorization for the requests in the example is delegated as follows: > 2 `@readonly` due to `@cds.autoexpose`
> 3 According to the restriction. `` is relevant for instance-based filters. -### Inheritance of Restrictions -Service entities inherit the restriction from the database entity, on which they define a projection. An explicit restriction defined on a service entity *replaces* inherited restrictions from the underlying entity. -Entity `Books` on a database level: +## Instance-Based Access Control { #instance-based-auth } -```cds -namespace db; -entity Books @(restrict: [ - { grant: 'READ', to: 'Buyer' }, -]) {/*...*/} -``` +The [restrict annotation](#restrict-annotation) for an entity allows you to enforce authorization checks that statically depend on the event type and user roles. +In addition, you can define a `where`-condition that further limits the set of accessible instances. +This condition, which acts like a filter, establishes *instance-based authorization*. -Services `BuyerService` and `AdminService` on a service level: +The condition defined in the `where` clause typically associates domain data with static user claims. -```cds -service BuyerService @(requires: 'authenticated-user'){ - entity Books as projection on db.Books; /* inherits */ -} +### Filter Conditions { #filter-consitions } -service AdminService @(requires: 'authenticated-user'){ - entity Books @(restrict: [ - { grant: '*', to: 'Admin'} /* overrides */ - ]) as projection on db.Books; -} -``` +The condition defined in the `where` clause typically associates domain data with static [user claims](cap-users#claims). +Basically, it *either filters the result set in queries or accepts only write operations on instances that meet the condition*. +This means that, the condition applies to following standard CDS events only: +- `READ` (as result filter) +- `UPDATE` (as reject condition) +- `DELETE` (as reject condition) -| Events | `Buyer` | `Admin` | `authenticated-user` | -|-------------------------------|:-------:|:-------:|:--------------------:| -| `BuyerService.Books` (`READ`) | | | | -| `AdminService.Books` (`*`) | | | | +
-::: tip -We recommend defining restrictions on a database entity level only in exceptional cases. Inheritance and override mechanisms can lead to an unclear situation. -::: +In addition, the runtime [checks the filter condition of the input data](#input-data-auth) for following standard CDS events: +- `CREATE` (input filter) +- `UPDATE` (input filer) -::: warning _Warning_ -A service level entity can't inherit a restriction with a `where` condition that doesn't match the projected entity. The restriction has to be overridden in this case. -::: +
+ +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. -## Instance-Based Authorization { #instance-based-auth } +
-The [restrict annotation](#restrict-annotation) for an entity allows you to enforce authorization checks that statically depend on the event type and user roles. In addition, you can define a `where`-condition that further limits the set of accessible instances. This condition, which acts like a filter, establishes an *instance-based authorization*. +* [Exists with a subquery](#exists-subquery) for access to ACL like entities. -The condition defined in the `where`-clause typically associates domain data with static [user claims](#user-claims). Basically, it *either filters the result set in queries or accepts only write operations on instances that meet the condition*. This means that, the condition applies to following standard CDS events only1: -- `READ` (as result filter) -- `UPDATE` (as reject condition2) -- `DELETE` (as reject condition2) +
- > 1 Node.js supports _static expressions_ that *don't have any reference to the model* such as `where: $user.level = 2` for all events. - > 2 CAP Java uses a filter condition by default. For instance, a user is allowed to read or edit `Orders` (defined with the `managed` aspect) that they have created: @@ -509,35 +464,37 @@ annotate Articles with @(restrict: [ { grant: ['UPDATE'], to: 'Vendor', where: (stock > 0) } ]); ``` -You can define `where`-conditions in restrictions based on [CQL](/cds/cql)-where-clauses.
-Supported features are: -* 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 [paths](#association-paths)) -* [Exists predicate](#exists-predicate) based on subselects. +::: tip +Filter conditions declared as **compiler expressions** ensure validity at compile time and therefore strengthen security. +::: -::: info Avoid enumerable keys -In case the filter condition is not met in an `UPDATE` or `DELETE` request, the runtime rejects the request (response code 403) even if the user is not even allowed to read the entity. To avoid to disclosure the existence of such entities to unauthorized users, make sure that the key is not efficiently enumerable. +At runtime you'll find filter predicates attached to the appropriate CQN queries matching the instance-based condition. + +:::warning Modification of Statements +Be careful when you modify or extend the statements in custom handlers. +Make sure you keep the filters for authorization. ::: -### User Attribute Values { #user-attrs} -To refer to attribute values from the user claim, prefix the attribute name with '`$user.`' as outlined in [static user claims](#user-claims). For instance, `$user.country` refers to the attribute with the name `country`. + +#### User Attributes { #user-attrs} + +To refer to attribute values from the user claim, prefix the attribute name with '`$user.`' as outlined in [static user claims](cap-users#claims). For instance, `$user.country` refers to the attribute with the name `country`. In general, `$user.` contains a **list of attribute values** that are assigned to the user. The following rules apply: * A predicate in the `where` clause evaluates to `true` if one of the attribute values from the list matches the condition. -* An empty (or not defined) list means that the user is fully restricted with regards to this attribute (that means that the predicate evaluates to `false`). +* An empty (or not defined) list means that the user is fully restricted with regard to this attribute (that is, the predicate evaluates to `false`). For example, the condition `where: $user.country = countryCode` will grant a user with attribute values `country = ['DE', 'FR']` access to entity instances that have `countryCode = DE` _or_ `countryCode = FR`. In contrast, the user has no access to any entity instances if the value list of country is empty or the attribute is not available at all. -#### Unrestricted XSUAA Attributes +##### Unrestricted XSUAA Attributes -By default, all attributes defined in [XSUAA instances](#xsuaa-configuration) require a value (`valueRequired:true`) which is well-aligned with the CAP runtime that enforces restrictions on empty attributes. +By default, all attributes defined in [XSUAA instances](./cap-users#xsuaa-roles) require a value (`valueRequired:true`), which is well-aligned with the CAP runtime that enforces restrictions on empty attributes. If you explicitly want to offer unrestricted attributes to customers, you need to do the following: 1. Switch your XSUAA configuration to `valueRequired:false` 2. Adjust the filter-condition accordingly, for example: `where: $user.country = countryCode or $user.country is null`. - > If `$user.country` is undefined or empty, the overall expression evaluates to `true` reflecting the unrestricted attribute. + > If `$user.country` is undefined or empty, the overall expression evaluates to `true`, reflecting the unrestricted attribute. ::: warning Refrain from unrestricted XSUAA attributes as they need to be designed very carefully as shown in the following example. @@ -555,8 +512,9 @@ service SalesService @(requires: ['SalesAdmin', 'SalesManager']) { } } ``` -Let's assume a customer creates XSUAA roles `SalesManagerEMEA` with dedicated values (`['DE', 'FR', ...]`) and 'SalesAdmin' with *unrestricted* values. -As expected, a user assigned only to 'SalesAdmin' has access to all `SalesOrgs`. But when role `SalesManagerEMEA` is added, *only* EMEA orgs are accessible suddenly! + +Let's assume a customer creates XSUAA roles `SalesManagerEMEA` with dedicated values (`['DE', 'FR', ...]`) and `SalesAdmin` with *unrestricted* values. +As expected, a user assigned only to `SalesAdmin` has access to all `SalesOrgs`. But when role `SalesManagerEMEA` is added, *only* EMEA organizations are accessible suddenly! The preferred way is to model with restricted attribute `country` (`valueRequired:true`) and an additional grant: ```cds @@ -572,9 +530,7 @@ service SalesService @(requires: ['SalesAdmin', 'SalesManager']) { } ``` - - -### Exists Predicate { #exists-predicate } +#### Exists Predicate { #exists-predicate } In many cases, the authorization of an entity needs to be derived from entities reachable via association path. See [domain-driven authorization](#domain-driven-authorization) for more details. You can leverage the `exists` predicate in `where` conditions to define filters that directly apply to associated entities defined by an association path: @@ -630,16 +586,14 @@ service ProductsService @(requires: 'authenticated-user') { } ``` -Here, the authorization of `Products` is derived from `Divisions` by leveraging the _n:m relationship_ via entity `ProducingDivisions`. Note that the path `producers.division` in the `exists` predicate points to target entity `Divisions`, where the filter with the user-dependent attribute `$user.division` is applied. +Here, the authorization of `Products` is derived from `Divisions` by leveraging the *n:m relationship* via entity `ProducingDivisions`. Note that the path `producers.division` in the `exists` predicate points to target entity `Divisions`, where the filter with the user-dependent attribute `$user.division` is applied. ::: warning Consider Access Control Lists Be aware that deep paths might introduce a performance bottleneck. Access Control List (ACL) tables, managed by the application, allow efficient queries and might be the better option in this case. ::: -
- -### Association Paths { #association-paths} +#### Association Paths { #association-paths} The `where`-condition in a restriction can also contain [CQL path expressions](/cds/cql#path-expressions) that navigate to elements of associated entities: @@ -656,23 +610,141 @@ service SalesOrderService @(requires: 'authenticated-user') { } ``` -Paths on 1:n associations (`Association to many`) are only supported, _if the condition selects at most one associated instance_. -It's highly recommended to use the [exists](#exists-predicate) predicate instead. -::: tip -Be aware of increased execution time when modeling paths in the authorization check of frequently requested entities. Working with materialized views might be an option for performance improvement in this case. +Paths on 1:n associations (`Association to many`) evaluate to `true`, _if the condition selects at most one associated instance_ (`exists` semantic). + + +
+ +
+ +
+ + +### 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`. + +Let's assume an entity `Orders` which restricts access to users classified by assigned accounting areas: + +```cds +annotate Orders with @(restrict: [ + { grant: '*', where: 'accountingArea = $user.accountingAreas' } ]); +``` + +A user with accounting areas `[Development, Research]` is not able to send an `UPDATE` request, that changes `accountingArea` from `Research` or `Development` to `CarFleet`, for example. +Note that the `UPDATE` on instances _not matching the request user's accounting areas_ (for example, `CarFleet`) are rejected by standard instance-based authorization checks. + +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} + +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**. +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. +The additional authorization check may affect performance. + +::: warning Avoid enumerable keys +To avoid to disclosure 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 behaviour otherwise. ::: -::: warning _Warning_ -In Node.js association paths in `where`-clauses are currently only supported when using SAP HANA. +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} + +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. +This has the following implications: +- Restrictions of (recursively) expanded or inlined entities of a `READ` request aren't checked. +- Deep inserts and updates are checked on the root entity only. + +See [solution sketches](#limitation-deep-authorization) for information about how to deal with that. + + +## Deep Authorizations { #deep-auth .java} + +### Associations + +Queries to Application Services are not only authorized by the target entity which has a `@restrict` or `@requires` annotation, but also for all __associated entities__ that are used in the statement. +For instance, consider the following model: + +```cds +@(restrict: [{ grant: 'READ', to: 'Manager' }]) +entity Books {...} + +@(restrict: [{ grant: 'READ', to: 'Manager' }]) +entity Orders { + key ID: String; + items: Composition of many { + key book: Association to Books; + quantity: Integer; + } +} +``` + +For the following OData request `GET Orders(ID='1')/items?$expand=book`, authorizations for `Orders` and for `Books` are checked. +If the entity `Books` has a `where` clause for instance-based authorization, it will be added as a filter to the sub-request with the expand. + +Custom CQL statements submitted to the [Application Service](../../java/cqn-services/application-services) instances are also authorized by the same rules including the path expressions and subqueries used in them. + +For example, the following statement checks role-based authorizations for both `Orders` and `Books`, +because the association to `Books` is used in the select list. + +```java +Select.from(Orders_.class, + f -> f.filter(o -> o.ID().eq("1")).items()) + .columns(c -> c.book().title()); +``` + +For modification statements with associated entities used in infix filters or where clauses, +role-based authorizations are checked as well. +Associated entities require `READ` authorization, in contrast to the target of the statement itself. + +The following statement requires `UPDATE` authorization on `Orders` and `READ` authorization on `Books` +because an association from `Orders.items` to the book is used in the where condition. + +```java +Update.entity(Orders_.class, f -> f.filter(o -> o.ID().eq("1")).items()) + .data("quantity", 2) + .where(t -> t.book().ID().eq(1)); +``` +Starting with CAP Java `4.0`, deep authorization is active by default. +It can be disabled by setting cds.security.authorization.deep.enabled: false. + + +### Compositions + +Restrictions on associated composition entities touched by the request are **not** regarded by the runtime. +The rational behind that is that authorization rules are [implicitly defined by the root entity of the document](#autoexposed-restrictions) and therefore security annotations **of the composition root entity are evaluated**. + +This has the following implications: +- Restrictions of (recursively) expanded or inlined entities of a `READ` request aren't checked. +- Deep `INSERT`s and `UPDATE`s are checked on the root entity only. + +::: warning +**Restrictions on compositions are not checked by the runtime**. +If you model dedicated restriction rules on child entity level, you need to add custom authorization handlers accordingly. ::: + + + ## Best Practices CAP authorization allows you to control access to your business data on a fine granular level. But keep in mind that the high flexibility can end up in security vulnerabilities if not applied appropriately. In this perspective, lean and straightforward models are preferred. When modeling your access rules, the following recommendations can support you to design such models. ### Choose Conceptual Roles -When defining user roles, one of the first options could be to align roles to the available _operations_ on entities, which results in roles such as `SalesOrders.Read`, `SalesOrders.Create`, `SalesOrders.Update`, and `SalesOrders.Delete`, etc. What is the problem with this approach? Think about the resulting number of roles that the user administrator has to handle when assigning them to business users. The administrator would also have to know the domain model precisely and understand the result of combining the roles. Similarly, assigning roles to operations only (`Read`, `Create`, `Update`, ...) typically doesn't fit your business needs.
+When defining user roles, one of the first options could be to align roles to the available *operations* on entities, which results in roles such as `SalesOrders.Read`, `SalesOrders.Create`, `SalesOrders.Update`, and `SalesOrders.Delete`. + +What is the problem with this approach? Think about the resulting number of roles that the user administrator has to handle when assigning them to business users. The administrator would also have to know the domain model precisely and understand the result of combining the roles. Similarly, assigning roles to operations only (`Read`, `Create`, `Update`, ...) typically doesn't fit your business needs.
We strongly recommend defining roles that describe **how a business user interacts with the system**. Roles like `Vendor`, `Customer`, or `Accountant` can be appropriate. With this approach, the application developers define the set of accessible resources in the CDS model for each role - and not the user administrator. ### Prefer Single-Purposed, Use-Case Specific Services { #dedicated-services} @@ -691,7 +763,7 @@ service CatalogService @(requires: 'authenticated-user') { ``` Four different roles (`authenticated-user`, `Vendor`, `Accountant`, `Admin`) *share* the same service - `CatalogService`. As a result, it's confusing how a user can use `Books` or `doAccounting`. Considering the complexity of this small example (4 roles, 1 service, 2 resources), this approach can introduce a security risk, especially if the model is larger and subject to adaptation. Moreover, UIs defined for this service will likely appear unclear as well.
-The fundamental purpose of services is to expose business data in a specific way. Hence, the more straightforward way is to **use a service for each of the roles**: +The fundamental purpose of services is to expose business data in a specific way. Hence, the more straightforward way is to **use a service for each role**: ```cds @path:'browse' @@ -732,23 +804,25 @@ service GitHubRepositoryService @(requires: 'authenticated-user') { } ``` -This service allows querying organizations for all authenticated users. In addition, `Admin` users are allowed to rename or delete. Granting `UPDATE` to `Admin` would allow administrators to change organization attributes that aren't meant to change. +This service allows querying organizations for all authenticated users. In addition, `Admin` users are allowed to rename or delete. + +Granting `UPDATE` to `Admin` would allow administrators to change organization attributes that are not meant to change. ### Think About Domain-Driven Authorization { #domain-driven-authorization} -Static roles often don't fit into an intuitive authorization model. Instead of making authorization dependent from static properties of the user, it's often more appropriate to derive access rules from the business domain. For instance, all users assigned to a department (in the domain) are allowed to access the data of the organization comprising the department. Relationships in the entity model (for example, a department assignment to organization), influence authorization rules at runtime. In contrast to static user roles, **dynamic roles** are fully domain-driven. +Static roles often don't fit into an intuitive authorization model. Instead of making authorization dependent on static properties of the user, it's often more appropriate to derive access rules from the business domain. For instance, all users assigned to a department (in the domain) are allowed to access the data of the organization comprising the department. Relationships in the entity model (for example, a department assignment to organization) influence authorization rules at runtime. In contrast to static user roles, **dynamic roles** are fully domain-driven. Revisit the [ProjectService example](#exists-predicate), which demonstrates how to leverage instance-based authorization to induce dynamic roles. Advantages of dynamic roles are: - The most flexible way to define authorizations -- Induced authorizations according to business domain +- Authorizations induced according to business domain - Application-specific authorization model and intuitive UIs - Decentralized role management for application users (no central user administrator required) Drawbacks to be considered are: - Additional effort for modeling and designing application-specific role management (entities, services, UI) -- Potentially higher security risk due to lower use of the framework functionality +- Potentially higher security risk due to lower use of framework functionality - Sharing authorization management with other (non-CAP) applications is harder to achieve - Dynamic role enforcement can introduce a performance penalty @@ -781,7 +855,9 @@ service BrowseEmployeesService @(requires:'Employee') { } ``` -A team (entity `Teams`) contains members of type `Employees`. An employee refers to a single contract (entity `Contracts`) which contains sensitive information that should be visible only to `Manager` users. `Employee` users should be able to browse the teams and their members, but aren't allowed to read or even edit their contracts.
+A team (entity `Teams`) contains members of type `Employees`. An employee refers to a single contract (entity `Contracts`), which contains sensitive information that should be visible only to `Manager` users. + +`Employee` users should be able to browse the teams and their members but are not allowed to read or even edit their contracts.
As `db.Employees` and `db.Contracts` are auto-exposed, managers can navigate to all instances through the `ManageTeamsService.Teams` service entity (for example, OData request `/ManageTeamsService/Teams?$expand=members($expand=contract)`).
It's important to note that this also holds for an `Employee` user, as **only the target entity** `BrowseEmployeesService.Teams` **has to pass the authorization check in the generic handler, and not the associated entities**.
To solve this security issue, introduce a new service entity `BrowseEmployeesService.Employees` that removes the navigation to `Contracts` from the projection: @@ -795,19 +871,21 @@ service BrowseEmployeesService @(requires:'Employee') { } ``` -Now, an `Employee` user can't expand the contracts as the composition isn't reachable anymore from the service. +Now, an `Employee` user cannot expand the contracts as the composition is not reachable anymore from the service. ::: tip -Associations without navigation links (for example, when an associated entity isn't exposed) are still critical with regards to security. +Associations without navigation links (for example, when an associated entity is not exposed) are still critical with regard to security. ::: ### Design Authorization Models from the Start -As shown before, defining an adequate authorization strategy has a deep impact on the service model. Apart from the fundamental decision, if you want to build your authorizations on [dynamic roles](#domain-driven-authorization), authorization requirements can result in rearranging service and entity definitions completely. In the worst case, this means rewriting huge parts of the application (including the UI). For this reason, it's *strongly* recommended to take security design into consideration at an early stage of your project. +As shown before, defining an adequate authorization strategy has a deep impact on the service model. Apart from the fundamental decision of whether you want to build your authorizations on [dynamic roles](#domain-driven-authorization), authorization requirements can result in completely rearranging service and entity definitions. + +For this reason, it's *strongly* recommended to take security design into consideration at an early stage of your project. ### Keep it as Simple as Possible -* If different authorizations are needed for different operations, it's easier to have them defined at the service level. If you start defining them at the entity level, all possible operations must be specified, otherwise the not mentioned operations are automatically forbidden. -* If possible, try to define your authorizations either on the service or on the entity level. Mixing both variants increases complexity and not all combinations are supported either. +* If different authorizations are needed for different operations, it's easier to have them defined at the service level. If you start defining them at the entity level, all possible operations must be specified; otherwise, the operations not mentioned are automatically forbidden. +* If possible, try to define your authorizations either on the service or on the entity level. Mixing both variants increases complexity, and not all combinations are supported either. ### Separation of Concerns @@ -847,149 +925,3 @@ service CustomerService @(requires: 'authenticated-user'){ ::: This keeps your actual service definitions concise and focused on structure only. It also allows you to give authorization models separate ownership and lifecycle. - - -## Programmatic Enforcement { #enforcement} - -The service provider frameworks **automatically enforce** restrictions in generic handlers. They evaluate the annotations in the CDS models and, for example: - -* Reject incoming requests if static restrictions aren't met. -* Add corresponding filters to queries for instance-based authorization, etc. - -If generic enforcement doesn't fit your needs, you can override or adapt it with **programmatic enforcement** in custom handlers: - -- [Authorization Enforcement in Node.js](/node.js/authentication#enforcement) -- [Enforcement API & Custom Handlers in Java](/java/security#enforcement-api) - -## Role Assignments with IAS and AMS - -The Authorization Management Service (AMS) as part of SAP Cloud Identity Services (SCI) provides libraries and services for developers of cloud business applications to declare, enforce and manage instance based authorization checks. When used together with CAP the AMS "Policies” can contain the CAP roles as well as additional filter criteria for instance based authorizations that can be defined in the CAP model. transformed to AMS policies and later on refined by customers user and authorization administrators in the SCI administration console and assigned to business users. - -### Use AMS as Authorization Management System on SAP BTP - -SAP BTP is currently replacing the authorization management done with XSUAA by an integrated solution with AMS. AMS is integrated into SAP Cloud Identity (SCI), which will offer authentication, authorization, user provisioning and management in one place. - -For newly build applications the usage of AMS is generally recommended. The only constraint that comes with the usage of AMS is that customers need to copy their users to the Identity Directory Service as the central place to manage users for SAP BTP applications. This is also the general SAP strategy to simplify user management in the future. - -### Case For XSUAA - -There is one use case where currently an XSUAA based authorization management is preferable: When XSUAA based services to be consumed by a CAP application come with their own business user roles and thus make user role assignment in the SAP Cloud Cockpit necessary. This will be resolved in the future when the authorization management will be fully based on the SCI Admin console. - -For example, SAP Task Center you want to consume an XSUAA-based service that requires own end user role. Apart from this, most services should be technical services that do not require an own authorization management that is not yet integrated in AMS. - - - -[Learn more about using IAS and AMS with CAP Node.js](https://github.com/SAP-samples/btp-developer-guide-cap/blob/main/documentation/xsuaa-to-ams/README.md){.learn-more} - - -## Role Assignments with XSUAA { #xsuaa-configuration} - -Information about roles and attributes has to be made available to the UAA platform service. This information enables the respective JWT tokens to be constructed and sent with the requests for authenticated users. In particular, the following happens automatically behind-the-scenes upon build: - - -### 1. Roles and Attributes Are Filled into the XSUAA Configuration - -Derive scopes, attributes, and role templates from the CDS model: - -```sh -cds add xsuaa -``` - -This generates an _xs-security.json_ file: - -::: code-group -```json [xs-security.json] -{ - "scopes": [ - { "name": "$XSAPPNAME.admin", "description": "admin" } - ], - "attributes": [ - { "name": "level", "description": "level", "valueType": "s" } - ], - "role-templates": [ - { "name": "admin", "scope-references": [ "$XSAPPNAME.admin" ], "description": "generated" } - ] -} -``` -::: - -For every role name in the CDS model, one scope and one role template are generated with the exact name of the CDS role. - -::: tip Re-generate on model changes -You can have such a file re-generated via -```sh -cds compile srv --to xsuaa > xs-security.json -``` -::: - -See [Application Security Descriptor Configuration Syntax](https://help.sap.com/docs/HANA_CLOUD_DATABASE/b9902c314aef4afb8f7a29bf8c5b37b3/6d3ed64092f748cbac691abc5fe52985.html) in the SAP HANA Platform documentation for the syntax of the _xs-security.json_ and advanced configuration options. - - -::: warning Avoid invalid characters in your models -Roles modeled in CDS may contain characters considered invalid by the XSUAA service. -::: - -If you modify the _xs-security.json_ manually, make sure that the scope names in the file exactly match the role names in the CDS model, as these scope names will be checked at runtime. - -### 2. XSUAA Configuration Is Completed and Published - -#### Through MTA Build - -If there's no _mta.yaml_ present, run this command: - -```sh -cds add mta -``` - -::: details See what this does in the background… - -1. It creates an _mta.yaml_ file with an `xsuaa` service. -2. The created service added to the `requires` section of your backend, and possibly other services requiring authentication. -::: code-group -```yaml [mta.yaml] -modules: - - name: bookshop-srv - requires: - - bookshop-auth # [!code ++] -resources: - name: bookshop-auth # [!code ++] - type: org.cloudfoundry.managed-service # [!code ++] - parameters: # [!code ++] - service: xsuaa # [!code ++] - service-plan: application # [!code ++] - path: ./xs-security.json # include cds managed scopes and role templates [!code ++] - config: # [!code ++] - xsappname: bookshop-${org}-${space} # [!code ++] - tenant-mode: dedicated # 'shared' for multitenant deployments [!code ++] -``` -::: - - -Inline configuration in the _mta.yaml_ `config` block and the _xs-security.json_ file are merged. If there are conflicts, the [MTA security configuration](https://help.sap.com/docs/HANA_CLOUD_DATABASE/b9902c314aef4afb8f7a29bf8c5b37b3/6d3ed64092f748cbac691abc5fe52985.html) has priority. - -[Learn more about **building and deploying MTA applications**.](/guides/deployment/){ .learn-more} - -### 3. Assembling Roles and Assigning Roles to Users - -This is a manual step an administrator would do in SAP BTP Cockpit. See [Set Up the Roles for the Application](/node.js/authentication#auth-in-cockpit) for more details. If a user attribute isn't set for a user in the IdP of the SAP BTP Cockpit, this means that the user has no restriction for this attribute. For example, if a user has no value set for an attribute "Country", they're allowed to see data records for all countries. -In the _xs-security.json_, the `attribute` entity has a property `valueRequired` where the developer can specify whether unrestricted access is possible by not assigning a value to the attribute. - - -### 4. Scopes Are Narrowed to Local Roles - -Based on this, the JWT token for an administrator contains a scope `my.app.admin`. From within service implementations of `my.app` you can reference the scope: - -```js -req.user.is ("admin") -``` -... and, if necessary, from others by: - -```js -req.user.is ("my.app.admin") -``` - -
- -> See the following sections for more details: -- [Developing Security Artifacts in SAP BTP](https://help.sap.com/products/BTP/65de2977205c403bbc107264b8eccf4b/419ae2ef1ddd49dca9eb65af2d67c6ec.html) -- [Maintaining Application Security in XS Advanced](https://help.sap.com/docs/HANA_CLOUD_DATABASE/b9902c314aef4afb8f7a29bf8c5b37b3/35d910ee7c7a445a950b6aad989a5a26.html) diff --git a/guides/security/cap-users.md b/guides/security/cap-users.md new file mode 100644 index 0000000000..0a1030b024 --- /dev/null +++ b/guides/security/cap-users.md @@ -0,0 +1,1696 @@ +--- +# layout: cookbook +label: CAP Users +synopsis: > + This guide introduces to CAP user abstraction and role assignments. +status: released +--- + + + + + +# CAP Users { #cap-users } + + + +This guide introduces to CAP user abstraction and role assignments. + +[[toc]] + +## CAP User Abstraction { #claims } + +A successful authentication results in a CAP [user representation](#claims) reflecting the request user in a uniform way. +Referring to the [key concepts](./overview#key-concept-decoupled-coding), the abstraction serves to fully decouple authorization and business logic from pluggable authentication strategies. +It contains static information about the user such as name, ID and tenant. Additionally, it contains claims such as roles or assigned attributes that are relevant for [authorization](./authorization). + +![CAP Userse](./assets/cap-users.drawio.svg){width="650px" } + +After _successful_ authentication, a **CAP user** is mainly represented by the following properties: + +- **_Logon name_** identifying the user uniquely +- **_Tenant_** describes the tenant of the user (subscriber or provider) which implies the CDS model and business data container. +- **_Roles_** the user has been assigned by a user administrator (business [user roles](#roles)) or roles which are derived by the authentication level ([pseudo roles](#pseudo-roles)). +- **_Attributes_** the user has been assigned, for example, for instance-based authorization. + +
+ +The user information is reflected in the `UserInfo` object [attached to the request](#reflection). + +
+ +
+ +The user information is reflected in `req.user` and `req.tenant` [attached to the request](#reflection). + +
+ + +### User Types + +CAP users can be classified in multiple dimensions: + +**Business users vs. technical users:** +- Business users represent identified end users who log in to interact with the system. +- Technical users operate on behalf of an entire tenant at a technical API level. + +**Authenticated users vs. anonymous users** +- Authenticated users have successfully completed authentication by presenting valid credentials (e.g., a token). +- Anonymous users are unidentifiable in general, as they usually do not present any credentials. + +**Provider vs. subscriber tenant** +- The provider tenant includes all users of the application owner. +- A subscriber tenant includes all users of an application customer. + + +Typically, the provider tenant is not subscribed to a [multi-tenant application](../multitenancy/#multitenancy) and therefore has no business users. + +| Multi-Tenant Application | Business users | Technical user +|---------------------------|----------------|---------------- +| Provider Tenant | - | +| Subscriber Tenants | | + +In contrast, for a single-tenant application, the provider tenant coincides with the only subscriber tenant and therefore contains all business users. + +| Single-Tenant Application | Business users | Technical user +|---------------------------|----------------|---------------- +| Provider (=subscriber) Tenant | | + + +::: tip +Apart from anonymous users, all users have a unique tenant. +::: + +The user types are designed to support various flows, such as: +- UI requests executed on behalf of a business user interacting with the CAP backend service. +- Backend processing that utilizes platform services on behalf of the technical user of the subscriber tenant. +- Asynchronously received messages that process data on behalf of the technical user of a subscriber tenant. +- Background tasks that operate on behalf of the technical provider tenant. +- etc. + +Find more details about how to [switch the user context](#switching-users) during request processing. + + +### Roles { #roles} + +As a basis for access control, you can design application specific CAP roles which are assigned to users at application runtime. +**A CAP role should reflect _how_ a user can interact with the application at an operational level**, rather than a fine-grained event at a purely technical level. + +```cds +annotate Issues with @(restrict: [ +    { grant: ['READ','WRITE'], +      to: 'ReportIssues', +      where: ($user = CreatedBy) }, +    { grant: ['READ'], +      to: 'ReviewIssues' }, + { grant: ['READ', 'WRITE'], + to: 'ManageIssues' } +]); +``` + +For instance, the role `ReportIssues` allows to work with the `Issues` created by the own user, whereas a user with role `ReviewIssues` is only allowed to read `Issues` of any user. + +CAP roles represent basic building blocks for authorization rules that are defined by the application developers who have in-depth domain knowledge. +Independently of that, user administrators combine CAP roles in higher-level policies and assign them to business users in the platform's central authorization management solution. + +Dynamic assignments of roles to users can be done by +- [AMS roles](#roles-assignment-ams) in case of [IAS authentication](./authentication#ias-auth). +- [XSUAA roles](#xsuaa-roles) in case of [XSUAA authentication](./authentication#xsuaa-auth). + +::: tip +CDS-based authorization deliberately avoids technical concepts, such as _scopes_ in _OAuth_, in favor of user roles, which are closer to the business domain of applications. +::: + +#### Pseudo Roles { #pseudo-roles} + +It's frequently required to define access rules that aren't based on an application-specific user role, but rather on the _technical authentication level_ of the request. +For instance, a service should be accessible only for technical users, with or without user propagation. +Such roles are called pseudo roles as they aren't assigned by user administrators, but are added by the runtime automatically on successful authentication, reflecting the technical level: + +
+ +| Pseudo Role | User Type | Technical Indicator | User Name | +|----------------------|-------------|-------------------------------------------------------------|------------------------------------------------------| +| `authenticated-user` | | _successful authentication_ | _derived from the token_ | +| `any` | | | _derived from the token if available or `anonymous`_ | +| `system-user` | _technical_ | _grant type client credential_ | `system` | +| `internal-user` | _technical_ | _grant type client credential and shared identity instance_ | `system-internal` | + +
+ +
+ +| Pseudo Role | User Type | Technical Indicator | User Name | +|----------------------|-------------|-------------------------------------------------------------|------------------------------------------------------| +| `authenticated-user` | | _successful authentication_ | _derived from the token_ | +| `any` | | | _derived from the token if available or `anonymous`_ | +| `system-user` | _technical_ | _grant type client credential_ | `system` | +| `internal-user` | _technical_ | _grant type client credential and shared identity instance_ | `system` | + +
+ +The pseudo-role `system-user` allows you to separate access by business users from _technical_ clients. +Note that this role does not distinguish between any technical clients sending requests to the API. + +Pseudo-role `internal-user` allows to define application endpoints that can be accessed exclusively by the own PaaS tenant on technical level. +In contrast to `system-user`, the endpoints protected by this pseudo-role do not allow requests from any external technical clients. +Hence is suitable for **technical intra-application communication**, see [Security > Application Zone](./overview#application-zone). + +::: warning +All technical clients that have access to the application's XSUAA or IAS service instance can call your service endpoints as `internal-user`. +**Refrain from sharing this service instance with untrusted clients**, for instance by passing services keys or [SAP BTP Destination Service](https://help.sap.com/docs/connectivity/sap-btp-connectivity-cf/create-destinations-from-scratch) instances. +::: + + +### Model References + +The resulting object representation of the user is attached to the current request context and has an impact on the request flow for instance with regards to +- [authorizations](./authorization#restrictions) +- [enriching business data](../domain-modeling#managed-data) with user data +- setting [DB session variables](../db-feature-comparison#session-variables) + +In the CDS model, some of the user properties can be referenced in annotations or static views: + +| User Property | CDS Model Reference | CDS Artifact | +|-------------------------------|---------------------|--------------------| +| Name | `$user` | annotations and static views | +| Attribute | `$user.` | [@restrict](./authorization#user-attrs) | +| Role | `` | [@requires](./authorization#requires) and [@restrict.to](./authorization#restrict-annotation) | + + +## Role Assignment with AMS { #roles-assignment-ams } + +CAP applications that use the [Identity Authentication Service (IAS)](https://help.sap.com/docs/identity-authentication) for authentication also leverage the [Authorization Management Service (AMS)](https://help.sap.com/docs/cloud-identity-services/authorization-management-service) to provide comprehensive authorization. Similar to IAS, AMS is part of the [SAP Cloud Identity Services (SCI)](https://help.sap.com/docs/cloud-identity-services). + +Why is AMS required? Unlike tokens issued by XSUAA, IAS tokens only contain static user information and cannot directly provide CAP roles. +AMS acts as a central service to define access policies that include CAP roles and additional filter criteria for instance-based authorizations in CAP applications. +_Business users_, technically identified by the IAS ID token, can have AMS policies assigned by user administrators. + +::: tip +Authorizations for technical users can't be addressed by AMS policies yet. +::: + +The integration with AMS is provided as an easy-to-use plugin for CAP applications. +At the time of the request, the AMS policies assigned to the request user are evaluated by the CAP AMS plugin, and the CAP roles and filters are applied to the request context accordingly, as illustrated in the diagram: + +![The graphic is explained in the following text.](./assets/ams.png){width="500px" } + +The interaction between the CAP application and AMS (via plugin) is as follows: + +1. IAS-Authentication is performed independently as a pre-step. +2. The plugin injects **user roles and filters** according to AMS policies assigned to the current request user. +3. CAP performs the authorization on the basis of the CDS authorization model and the injected user claims. + + +### Adding AMS Support { .java } + +**AMS is transparent to CAP application code** and can be easily consumed via plugin dependency. + +To enhance your project with AMS, you can make use of CDS CLI tooling: + +```sh +cds add ams +``` + +This automatically adds required configuration for AMS, taking into account the concrete application context (tenant mode and runtime environment etc.). +If required, it also runs the new `cds add ias` command to configure the project for IAS authentication. + +::: details See dependencies added + +::: code-group +```xml [pom.xml] + + 3.7.0 + +``` + +```xml [srv/pom.xml - dependencies] + + + com.sap.cloud.security.ams.client + jakarta-ams + ${sap.cloud.security.ams.version} + + + com.sap.cloud.security.ams.client + cap-ams-support + ${sap.cloud.security.ams.version} + + +``` + +```xml [srv/pom.xml - plugins] + + + com.sap.cds + cds-maven-plugin + + + cds.build + + cds + + + + [...] + build --for ams + + + + + + com.sap.cloud.security.ams.client + dcl-compiler-plugin + ${sap.cloud.security.ams.version} + + + compile + + compile + + + ${project.basedir}/src/main/resources/ams + true + pretty + true + + + + + +``` + +::: + +These libraries integrate into the CAP framework to handle incoming requests. +Based on the user's assigned [policies](#policies), the user's roles are determined and written to the [UserInfo](#reflection) object. +The framework then authorizes the request as usual based on the user's roles. + +::: details Node.js plugin `@sap/ams` added to the project + +```json [package.json] +{ + "devDependencies": { + "@sap/ams": "^3" + } +} +``` + +::: + +The `@sap/ams` plugin provides multiple build-time features: + +- Validate `ams.attributes` annotations for type coherence against the AMS schema. +- Generate policies from the CDS model during the build using a [custom build task](../deployment/custom-builds#custom-build-plugins). +- Generate a deployer application during the build to upload the Data Control Language (DCL) base policies. + +::: tip +In general, AMS provides highly flexible APIs to define and enforce authorization rules at runtime suitable for native Cloud applications. +**In the context of CAP projects, only a limited subset of these APIs is relevant and is offered in a streamlined way via the CAP integration plugins**. +::: + +### Adding AMS Support { .node } + +**AMS is transparent to CAP application code** and can be easily consumed via plugin dependency. + +To enhance your project with AMS, you can make use of CDS CLI tooling: + +```sh +cds add ams +``` + +This automatically adds required configuration for AMS, taking into account the concrete application context (tenant mode and runtime environment etc.). +If required, it also runs the new `cds add ias` command to configure the project for IAS authentication. + +::: details See dependencies added + +```json [package.json] +{ + "dependencies": [ + "@sap/ams": "^3", + "@sap/xssec": "^4" + ], + "devDependencies": [ + "@sap/ams-dev": "^2" +} +``` +::: + +`@sap/ams` integrates into the CAP framework to handle incoming requests. +Based on the user's assigned [policies](#policies), the user's roles are determined to decorate the [user.is](/node.js/authentication#user-is) function with additional roles. +The framework then authorizes the request as usual based on the user's roles. + +For local development, `@sap/ams-dev` needs to compile the DCL files to Data Control Notation (DCN) files in `gen/dcn` which is the machine-readable version of DCL that is required by AMS at runtime. + +Additionally, `@sap/ams` provides multiple build-time features: + +- Validate `ams.attributes` annotations for type coherence against the AMS schema. +- Generate policies from the CDS model during the build using a [custom build task](../deployment/custom-builds#custom-build-plugins). +- Generate a deployer application during the build to upload the Data Control Language (DCL) base policies. + +::: tip +In general, AMS provides highly flexible APIs to define and enforce authorization rules at runtime suitable for native Cloud applications. +**In the context of CAP projects, only a limited subset of these APIs is relevant and is offered in a streamlined way via the CAP integration plugins**. +::: + +### Prepare CDS Model + +On the level of application domain, you can declaratively introduce access rules in the CDS model, enabling higher-level interaction flows with the entire application domain: + - a [CAP role for AMS](#roles-for-ams) can span multiple domain services and entities, providing a holistic perspective on _how a user interacts with the domain data_. + - a [CAP attribute for AMS](#attributes-for-ams) is typically cross-sectional and hence is defined on a domain-global level. + +The CDS model is fully decoupled from AMS policies which are defined on business level on top by external administrators. +Hence, the **rules in the CAP model act as basic building blocks for higher-level businness rules** and therefore should have appropriate granularity. + + +#### CAP Roles for AMS { #roles-for-ams } + +You can define CAP roles in the CDS model as [described before](#roles). + +::: tip +A CAP role describes a **conceptual role on technical domain level** defined by application developers. +In contrast, an AMS policy reflects a coarser-grained **business role on application level** defined by user administrators. +::: + +For instance, you can enhance the bookshop sample by replacing the `admin` role with more fine-grained CAP roles: + +```cds +service AdminService @(requires: ['ManageAuthors', 'ManageBooks']) { + + entity Books @(restrict: [ + { grant: ['READ'], to: 'ManageAuthors' }, + { grant: ['READ', 'WRITE'], to: 'ManageBooks' } ]) + as projection on my.Books; + + entity Authors @(restrict: [ + { grant: ['READ', 'WRITE'], to: 'ManageAuthors' }, + { grant: ['READ'], to: 'ManageBooks' } ]) + as projection on my.Authors; +} +``` + +Role `ManageBooks` allows a user to mange books _for the authors already in sale_, as well as offering new books. +In contrast, users with `ManageAuthors` are allowed to decide which authors' books should be offered, but they do not define the range of books. +Both CAP roles are ready to be used in higher-level [AMS policies](#policies). + +::: tip +You can simply reuse existing CAP roles for AMS. There is no need to modify the CDS model. +::: + +[Learn more about role-based authorizations in CAP](./authorization#restrictions){.learn-more} + + +#### CAP Attributes for AMS { #attributes-for-ams } + +Attributes for AMS offer user administrators an additional layer of flexibility to partition domain entities into smaller, more manageable units for access control. +The domain attributes, which are exposed to user administrators for defining custom filter conditions, must be predefined by the application developer in the CDS model using the `@ams` annotation. + +For example, the instances of entity `Books` can be classified by the associated genre. +Hence, `genre.name` appears to be a suitable AMS attribute value, exposed under the name `Genre`: + +```cds +annotate AdminService.Books with @ams.attributes: { + Genre: (genre.name) +}; +``` + +In general, the `@ams` annotation operates on the entity level. +The value of the AMS attribute needs to point to a single-value property of the target entity (paths are supported). +You need to make use of a compiler expression in order to ensure validity of the value reference. + + +::: tip +Choose attributes exposed to AMS carefully. +The attribute should have cross-sectional semantics in the domain. +::: + +As such attributes are usually shared by multiple entities, it is convenient to add the `@ams`-annotation at the level of a shared aspect as sketched here: + +```cds +@ams.attributes: { + Genre: (genre.name) +} +aspect withGenre { + genre : Association to Genres; +} + +entity Books : withGenre { ... } +``` + +The detailed syntax of `@ams` annotation provides an `attribute` property which might be helpful to decouple the external from the internal name: +```cds +annotate AdminService.Books with @ams.attributes.genre: { + attribute: 'Genre', element: (genre.name) +}; +``` + + +### Prepare Base Policies { #policies } + +CAP roles and attribute filters cannot be directly assigned to business users. +Instead, the application defines AMS base policies that include CAP roles and attributes, allowing user administrators to assign them to users or create custom policies based on them. + +:::tip +AMS policies represent the business-level roles of end users interacting with the application. +Often, they reflect real-world jobs or functions. +::: + +
+ +After the application is built, check the *srv/src/main/resources/ams* folder to see the generated AMS *schema* and a *basePolicies* DCL file in a package called *cap*: + +::: code-group + +``` [srv/src/main/resources] +└─ ams + ├─ cap + │ └─ basePolicies.dcl + └─ schema.dcl +``` + +::: + +
+ +
+After the application is built, check the *ams/dcl* folder to see the generated AMS *schema* and a *basePolicies* DCL file in a package called *cap*: + +::: code-group + +``` [./ams] +└─ dcl + ├─ cap + │ └─ basePolicies.dcl + └─ schema.dcl +``` + +::: +
+ +[Learn more about policy generation](https://sap.github.io/cloud-identity-developer-guide/CAP/cds-Plugin.html#dcl-generation){.learn-more} + + +The generated policies are a good starting point to add manual modifications. + +The generated DCL schema includes all AMS attributes exposed for filtering basically: + +```yaml [/ams/schema.dcl] +SCHEMA { + Genre : String +} +``` + +In the schema you may configure [value help](https://sap.github.io/cloud-identity-developer-guide/Authorization/ValueHelp.html) for the attributes in the [Cockpit UI for AMS](#ams-deployment). + +The generated policies are usually subject to change. +For example, you can rename the policies to reflect appropriate job functions and adjust the referenced CAP roles according to your needs: + +```yaml [/ams/cap/basePolicies.dcl] +POLICY StockManager { + ASSIGN ROLE ManageBooks WHERE Genre IS NOT RESTRICTED; +} + +POLICY ContentManager { + ASSIGN ROLE ManageAuthors; + ASSIGN ROLE ManageBooks; +} +``` + +In contrast to a `StockManager` who is responsible for the books offering, a `ContentManager` additionally makes the author selection. +Optionally, a `StockManager` with CAP role `ManageBooks` may be restricted to specific genres by applying filters prepared in [customized policies](#local-testing). +As a `ContentManager` there is no genre-based restriction based on genres is prepared. + +There are several options for the attribute declarations that have an impact on the effect of filters: + +| Attribute Statement | Description | Attribute Filter | +|:-----------------------:|:--------------------:|:---------------:| +| `WHERE Genre IS NOT RESTRICTED` | Offers `Genre` as filterable attribute in the scope of the role | Filter restriction could be provided in a custom policy, but no filter applied by default (potentially restricted) | +| `WHERE Genre IS RESTRICTED` | Enforces `Genre` as filtered attribute in the scope for the role | Filter restriction must be provided in a custom policy and is applied (restricted) | +| - no defintion - | The role does not offer any attribute for filtering | No restriction filter is applied (unrestricted) | + +::: tip +The attribute statement is in the scope of a dedicated CAP role and filters are applied on matching entites only. +::: + +[Learn more about AMS policies](https://help.sap.com/docs/cloud-identity-services/cloud-identity-services/configuring-authorization-policies){.learn-more} + + +### Local Testing { #local-testing } + +Although the AMS policies are not yet [deployed to the Cloud service](#ams-deployment), you can assign (base) policies to mock users and run locally: + +
+ +```yaml +cds: + security: + mock: + users: + content-manager: // [!code ++] + policies: // [!code ++] + - cap.ContentManager // [!code ++] + stock-manager: // [!code ++] + policies: // [!code ++] + - cap.StockManager // [!code ++] +``` + +
+ +
+ +```json [package.json] +{ + "cds": { + "requires": { + "auth": { + "[development]": { + "kind": "mocked", + "users": { + "content-manager": { // [!code ++:5] + "policies": [ + "cap.ContentManager" + ] + }, + "stock-manager": { // [!code ++:5] + "policies": [ + "cap.StockManager" + ] + } + } + } + } + } +} +``` + +
+ +:::tip +Don't forget to refer to fully qualified policy names including the package name (`cap` in this example). +::: + +Now (re)start the application with + +
+ +```sh +mvn spring-boot:run +``` + +
+ +
+ +```sh +cds watch +``` + +
+ +
+ +and verify in the UI for `AdminService` (`http://localhost:8080/index.html#Books-manage`) that the the assigned policies imply the expected access rules: + +
+ +
+ +You can now verify that the assigned policies enforce the expected access rules: + +
+ +- mock user `content-manager` has full access to `Books` and `Authors`. +- mock user `stock-manager` can _read_ `Books` and `Authors` and can _edit_ `Books` (but _not_ `Authors`). + +For the test scenario, you can define custom policies in pre-defined package `local` which is ignored during [deployment of the policies](#ams-deployment) to the Cloud service and hence will no show up in production. + +Let's add a custom policy `StockManagerFiction` which is based on base policy `cap.StockManager` restricting the assigned users to the genres `Mystery` and `Fantasy`: + +```yaml [/ams/local/customPolicies.dcl] +POLICY StockManagerFiction { + USE cap.StockManager RESTRICT Genre IN ('Mystery', 'Fantasy'); +} +``` + +[Learn more about DCL operators](https://help.sap.com/docs/cloud-identity-services/cloud-identity-services/condition-operators){.learn-more} + +
+ +::: tip +Don't miss to add the policy files in sub folders of `ams` reflecting the namespace properly: Policy `local.StockManagerFiction` is expected to be in a file within directory `/ams/local/`. +::: + +```yaml +cds: + security: + mock: + users: + stock-manager-fiction: // [!code ++] + policies: // [!code ++] + - local.StockManagerFiction // [!code ++] +``` + +You can verify in the UI that mock user `stock-manager-fiction` is restricted to books of genres `Mystery` and `Fantasy`. + +
+ +
+ +::: tip +Don't miss to add the policy files in sub folders of `ams/dcl` reflecting the namespace properly: Policy `local.StockManagerFiction` is expected to be in a file within directory `./ams/dcl/local/`. +::: + +```json [package.json] +{ + "cds": { + "requires": { + "auth": { + "[development]": { + "kind": "mocked", + "users": { + "stock-manager-fiction": { // [!code ++:5] + "policies": [ + "local.StockManagerFiction" + ] + } + } + } + } + } + } +} +``` + +
+ +[Learn more about AMS attribute filters with CAP](https://sap.github.io/cloud-identity-developer-guide/CAP/InstanceBasedAuthorization.html#instance-based-authorization){.learn-more} + + +### Cloud Deployment { #ams-deployment } + +If not done yet, prepare your project Cloud deployment [as eplained before](./authentication#ias-ready). + +Policies can be automatically deployed to the AMS server during deployment of the application by means of AMS deployer script available in `@sap/ams`. + +Enhancing the project with by `cds add ams` automatically adds task e.g. in the MTA for AMS policy deyployment. + +
+ +::: details AMS policy deployer task in the MTA + +::: code-group +```yaml [mta.yaml- deployer task] +- name: bookshop-ams-policies-deployer + type: javascript.nodejs + path: srv/src/gen/policies # Node.js: gen/policies + parameters: + buildpack: nodejs_buildpack + no-route: true + no-start: true + tasks: + - name: deploy-dcl + command: npm start + memory: 512M + requires: + - name: bookshop-ias + [...] +``` + + +```json [srv/src/gen/policies/package.json - deyployer module] +{ + "name": "ams-dcl-content-deployer", + "version": "3.0.0", + "dependencies": { + "@sap/ams": "^3" + }, + [...] + "scripts": { + "start": "npx --package=@sap/ams deploy-dcl" + } +} +``` + +::: + + +Note that the policy deployer task requires a path to a directory structure containing the `ams` root folder with the policies to be deployed. +By default, the path points to `srv/src/gen/policies` which is prepared automatically during build step with the appropriate policy-content copied from `srv/src/main/resources/ams`. +In addition, `@sap/ams` needs to be referenced to add the deployer logic. + +
+ +
+ +::: details AMS policy deployer task in the MTA + +::: code-group +```yaml [mta.yaml - deployer task] +- name: bookshop-ams-policies-deployer + type: javascript.nodejs + path: gen/policies + parameters: + buildpack: nodejs_buildpack + no-route: true + no-start: true + tasks: + - name: deploy-dcl + command: npm start + memory: 512M + requires: + - name: bookshop-ias + [...] +``` + + +```json [gen/policies/package.json - deyployer module] +{ + "name": "ams-dcl-content-deployer", + "version": "3.0.0", + "dependencies": { + "@sap/ams": "^3" + }, + [...] + "scripts": { + "start": "npx --package=@sap/ams deploy-dcl" + } +} +``` + +::: + + +Note that the policy deployer task requires a path to a directory structure containing the `ams/dcl` root folder with the policies to be deployed. +By default, the path points to `gen/policies` which is prepared automatically during build step with the appropriate policy-content copied from `ams/dcl`. +In addition, `@sap/ams` needs to be referenced to add the deployer logic. + +
+ +::: tip +Several microservices sharing the same IAS instance need a common folder structure the deployer task operates on. +It contains the common view of policies applied to all services. +::: + +[Learn more about AMS deployer](https://sap.github.io/cloud-identity-developer-guide/Authorization/DeployDCL.html#ams-policies-deployer-app){.learn-more} + +Now let's deploy and start the application with + +```sh +cds up +``` + +You can now perform the following tasks in the Administrative Console for the IAS tenant (see prerequisites [here](./authentication#ias-admin)): +- Assign (base or custom) policies to IAS users +- Create custom policies + +To create a custom policy with filter restrictions, follow these steps: +1. Select **Applications & Resources** > **Applications**. Pick the IAS application of your project from the list. +2. In **Authorization Policies** select **Create** > **Create Restriction**. Choose an appropriate policy name, e.g. `StockManagerFiction`. +3. Customize the filter conditions for the available AMS attributes +4. Confirm with **Save** + +::: details Create custom AMS policy with filter condition + +![AMS custom policies in Administrative Console](assets/ams-custom-policy.jpg) + +![AMS custom policy filters in Administrative Console](assets/ams-custom-policy-filter.jpg) + +::: + +[Learn more about how to create custom AMS policies](https://help.sap.com/docs/cloud-identity-services/cloud-identity-services/create-authorization-policy){.learn-more} + + +To assign a policy to an IAS user, follow these steps: +1. Select **Applications & Resources** > **Applications**. Pick the IAS application of your project from the list. +2. Switch to tab **Authorization Policies** and select the policy you want to assign +3. In **Assignments**, add the IAS user of the tenant to which the policy should be assigned (you can review the policy definition in **Rules**). + +[Learn more about how to edit custom AMS policies](https://help.sap.com/docs/cloud-identity-services/cloud-identity-services/edit-authorization-policy){.learn-more} + +::: details Assign AMS policy to an IAS user + +![AMS base policies in Administrative Console](assets/ams-base-policies.jpg) + +![AMS policy assignment in Administrative Console](assets/ams-policy-assignment.jpg) + +::: + + +You can log on to the bookshop test application with the test user and check that only books of dedicated genres can be modified. + +[Learn more about AMS policy assignment](https://help.sap.com/docs/cloud-identity-services/cloud-identity-services/assign-authorization-policies) {.learn-more} + + + +### Tracing + +You can verify a valid configfuration of the AMS plugin by the following log output: + +
+ +```sh +c.s.c.s.a.c.AmsRuntimeConfiguration : Configured AmsUserInfoProvider +``` + +
+ +
+ +```sh +[ams] - AMS Plugin loaded. +[ams] - Added AMS middleware after 'auth' middleware. +``` + +
+ +In addition, for detailed analysis of issues, you can set AMS logger to `DEBUG` level: + +
+ +```yaml +logging: + level: + com.sap.cloud.security.ams: DEBUG +``` + +
+ +
+ +```json +{ + "cds": { + "log": { + "levels": { + "ams": "DEBUG" + } + } + } +} +``` + +
+ +which gives you more information about the policy evaluation at request time: + +
+ +```sh +c.s.c.s.a.l.PolicyEvaluationSlf4jLogger : Policy evaluation result: {..., +"unknowns":"[$app.Genre]", "$dcl.policies":"[local.StockManagerFiction]", + ... +"accessResult":"or( eq($app.Genre, "Mystery") eq($app.Genre, "Fantasy") )"}. +``` + +
+ +
+ +```sh +[ams] - Determined potential actions for resource '$SCOPES': stock-manager { + potentialActions: Set(1) { 'stock-manager' }, + policies: [ 'local.StockManagerFiction' ], + ... + } +[ams] - AMS user roles added to user.is: [ 'stock-manager' ] +[ams] - Privilege check for 'stock-manager' on '$SCOPES' was conditional. { + result: 'conditional', + dcn: "$app.genre IN ['Fantasy', 'Mystery']", + policies: [ 'local.StockManagerFiction' ], + ... + } +[ams] - Resulting privileges for READ on AdminService.Books : [ + { + grant: 'READ', + to: [ 'stock-manager' ], + where: "genre.name IN ('Fantasy', 'Mystery')" + } + ] +``` + +
+ +You can add general user information by applying [user tracing](#user-tracing). + +::: tip +It might be useful to investiagte the injected filter conditions by activating the query-trace (logger `com.sap.cds.persistence.sql`). +::: + + +## Role Assignment with XSUAA { #xsuaa-roles } + +Information about roles and attributes can be made available to the XSUAA platform service. +This information enables the respective JWT tokens to be constructed and sent with the requests for authenticated users. + +In particular, the following happens automatically behind-the-scenes upon build: + + +### Generate Security Descriptor + +Derive scopes, attributes, and role templates from the CDS model: + +
+ +```sh +cds add xsuaa +``` + +
+ +
+ +```sh +cds add xsuaa --for production +``` + +
+ +This generates an _xs-security.json_ file: + +::: code-group +```json [xs-security.json] +{ + "scopes": [ + { "name": "$XSAPPNAME.admin", "description": "admin" } + ], + "attributes": [ + { "name": "level", "description": "level", "valueType": "s" } + ], + "role-templates": [ + { "name": "admin", "scope-references": [ "$XSAPPNAME.admin" ], "description": "generated" } + ] +} +``` +::: + +For every role name in the CDS model, one scope and one role template are generated with the exact name of the CDS role. + +::: tip Re-generate on model changes +You can have such a file re-generated via +```sh +cds compile srv --to xsuaa > xs-security.json +``` +::: + +See [Application Security Descriptor Configuration Syntax](https://help.sap.com/docs/HANA_CLOUD_DATABASE/b9902c314aef4afb8f7a29bf8c5b37b3/6d3ed64092f748cbac691abc5fe52985.html) in the SAP HANA Platform documentation for the syntax of the _xs-security.json_ and advanced configuration options. + + +::: warning Avoid invalid characters in your models +Roles modeled in CDS may contain characters considered invalid by the XSUAA service. +::: + +If you modify the _xs-security.json_ manually, make sure that the scope names in the file exactly match the role names in the CDS model, as these scope names will be checked at runtime. + +### Publish Security Descriptor + +If there's no _mta.yaml_ present, run this command: + +```sh +cds add mta +``` + +::: details See what this does in the background… + +1. It creates an _mta.yaml_ file with an `xsuaa` service. +2. The created service added to the `requires` section of your backend, and possibly other services requiring authentication. +::: code-group +```yaml [mta.yaml] +modules: + - name: bookshop-srv + requires: + - bookshop-auth // [!code ++] +resources: + name: bookshop-auth // [!code ++] + type: org.cloudfoundry.managed-service // [!code ++] + parameters: // [!code ++] + service: xsuaa // [!code ++] + service-plan: application // [!code ++] + path: ./xs-security.json # include cds managed scopes and role templates // [!code ++] + config: // [!code ++] + xsappname: bookshop-${org}-${space} // [!code ++] + tenant-mode: dedicated # 'shared' for multitenant deployments // [!code ++] +``` +::: + + +Inline configuration in the _mta.yaml_ `config` block and the _xs-security.json_ file are merged. If there are conflicts, the [MTA security configuration](https://help.sap.com/docs/HANA_CLOUD_DATABASE/b9902c314aef4afb8f7a29bf8c5b37b3/6d3ed64092f748cbac691abc5fe52985.html) has priority. + +[Learn more about **building and deploying MTA applications**.](/guides/deployment/){ .learn-more} + +### Assign Roles in SAP BTP Cockpit { #xsuaa-assign } + +This is a manual step a user administrator would do in SAP BTP Cockpit to setup and assign roles for the application: + +By creating a service instance of the `xsuaa` service, all the roles from the _xs-security.json_ file are already added to your subaccount. +Next, you create a role collection that assigns these roles to your users. + +1. Open the SAP BTP Cockpit. + + > For your trial account, this is: [https://cockpit.hanatrial.ondemand.com](https://cockpit.hanatrial.ondemand.com) + +2. Navigate to your subaccount and then choose *Security* > *Role Collections*. +3. Choose *Create New Role Collection*: + + ![Create role collections in SAP BTP cockpit](./assets/create-role-collection.png) + +4. Enter a *Name* for the role collection, for example `BookshopAdmin`, and choose *Create*. +5. Choose your new role collection to open it and switch to *Edit* mode. +6. Add the `admin` role for your bookshop application (application id `bookshop!a`) to the *Roles* list. +7. Add the email addresses for your users to the *Users* list. +8. Choose *Save* + + +If a user attribute isn't set for a user in the IdP of the SAP BTP Cockpit, this means that the user has no restriction for this attribute. +For example, if a user has no value set for an attribute "Country", they're allowed to see data records for all countries. +In the _xs-security.json_, the `attribute` entity has a property `valueRequired` where the developer can specify whether unrestricted access is possible by not assigning a value to the attribute. + + + +## Developing with CAP Users { #developing-with-users } + +CAP is not tied to any specific authentication method, nor to concrete user information such as that provided by IAS or XSUAA. +Instead, an abstract [user representation](cap-users#claims) is attached to the request which can be used to influence request processing. +For example, both authorization enforcement and domain logic can depend on properties of the the current user. + +::: warning +Avoid writing custom code based on the raw authentication info, as this undermines the decoupling between authentication strategy and your business logic. +::: + +::: tip +**In most casese, there is no need to write custom code dependent on the CAP user - leverage CDS modelling whenever possible**. +::: + + +### Reflection { #reflection } + +
+ +In CAP Java, The CAP user of a request is represented by a [UserInfo](https://www.javadoc.io/doc/com.sap.cds/cds-services-api/latest/com/sap/cds/services/request/UserInfo.html) object that can be retrieved from the [RequestContext](https://www.javadoc.io/doc/com.sap.cds/cds-services-api/latest/com/sap/cds/services/request/RequestContext.html) of a handler in different ways: + +```java +@Before(entity = Books_.CDS_NAME) +public void beforeReadBooks(CdsReadEventContext context) { + UserInfo userInfo = context.getUserInfo(); + String name = userInfo.getName(); + // [...] +} +``` + +or by Spring dependency injection within a handler bean: + +```java +@Autowired +UserInfo userInfo; + +@After(event = CqnService.EVENT_READ) +public void discountBooks(Stream books) { + String name = userInfo.getName(); + // [...] +} +``` + +There is always an `UserInfo` attached to the current `RequestContext`, reflecting any type of [users](#user-types). +The `UserInfo` object is not modifyable, but during request processing, a new `RequestContext` can be spawned and may be accompanied by a [switch of the current user](#switching-users). + + +Depending on the configured [authentication](./authentication) strategy, CAP derives a *default set* of user claims containing the user's name, tenant, attributes and assigned roles: + +| User Property | UserInfo Getter | XSUAA JWT Property | IAS JWT Property | `@restrict`-annotation | +|---------------|-----------------------------------------|-----------------------------|-------------------------|------------------------| +| _Logon name_ | `getName()` | `user_name` | `sub` | `$user` | +| _Tenant_ | `getTenant()` | `zid` | `app_tid` | `$user.tenant` | +| _Attributes_ | `getAttributeValues(String attr)` | `xs.user.attributes.` | All non-meta attributes | `$user.` | +| _Roles_ | `getRoles()` and `hasRole(String role)` | `scopes` | n/a - injected via AMS | String in `to`-clause | + +::: tip +CAP does not make any assumptions on the presented claims given in the token. String values are copied as they are. +::: + +In addition, there are getters to retrieve information about [pseudo-roles](#pseudo-roles): + +| UserInfo method | Description | CAP Role | +|:--------------------|:-------------------------------------------------------------------------------------------------------------------|----------------------| +| `isAuthenticated()` | True if the current user has been authenticated. | `authenticated-user` | +| `isSystemUser()` | Indicates whether the current user has pseudo-role `system-user`. | `system-user` | +| `isInternalUser()` | Indicates whether the current user has pseudo-role `internal-user`. | `internal-user` | +| `isPrivileged()` | Returns `true` if the current user runs in [privileged mode](#switching-to-privileged-user), i.e. is unrestricted. | n/a | + +
+ +
+ +In CAP Node.js, the CAP user of a request is represented by a [`cds.User`](../../node.js/authentication#cds-user) object. +An instance of `cds.User` representing the current principal is available from the current request context in `req.user`. +Similarly, the identifier of the user's tenant is available from `req.tenant`. + +```js +srv.before('READ', srv.entities.Books, req => { + const { user, tenant } = req + // [...] +}) +``` + +In addition to the request context, information about the current user can similarly be retrieved from the global [`cds.context`](../../node.js/events#cds-context), which provides access to the current [`cds.EventContext`](../../node.js/events#cds-event-context): + +```js +const cds = require('@sap/cds') +const { user, tenant } = cds.context +``` + +:::tip +Prefer local req objects in your handlers for accessing event context properties, as each access to `cds.context` happens through [AsyncLocalStorage.getStore()](https://nodejs.org/api/async_context.html#asynclocalstoragegetstore), which induces some minor overhead. +::: + +Setting `cds.context` usually happens in inbound authentication middlewares or in inbound protocol adapters. +During processing, you can set it programmatically or spawn a new root transaction providing a context argument to achieve a [switch of the current user](#switching-users--switching-users-node). + +Depending on the configured [authentication](./authentication) strategy, CAP derives a default set of user claims containing the user's name, tenant, attributes and assigned roles: + +| User Property | UserInfo Getter | XSUAA JWT Property | IAS JWT Property | `@restrict`-annotation | +|---------------|-------------------------------------|-----------------------------|-------------------------|------------------------| +| _Logon name_ | `user.id` | `user_name` | `sub` | `$user` | +| _Tenant_ | `req.tenant` / `cds.context.tenant` | `zid` | `app_tid` | `$user.tenant` | +| _Attributes_ | `attr(attr)` | `xs.user.attributes.` | All non-meta attributes | `$user.` | +| _Roles_ | `is(role)` | `scopes` | n/a - injected via AMS | String in `to`-clause | + +
+ + + + +### Customizing Users { #customizing-users } + +
+ +In most cases, CAP's default mapping to the CAP user will match your requirements, but CAP also allows you to customize the mapping according to specific needs. + +For instance, the logon name as injected by standard XSUAA integration might not be unique if several customer IdPs are connected to the underlying identity service. +Here a combination of `user_name` and `origin` mapped to `$user` might be a feasible solution that you can implement in a custom adaptation. + +This is done by means of a custom [UserInfoProvider](https://www.javadoc.io/doc/com.sap.cds/cds-services-api/latest/com/sap/cds/services/runtime/UserInfoProvider.html) interface that can be implemented as Spring bean as demonstrated in [Registering Global Parameter Providers](../../java/event-handlers/request-contexts#global-providers): + +::: details Sample implementation to override the user name + +```java +@Component +@Order(1) +public class UniqeNameUserInfoProvider implements UserInfoProvider { + + private UserInfoProvider defaultProvider; + + @Override + public UserInfo get() { + ModifiableUserInfo userInfo = UserInfo.create(); + if (defaultProvider != null) { + UserInfo prevUserInfo = defaultProvider.get(); + if (prevUserInfo != null) { + userInfo = prevUserInfo.copy(); + } + } + if (userInfo != null) { + XsuaaUserInfo xsuaaUserInfo = userInfo.as(XsuaaUserInfo.class); + userInfo.setName(xsuaaUserInfo.getEmail() + "/" + + xsuaaUserInfo.getOrigin()); // adapt name + } + + return userInfo; + } + + @Override + public void setPrevious(UserInfoProvider prev) { + this.defaultProvider = prev; + } +} +``` + +::: + +In the example, the `UniqeNameUserInfoProvider` defines an overlay on the default XSUAA-based provider (`defaultProvider`) by leveraging chaining technique (`@Order(1)` ensures proper ordering). +`UserInfo.copy()` returns [`ModifiableUserInfo`](https://www.javadoc.io/doc/com.sap.cds/cds-services-api/latest/com/sap/cds/services/request/ModifiableUserInfo.html) interface which allows arbitrary modifications such as +overriding the user's name by a combination of email and origin. + +::: warning Be very careful when redefining `$user` +The user name is frequently stored with business data (for example, `managed` aspect) and might introduce migration efforts. +Also consider data protection and privacy regulations when storing user data. +::: + +There are multiple reasonable use cases in which user modification is a suitable approach: + +- Injecting or mixing user roles by calling `modifiableUserInfo.addRole(String role)` (In fact this is the base for [AMS plugin](#roles-assignment-ams) injecting user specifc roles). +- Providing calculated attributes used for [instance-based authorization](./authorization#user-attrs) by invoking `modifiableUserInfo.setAttributeValues(String attribute, List values)`. +- Constructing a request user based on forwarded (and trusted) header information, completely replacing default authentication. +- etc. + +[See more examples for custom UserInfoProvider](https://pages.github.tools.sap/cap/docs/java/event-handlers/request-contexts#global-providers){.learn-more} + +
+ +
+ +In most cases, CAP's default mapping to the CAP user will match your requirements, but CAP also allows you to customize the mapping according to specific needs. + +For instance, the logon name as injected by standard XSUAA integration might not be unique if several customer IdPs are connected to the underlying identity service. +Here a combination of `user_name` and `origin` mapped to `$user` might be a feasible solution that you can implement in a custom adaptation. + +This can be done by modifying `cds.middlewares`. +To modify the `cds.context.user` while still relying on existing generic middlewares, a new middleware must be registered after the `auth` middleware. +If you intend to manipulate the `cds.context.tenant` as well, the new middlware must run before `cds.context.model` is set for the current request. + +::: details Sample implementation to override the user id + +```js +cds.middlewares.before = [ + cds.middlewares.context(), + cds.middlewares.trace(), + cds.middlewares.auth(), + function ctx_user (_,__,next) { + const ctx = cds.context + ctx.user.id = ctx.user.attr('origin') + ctx.user.id + next() + }, + cds.middlewares.ctx_model() +] +``` + +::: + +There are multiple reasonable use cases in which user modification is a suitable approach: + +- Overriding user roles by calling `user.roles(roles)`. +- Overriding user attributes and providing calculated attributes used for [instance-based authorization](./authorization#user-attrs) by invoking `user.attr(attributes)`. +- etc. + +::: warning Be very careful when redefining `$user` and customizing `cds.middlewares` +The user name is frequently stored with business data (for example, `managed` aspect) and might introduce migration efforts. +Also consider data protection and privacy regulations when storing user data. +::: + +:::tip Custom Authentication Middleware +In case you require even more control, it is possible to replace the authentication middleware with a fully [Custom Authentication](../../node.js/authentication#custom). +::: + +
+ +### Switching Users { #switching-users } + +
+ +There are a few typical use cases in a (multitenant) application where switching the current user of the request is required. +For instance, the business request on behalf of a named subscriber user needs to reach out to a platform service on behalf of the underlying technical user of the subscriber. + +These scenarios are identified by a combination of the user (*technical* or *named*) and the tenant (*provider* or *subscriber*): + +![A named user can switch to a technical user in the same/subscriber tenant using the systemUser() method. Also, a named user can switch to a technical user in the provider tenant using the systemUserProvider() method. In addition technical users provider/subscriber tenants can switch to technical users on provider/subscriber tenants using the methods systemUserProvider() or systemUser(tenant).](./assets/requestcontext.drawio.svg) + +In CAP Java, the user context can only be modified by explicitly opening an appropriate Request Context which ensures a well-defined scope for the changed settings. +Services might, for example, trigger HTTP requests to external services by deriving the target tenant from the current Request Context. + +The `RequestContextRunner` API offers convenience methods that allow an easy transition from the current Request Context to a derived one according to the concrete scenario. + +| Method | Scenario | +|----------------------|--------------------------------------------------------------------------------------------------------------------------------------| +| `systemUser()` | [Switches](#switching-to-technical-user) to the **technical user** and preserves the tenant from the current user. | +| `systemUserProvider()` | [Switches](#switching-to-provider-tenant) to the **technical user of the provider account**. | +| `systemUser(tenant)` | [Switches](#switching-to-subscriber-tenant) to a **technical user targeting a given subscriber account**. | +| `privilegedUser()` | [Elevates](#switching-to-privileged-user) the current `UserInfo` to by-pass all authorization checks. | +| `anonymousUser()` | [Switches](#switching-to-anonymous-user) to an anonymous user. | + +Named user contexts are only created by the CAP Java framework as initial Request Context based on appropriate authentication information (for example, JWT token) attached to the incoming HTTP request. + +:::tip Note +- It is not possible to switch from technical user to a named user. +- Asynchronous requests to CAP services are always on behalf of a technical user. +::: + +
+ +
+ +There are a few typical use cases in a (multitenant) application, where switching the current user of the request is required. +For instance, the business request on behalf of a named subscriber user needs to reach out to a service on behalf of the subscribers technical user. + +These scenarios are identified by a combination of the user (*technical* or *named*) and the tenant (*provider* or *subscriber*): + +![Typical Scenarios for a User Context Switch](./assets/requestcontext-node.drawio.svg) + +In CAP Node.js, the `cds.context` allows to access the current `cds.EventContext` and enables updating the principal of the context. +The prefered method for switching users and executing code in a different context and for a different principal, is to spawn a new root transaction using [`cds/srv.tx()`](../../node.js/cds-tx#srv-tx). +Providing a `ctx` argument when creating a new root transaction allows switching the user for nested operations. +The `cds.User` class exposes convenience constructors and accessors for specialized `cds.User` instances that represent typical technical principals you may require. + +```js +const newUser = new cds.User({ id: '...', roles: [...], ...}) +await srv.tx ({ user: newUser, tenant: '' }, async tx => { + // Perform operations with a privileged principal +}) +``` + +:::tip +When creating new root transactions in calls to [`cds/srv.tx()`](../../node.js/cds-tx#srv-tx), all properties not specified in the `ctx` argument are inherited from `cds.context`, if set in the current continuation. +::: + +
+ +#### Switching to Technical User {#switching-to-technical-user } + +
+ +![The graphic is explained in the accompanying text.](./assets/nameduser.drawio.svg){width="330px"} + +The incoming JWT token triggers the creation of an initial Request Context with a named user. +Accesses to the database in the OData Adapter as well as the custom `On` handler are executed within tenant1 and authorization checks are performed for user JohnDoe. +An additionally defined `After` handler wants to call out to an external service using a technical user without propagating the named user JohnDoe. +To achieve this, it's required to call `requestContext()` on the current `CdsRuntime` and use the `systemUser()` method to remove the named user from the new Request Context: + +```java +@After(entity = Books_.CDS_NAME) +public void afterHandler(EventContext context){ + runtime.requestContext().systemUser().run(reqContext -> { + // call technical service + }); +} +``` + +
+ +
+ +![The graphic is explained in the accompanying text.](./assets/nameduser-node.drawio.svg){width="330px"} + +The incoming JWT token triggers the creation of an initial `cds.EventContext` with a named user. +Accesses to the database in the OData Adapter as well as the custom `.on` handler are executed within _tenant1_ and authorization checks are performed for user _JohnDoe_. + +In addition, there is an `.after` handler that wants to call out to an external service using a technical user without propagating the named user _JohnDoe_. +To achieve this, you can create a new root transaction using `srv.tx` and use it to connect to the external service from within a new context: + +```js +srv.after('*', srv.entities.Books, async (res, req) => { + await srv.tx({ user: cds.User.privileged }, async tx => { + // call technical service + }) +}) +``` + +
+ +#### Switching to Technical Provider Tenant {#switching-to-provider-tenant } + +
+ +![The graphic is explained in the accompanying text.](./assets/switchprovidertenant.drawio.svg){width="500px"} + +The application offers a bound action in a CDS entity. Within the action, the application communicates with a remote CAP service using an internal technical user from the provider account. +The corresponding `on` handler of the action needs to create a new Request Context by calling `requestContext()`. +Using the `systemUserProvider()` method, the existing user information is removed and the tenant is automatically set to the provider tenant. +This allows the application to perform an HTTP call to the remote CAP service, which is secured using the pseudo-role `internal-user`. + +```java +@On(entity = Books_.CDS_NAME) +public void onAction(AddToOrderContext context){ + runtime.requestContext().systemUserProvider().run(reqContext -> { + // call remote CAP service + }); +} +``` + +
+ +
+ +![The graphic is explained in the accompanying text.](./assets/switchprovidertenant-node.drawio.svg){width="500px"} + +In this scenario the application offers a bound action in a CDS entity. +Within the action, the application communicates with a remote CAP service using a privileged user and the provider tenant. +The corresponding `.on` handler of the action needs to create a new root transaction by calling `srv.tx`. +The user passed to `srv.tx` in the `ctx` attribute will be used as the prinicpal for requests made within the new root transaction. + +```js +srv.on('action', srv.entities.Books, async req => { + const systemUser = new cds.User({ id: 'system', roles: [ 'internal-user' ] }) + await srv.tx({ user: systemUser , tenant: 'provider-tenant' }, async tx => { + // call remote CAP service + }) +}) +``` + +
+ +#### Switching to a Specific Technical Tenant {#switching-to-subscriber-tenant } + +
+ +![The graphic is explained in the accompanying text.](./assets/switchtenant.drawio.svg){width="450px"} + +The application is using a job scheduler that needs to regularly perform tasks on behalf of a certain tenant. +By default, background executions (for example in a dedicated thread pool) aren't associated to any subscriber tenant and user. +In this case, it's necessary to explicitly define a new Request Context based on the subscribed tenant by calling `systemUser(tenantId)`. +This ensures that the Persistence Service performs the query for the specified tenant. + +```java +runtime.requestContext().systemUser(tenant).run(reqContext -> { + return persistenceService.run(Select.from(Books_.class)) + .listOf(Books.class); +}); +``` + +::: warning Resource Bottlenecks in Tenant Looping +Avoid iterating through all subscriber tenants to perform tenant-specific tasks. +Instead, prefer a task-based approach which processes specific subscriber tenants selectively. +::: + +
+ +
+ +![The graphic is explained in the accompanying text.](./assets/switchtenant-node.drawio.svg){width="450px"} + +The application is using [`cds.spawn`](../../node.js/cds-tx#cds-spawn) to regularly perform tasks on behalf of a certain tenant. +By default, operations that are nested within `cds.spawn` will inherit the outer context. +You can explicitly define the context `cds.spawn` should use by passing relevant information in a `ctx` argument. +This enables to ensure that the Persistence Service performs the query for the specified tenant. + +```js +cds.spawn({ user: cds.User.privileged, tenant: 'tenant1', every: '1h' }, async tx => { + await persistenceService.run(SELECT.from(Books)) +}) +``` + +::: warning Resource Bottlenecks in Tenant Looping +Avoid iterating through all subscriber tenants to perform tenant-specific tasks. +Instead, prefer a task-based approach which processes specific subscriber tenants selectively. +::: + +
+ +
+ +#### Switching to Privileged User { #switching-to-privileged-user } + +Application services invoked within custom handlers enforce an authorization on second-layer, which is the preferred behaviour to ensure security by default. +However, in certain situations, you might want to bypass additional authorization checks if the initial request authorization is deemed sufficient. + +Such service calls can be executed on behalf of a privileged user, acting as a superuser without restrictions: +```java +cdsRuntime.requestContext().privilegedUser().run(privilegedContext -> { + assert privilegedContext.getUserInfo().isPrivileged(); + // service calls in this scope pass generic authorization handler +}); +``` + +::: warning +Call application services on behalf of the privileged user only in case the service call is fully independent from the business user's actual restrictions. +::: + +
+ +#### Switching to Anonymous User { #switching-to-anonymous-user } + +
+ +In rare situations you might want to call a public service without sharing information of the current request user. +In this case, user propagation is explicitly prevented. + +Such service calls can be executed on behalf of the anonymous user, acting as a public user without personal user claims: +```java +cdsRuntime.requestContext().anonymousUser().run(privilegedContext -> { + // ... Service calls in this scope pass generic authorization handler + +}); +``` + +
+ +
+ +In rare situations you might want to call a public service without sharing information about the current request user. +In this case, user propagation can explicitly be prevented by running in a context whose principal is the `anonymous` user. + +```js +cds.tx({ user: cds.User.anonymous }, async tx => { + // Perform operations anonymously +}) +``` + +
+ +### User Propagation { #user-propagation } + +
+ +#### Between Threads + +Within the same Request Context, all CAP service calls share the same user information. +By default, the Request Context of the current thread is not shared with spawned threads and hence user information is lost. +If you want to avoid this, you can propagate the Request Context to spawned threads as described [here](https://pages.github.tools.sap/cap/docs/java/event-handlers/request-contexts#threading-requestcontext) and hence the same user context is applied. + +
+ +#### Non-CAP Libraries { #user-token } + +
+ +CAP plugins for IAS and XSUAA store the resolved user information in Spring's [`SecurityContext`](https://docs.spring.io/spring-security/reference/api/java/org/springframework/security/core/context/SecurityContext.html) which contains all relevant authentication information. Hence, library code can rely on standards to fetch the authentication information and restore the user information if needed. + +In addition, the [authentication information](https://www.javadoc.io/doc/com.sap.cds/cds-services-api/latest/com/sap/cds/services/authentication/AuthenticationInfo.html) is stored in the Request Context and can be fetched as sketched here: + +```java +AuthenticationInfo authInfo = context.getAuthenticationInfo(); +JwtTokenAuthenticationInfo jwtTokenInfo = authInfo.as(JwtTokenAuthenticationInfo.class); +String jwtToken = jwtTokenInfo.getToken(); +``` + +
+ +
+ +CAPs generic authentication middlewares for IAS and XSUAA maintain resolved authentication information in the `authInfo` attribute of `cds.context.user`. +For `@sap/xssec`-based authentication strategies (`ias`, `jwt`, and `xsuaa`), `cds.context.user.authInfo` is an instance of `@sap/xssec`'s [`SecurityContext`](https://www.npmjs.com/package/@sap/xssec#securitycontext). +You can retrieve available authentication information for use in a non-CAP library from the `SecurityContext`. + +```js +const authInfo = cds.context.user.authInfo // @sap/xssec SecurityContext +const token = authInfo.token // @sap/xssec Token +const jwtToken = token.jwt // string +``` + +::: warning +The `cds.User.authInfo` property depends on the authentication library that you use. +CAP does not guarantee the content of this property. Use it with caution. +Always pin your dependencies as described in the [best practices](../../node.js/best-practices#deploy). +::: + +
+ +#### Remote Services { #remote-services } + +
+ +Remote APIs can be invoked either on behalf of a named user or a technical user, depending on the callee's specification. +Thus, a client executing a business request within a specific user context might need to explicitly adjust the user propagation strategy. +CAP's [Remote Services](../using-services) offer an easy and declarative way to define client-side representations of remote service APIs. +Such services integrate seamlessly with CAP, managing connection setup, including [authentication and user propagation](../../java/cqn-services/remote-services#configuring-the-authentication-strategy): + +```yaml +cds: + remote.services: + SomeReuseService: + binding: + name: reuse-service-instance + onBehalfOf: systemUserProvider +``` + +The parameter `onBehalfOf` in the binding configuration section allows to define the following *user propagation* strategies: + +- `currentUser` (default): Propagate the user of the current Request Context. +- `systemUser`: Propagate the (tenant-specific) technical user, based on the tenant set in the current Request Context. +- `systemUserProvider`: Propagate the technical user of the provider tenant. + +::: tip +Remote Services configurations with `destination` section support `onBehalfOf` only in case of [IAS App-2-App flows](../../java/cqn-services/remote-services#consuming-apis-from-other-ias-applications). +::: + +[Learn more about Remote Services in CAP Java](../../java/cqn-services/remote-services#remote-services){.learn-more} + +
+ +
+ +CAP's [Remote Services](../using-services) offer an easy and declarative way to define client-side representations of remote service APIs. +Such services integrate seamlessly with CAP, managing connection setup, including [authentication and user propagation](../using-services#authentication-and-authorization-of-remote-services). +Under the hood CAP utilizes the [BTP Destinations](https://help.sap.com/docs/connectivity/sap-btp-connectivity-cf/create-destinations-from-scratch) and [`@sap-cloud-sdk/connectivity`](https://www.npmjs.com/package/@sap-cloud-sdk/connectivity) to do most of the heavy lifting. + +```json +{ + "cds": { + "requires": { + "SomeReuseService": { + "kind": "odata", + "model": "srv/external/SomeReuseService", + "credentials": { + "destination": "some-reuse-service", + "path": "/reuse/odata/api", + } + } + } + } +} +``` + + + +::: tip +Always prefer using [Remote Services](#remote-services) over natively consuming [Cloud SDK](https://sap.github.io/cloud-sdk/). +::: + +
+ +
+ +#### Cloud SDK { #cloud-sdk } + + +On a programmatic level, the CAP runtime integrates with [Cloud SDK](https://sap.github.io/cloud-sdk/) offering an abstraction for connection setup with remote services, including authentication and user propagation. +By default, +- the *tenant* of the current Request Context is propagated under the hood. +- the *user token* is propagated via Spring's [`SecurityContext`](#user-token). +- *user propagation strategy* can be specified with parameter values [`OnBehalfOf`](https://sap.github.io/cloud-sdk/docs/java/features/connectivity/service-bindings#multitenancy-and-principal-propagation). + +::: tip +Prefer using [Remote Services](#remote-services) built on Cloud SDK rather than natively consuming the Cloud SDK. +::: + +[Learn more about Cloud SDK integration in CAP Java](../../java/cqn-services/remote-services#cloud-sdk-integration){.learn-more} + +
+ +## Pitfalls + +- **Don't write custom code against concrete user types of a specific identity service (e.g. XSUAA or IAS)**. +Instead, if required at all, use CAP's user abstraction layer (`UserInfo` in Java or `req.user` in Node.js) to handle user-related logic. + +- **Don't try to propagate named user context in asynchronous requests**. Do not attempt to propagate the context of a named user in asynchronous requests, such as when using the Outbox pattern or Messaging. +Asynchronous tasks are typically executed outside the scope of the original request context, after successful authorization. +Propagating the named user context can lead to inconsistencies or security issues. Instead, use technical users for such scenarios. + +- **Don't mix CAP Roles for business and technical users**. CAP roles should be clearly separated based on their purpose: Business user roles are designed to reflect how end users interact with the application. +Technical user roles are intended for system-level operations, such as background tasks or service-to-service communication. Mixing these roles can lead to confusion and unintended access control issues. + +- **Don't mix AMS Policy level with CAP Role level**. +AMS policies operate at the business level, while CAP roles are defined at the technical domain level. +Avoid mixing these two layers, as this could undermine the clarity and maintainability of your authorization model. + +- **Don't expose non-cross-sectional entity attributes as AMS Attributes**. +When defining AMS attributes, ensure that only cross-sectional attributes are exposed. +These attributes should have a broad, domain-wide relevance and be applicable across multiple entities. +Typically, only a limited number of attributes (fewer than 5) meet this criterion. +Exposing entity-specific attributes as AMS attributes can lead to unnecessary complexity and reduced reusability. + diff --git a/guides/security/overview.md b/guides/security/overview.md index 249401071a..34cab58985 100644 --- a/guides/security/overview.md +++ b/guides/security/overview.md @@ -1,142 +1,99 @@ --- synopsis: > - This section provides an overview about the security architecture of CAP applications on different platforms. + This section provides an overview about the security concepts and architecture of CAP applications on different platforms. status: released uacp: Used as link target from SAP Help Portal at https://help.sap.com/products/BTP/65de2977205c403bbc107264b8eccf4b/9186ed9ab00842e1a31309ff1be38792.html --- -# Platform Security +# Overview {{ $frontmatter.synopsis }} [[toc]] -## Platform Compliance { #platform-compliance } +## Key Concepts { #key-concepts } -CAP applications run in a certain environment, that is, in the context of some platform framework that has specific characteristics. -The underlying framework has a major impact on the security of the application, -regardless of whether it runs a [cloud](#cloud) environment or [local](#local) environment. -Moreover, CAP applications are tightly integrated with [platform services](#btp-services), in particular with identity and persistence service. +CAP's security architecture is built on several fundamental principles that enable flexible, secure, and maintainable applications. +These concepts work together to provide comprehensive security while maintaining developer productivity and operational efficiency. -::: warning End-to-end security necessarily requires compliance with all security policies of all involved components. -CAP application security requires consistent security configuration of the underlying platform and all consumed services. Consult the relevant security documentation accordingly. -::: +### Pluggable Building Blocks { #key-concept-pluggable } -### CAP in Cloud Environment { #cloud } - -Currently, CAP supports to run on two cloud runtimes of [SAP Business Technology Platform](https://help.sap.com/docs/btp): - -- [SAP BTP, Cloud Foundry Runtime](https://help.sap.com/docs/btp/sap-business-technology-platform/cloud-foundry-environment) -- [SAP BTP, Kyma Runtime](https://help.sap.com/docs/btp/sap-business-technology-platform/kyma-environment) - -Application providers are responsible to ensure a **secure platform environment**. -In particular, this includes *configuring* [platform services](#btp-services) the application consumes. -For instance, the provider (user) administrator needs to configure the [identity service](#identity-service) to separate platform users from business users that come from different identity providers. -Likewise login policies (for example, multifactor authentication or single-sign-on) need to be aligned with company-specific requirements. - -Note, that achieving production-ready security requires to meet all relevant aspects of the **development process** as well. -For instance, source code repositories need to be protected and may not contain any secrets or personal data. -Likewise, the **deployment process** needs to be secured. That includes not only setting up CI/CD pipelines running on technical platform users, but also defining integration tests to ensure properly secured application endpoints. - -As part of **secure operations**, application providers need to establish a patch and vulnerability management, as well as a secure support process. For example, component versions need to be updated and credentials need to be rotated regularly. - -::: warning -The application provider is responsible to **develop, deploy, and operate the application in a secure platform environment**. -CAP offers seamless integration into platform services and tools to help to meet these requirements. -::: - -Find more about BTP platform security here: - -[SAP BTP Security](https://help.sap.com/docs/btp/sap-business-technology-platform/security-e129aa20c78c4a9fb379b9803b02e5f6){.learn-more} -[SAP BTP Security Recommendations](https://help.sap.com/docs/btp/sap-btp-security-recommendations-c8a9bb59fe624f0981efa0eff2497d7d/sap-btp-security-recommendations){.learn-more} -[SAP BTP Security (Community)](https://pages.community.sap.com/topics/btp-security){.learn-more} - - -
- -### CAP in Local Environment { #local } - -Security not only plays a crucial role in [cloud](#cloud) environments, but also during local development. -Apparently the security requirements are different from cloud scenario as local endpoints are typically not exposed for remote clients. -But there are still a few things to consider because exploited vulnerabilities could be the basis for attacks on productive cloud services: - -- Make sure that locally started HTTP endpoints are bound to `localhost`. -- In case you run your service in hybrid mode with bindings to cloud service instances, -use [cds bind](../../advanced/hybrid-testing) instead of copying bindings manually to `default-env.json` file. -`cds bind` avoids materialization of secrets to local disc, which is inherently dangerous. -- Don't write sensitive data to application logs, also not via debug logging. -- Don't test with real business data, for example, copied from a productive system. +CAP divides the different security-related tasks into separate and independent building blocks, each with a standard CAP implementation suitable for most scenarios: +![Overview Security Components with CAP](./assets/security-components.drawio.svg){width="700px" } -### SAP BTP Services for Security { #btp-services} +- [Authentication](./authentication ) +- [CAP Users](./cap-users) +- [CAP Authorization](./authorization) +- [Remote Authentication](./remote-authentication) -SAP BTP provides a range of platform services that your CAP applications can utilize to meet production-grade security requirements. To ensure the security of your CAP applications, it's crucial to comply with the service level agreement (SLA) of these platform services. *As the provider of the application, you play a key role in meeting these requirements by correctly configuring and using these services.* +**By separating these concerns**, CAP ensures that each security function can be configured and customized independently without affecting other parts of the system, providing maximum flexibility. -::: tip -SAP BTP services and the underlying platform infrastructure hold various certifications and attestations, which can be found under the naming of SAP Cloud Platform in the [SAP Trust Center](https://www.sap.com/about/trust-center/certification-compliance/compliance-finder.html?search=SAP%20Business%20Technology%20Platform%20ISO). -::: +For example, authentication can be delegated to a [separate ingress component](./authentication#fully-auth), while authorization remains within the application service close to the data. -The CAP framework offers flexible APIs that you can integrate with various services, including your custom services. If you replace platform services with your custom ones, it's important to ensure that the service level agreements (SLAs) CAP depends on are still met. +### Customizable { #key-concept-customizable } -The most important services for security offered by the platform: +Due to the plugin-based architecture, **CAP allows standard functions to be modified as required or, if necessary, completely replaced**. +This flexibility is crucial for scenarios where the default methods do not fully meet the requirements of the application. +Moreover, this integration helps to easily incorporate non-CAP and even non-BTP services, thereby providing a flexible and interoperable environment. -[Webcast SAP BTP Cloud Identity and Security Services](https://assets.dm.ux.sap.com/webinars/sap-user-groups-k4u/pdfs/221117_sap_security_webcast_series_sap_btp_cloud_identity_and_security_services.pdf){.learn-more} +![Overview Customizable Components with CAP](./assets/security-customizable.drawio.svg){width="600px" } -#### [SAP Cloud Identity Services - Identity Authentication](https://help.sap.com/docs/IDENTITY_AUTHENTICATION) { #identity-service } +For instance, it is possible to define specific endpoints with a [custom authentication strategy](./authentication#custom-auth). +Likewise, the CAP representation of the request user can be overruled to match additional, application-specific requirements. -The Identity Authentication service defines the user base for (CAP) applications and services, and allows to control access. -Customers can integrate their 3rd party or on-premise identity provider (IdP) and harden security by defining multifactor authentication or by narrowing client IP ranges. -This service helps to introduce a strict separation between platform users (provider) and business users (subscribers), a requirement of CAP. It supports various authentication methods, including SAML 2.0 and [OpenID Connect](https://openid.net/connect/), and allows for the configuration of single sign-on access. +### Built on Best of Breed { #key-concept-platform-services } -[Learn more in the security guide.](https://help.sap.com/docs/IDENTITY_AUTHENTICATION?#discover_task-security){.learn-more} +CAP does not deal with user login flows, password and credential management, user sessions, or any cryptographic logic - **and applications should definitely not do so!** +Instead, **CAP seamlessly integrates with battle-tested [platform services](#btp-services)** that handle these critical security topics centrally. +This approach not only simplifies the implementation but also enhances security by leveraging robust, well-tested mechanisms provided by the platform. +Built on platform services, CAP allows developers to focus on core application functionality without worrying about the intricacies of security implementation. -#### [SAP Authorization and Trust Management Service](https://help.sap.com/docs/CP_AUTHORIZ_TRUST_MNG) +Most notably, authentication is covered by CAP-integration of [platform's identity services](./authentication#ias-auth). +Likewise, TLS termination is offered by the [platform infrastructure](#platform-environment). -The service lets customers manage user authorizations in technical roles at application level, which can be aggregated into business-level role collections for large-scale cloud scenarios. -Obviously, developers must define application roles carefully as they form basic access rules to business data. +![Overview Platform Integration with CAP](./assets/security-platform-integration.drawio.svg){width="600px" } -#### [SAP Malware Scanning Service](https://help.sap.com/docs/MALWARE_SCANNING) +### Decoupled from Business Logic { #key-concept-decoupled-coding } -This service can be used to scan transferred business documents for malware and viruses. -Currently, there is no CAP integration. A scan needs to be triggered by the business application explicitly. +As security functions are factorized into independent components, **application code is entirely decoupled** and hence is not subject to change in case of any security-related adaptions. +This ensures that business logic remains independent of platform services, which are often subject to security-hardening initiatives. +As a welcome side effect, this also allows testing application security in a **local test or development setup in a self-contained way**. -[Learn more in the security guide.](https://help.sap.com/docs/btp?#operate_task-security){.learn-more} +For instance, CAP allows performing outbound service calls via [Remote Services while handling authentication completely under the hood](./remote-authentication#remote-services). +This abstraction layer ensures that developers do not need to worry about the details of authentication. -#### [SAP Credential Store](https://help.sap.com/docs/CREDENTIAL_STORE) -Credentials managed by applications need to be stored in a secure way. -This service provides a REST API for (CAP) applications to store and retrieve credentials at runtime. +### Secure by Default { #key-concept-secure-by-default } -[Learn more in the security guide.](https://help.sap.com/docs/CREDENTIAL_STORE?#discover_task-security){.learn-more} +CAP security features are activated by default. If different behaviour is required, you must explicitly reconfigure or add custom code accordingly. +CAP's security autoconfiguration approach significantly reduces the risk of misconfiguration - **override only when absolutely necessary and when all effects are safely controlled**. -#### [SAP BTP Connectivity](https://help.sap.com/docs/CP_CONNECTIVITY) +For instance, endpoints of deployed CAP applications are [automatically authenticated](./authentication#model-auth), providing a secure baseline. +Making endpoints public requires manual configuration in either the CAP model or the middleware. -The connectivity service allows SAP BTP applications to securely access remote services that run on the Internet or on-premise. -It provides a way to establish a secure communication channel between remote endpoints that are connected via an untrusted network infrastructure. -[Learn more in the security guide.](https://help.sap.com/docs/CP_CONNECTIVITY/cca91383641e40ffbe03bdc78f00f681/cb50b6191615478aa11d2050dada467d.html){.learn-more} -## Architecture and Platform Requirements +## Security Architecture -As [pointed out](#platform-compliance), CAP cloud applications run in a specific context that has a major impact on the security [architecture](#architecture-overview). +CAP applications run in a specific context that has a major impact on the security [architecture](#architecture-overview). CAP requires a dedicated [platform environment](#platform-environment) to integrate with, in order to ensure end-to-end security. ### Architecture Overview { #architecture-overview } -The following diagram provides a high-level overview about the security-relevant aspects of a deployed CAP application in a cloud environment: +The following diagram provides a high-level overview about the security-relevant components and interfaces of a deployed CAP application in a cloud environment: - ![This TAM graphic is explained in the accompanying text.](./assets/cap-security-architecture-overview.png){width="600px"} -To serve a business request, different runtime components are involved: a request, issued by a UI or technical client ([public zone](#public-zone)), is forwarded by a gateway or ingress router to the CAP application. In case of a UI request, an [Application Router](https://help.sap.com/docs/btp/sap-business-technology-platform/application-router) instance acts as a proxy. The CAP application might make use of a CAP sidecar. All application components ([application zone](#application-zone)) might make use of platform services such as database or identity service ([platform zone](#platform-zone)). +To serve a business request, different runtime components are involved: a request, issued by a UI or technical client ([public zone](#public-zone)), is forwarded by a gateway or ingress router to the CAP application. In case of a UI request, an [Application Router](https://help.sap.com/docs/btp/sap-business-technology-platform/application-router) instance acts as a proxy to manage the login flow and the browser session. The CAP application can have additional services such as a CAP sidecar. All application components ([application zone](#application-zone)) might make use of platform services such as database or identity service ([platform zone](#platform-zone)). #### Public Zone { #public-zone } From CAP's point of view, all components without specific security requirements belong to the public zone. Therefore, you shouldn't rely on the behavior or structure of consumer components like browsers or technical clients for the security of server components. The platform's gateway provides a single point of entry for any incoming call and defines the API visible to the public zone. -As malicious users have free access to the public zone, these endpoints need to be protected carefully. +Since malicious users have free access to the public zone, you must protect these endpoints carefully. Ideally, you should limit the number of exposed endpoints to a minimum, perhaps through proper network configuration. #### Platform Zone { #platform-zone } @@ -149,30 +106,26 @@ The platform zone also includes the gateway, which is the main entry point for e #### Application Zone { #application-zone} -The application zone comprises all microservices that represent a CAP application. They are tightly integrated and form a unit of trust. The application provider is responsible to *develop, deploy and operate* these services: +The application zone comprises all microservices that represent a CAP application. They are tightly integrated and form a **unit of trust**. The application provider is responsible to *develop, deploy and operate* these services: -- The [Application Router](https://help.sap.com/docs/btp/sap-business-technology-platform/application-router) acts as as an optional reverse proxy wrapping the application service and providing business-independent functionality required for UIs. +- The [Application Router](https://help.sap.com/docs/btp/sap-business-technology-platform/application-router) acts as an optional reverse proxy wrapping the application service and providing business-independent functionality required for UIs. This includes serving UI content, providing a login flow as well as managing the session with the browser. -It can be deployed as application (reusable module) or alternatively consumed as a [service](https://help.sap.com/docs/btp/sap-business-technology-platform/managed-application-router). +It can be deployed as an application (reusable module) or alternatively consumed as a [service](https://help.sap.com/docs/btp/sap-business-technology-platform/managed-application-router). - The CAP application service exposes the API to serve business requests. Usually, it makes use of lower-level platform services. As built on CAP, a significant number of security requirements is covered either out of the box or by adding minimal configuration. - The optional CAP sidecar (reusable module) is used to outsource application-independent tasks such as providing multitenancy and extension support. -Application providers, that is platform users, have privileged access to the application zone. -In contrast, application subscribers, that is business users, are restricted to a minimal interface. +Application providers (platform users) have privileged access to the application zone. +In contrast, application subscribers (business users) are restricted to a minimal interface. ::: warning -❗ Application providers **may not share any secrets from the application zone** such as binding information with other components or persons. -In a productive environment, it is recommended to deploy and operate the application on behalf of a technical user. -::: - -::: tip -Without limitation of generality, there may be multiple CAP services or sidecars according to common [microservice architecture pattern](https://microservices.io/patterns/microservices.html). +❗ Application providers **must not share any secrets from the application zone** such as binding information with other components or persons. +In a production environment, we recommend deploying and operating the application on behalf of a technical user. ::: -### Required Platform Environment { #platform-environment } +### Platform Requirements { #platform-environment } There are several assumptions that a CAP application needs to make about the platform environment it is deployed to: @@ -180,18 +133,126 @@ There are several assumptions that a CAP application needs to make about the pla Hence, the **CAP application can offer a pure HTTP endpoint** without having to enforce TLS and to deal with certificates. 2. The server certificates presented by the external endpoints are signed by a trusted certificate authority. -This **frees CAP applications from the need to manage trust certificates**. The underlying runtimes (Java or Node) can validate the server certificates by default. +This **frees CAP applications from the need to manage trust certificates**. The underlying runtimes (Java or Node.js VMs) can validate the server certificates by default. -3. **Secrets** that are required to protect the application or to consume other platform services **are injected by the platform** into the application in a secure way. +3. **Secrets** that are required to protect the application or to consume other platform services **are injected by the platform** into the application microservices in a secure way. All supported [environments](overview#cloud) fulfill the given requirements. Additional requirements could be added in future. ::: tip -Custom domain certificates need to be signed by trusted certificate authority. +Custom domain certificates must be signed by a trusted certificate authority. +::: + +::: warning +❗ **In general, application endpoints are visible to public zone**. Hence, CAP applications need to protect all exposed endpoints. ::: + +## Platform Compliance { #platform-compliance } + +CAP applications run in a certain environment, that is, in the context of some platform framework that has specific characteristics as explained [before](#platform-environment). +The underlying framework has a major impact on the security of the application, +regardless of whether it runs a [cloud environment](#cloud) or [local environment](#local). +Moreover, CAP applications are tightly integrated with [platform services](#btp-services), in particular with identity and persistence service. + +::: warning ❗ End-to-end security necessarily requires compliance with all security policies of all involved components +CAP application security requires consistent security configuration of the underlying platform and all consumed services. Consult the relevant security documentation accordingly. +::: + +### CAP in Local Environment { #local } + +Security not only plays a crucial role in [cloud environments](#cloud), but also during local development. +Apparently the security requirements are different from cloud scenario as local endpoints are typically not exposed for remote clients. +But there are still a few things to consider because exploited vulnerabilities could be the basis for attacks on productive cloud services: + +- Make sure that locally started HTTP endpoints are bound to `localhost`. +- In case you run your service in hybrid mode with bindings to cloud service instances, +use [cds bind](../../advanced/hybrid-testing) instead of copying bindings manually to `default-env.json` file. +`cds bind` avoids materialization of secrets to local disc, which is inherently dangerous. +- Don't write sensitive data to application logs, also not via debug logging. +- Don't test with real business data, for example, copied from a productive system. + + +### CAP in Cloud Environment { #cloud } + +Currently, CAP supports to run on two cloud runtimes of [SAP Business Technology Platform](https://help.sap.com/docs/btp): + +- [SAP BTP, Cloud Foundry Runtime](https://help.sap.com/docs/btp/sap-business-technology-platform/cloud-foundry-environment) +- [SAP BTP, Kyma Runtime](https://help.sap.com/docs/btp/sap-business-technology-platform/kyma-environment) + +Application providers are responsible to ensure a **secure platform environment**. +In particular, this includes *configuring* [platform services](#btp-services) the application consumes. +For instance, the provider (user) administrator needs to configure the [identity service](#identity-service) to separate platform users from business users that come from different identity providers. +Likewise, login policies (for example, multifactor authentication or single-sign-on) must be aligned with company-specific requirements. + +Note, that achieving production-ready security requires to meet all relevant aspects of the **development process** as well. +For instance, source code repositories must be protected and must not contain any secrets or personal data. +Likewise, the **deployment process** must be secured. This includes not only setting up CI/CD pipelines running on technical platform users, but also defining integration tests to ensure properly secured application endpoints. + +As part of **secure operations**, application providers must establish patch and vulnerability management, as well as a secure support process. For example, component versions must be updated and credentials must be rotated regularly. + ::: warning -❗ **In general, application endpoints are visible to public zone**. Hence, CAP can't rely on private endpoints. -In particular, an application router does not prevent external access to the CAP application service. -As a consequence, **all CAP endpoints must be protected in an appropriate manner**. +The application provider is responsible to **develop, deploy, and operate the application in a secure platform environment**. +CAP offers seamless integration into platform services and tools to help to meet these requirements. +::: + +Find more about BTP platform security here: + +[SAP BTP Security](https://help.sap.com/docs/btp/sap-business-technology-platform/security-e129aa20c78c4a9fb379b9803b02e5f6){.learn-more} +[SAP BTP Security Recommendations](https://help.sap.com/docs/btp/sap-btp-security-recommendations-c8a9bb59fe624f0981efa0eff2497d7d/sap-btp-security-recommendations){.learn-more} +[SAP BTP Security (Community)](https://pages.community.sap.com/topics/btp-security){.learn-more} + + +
+ + + +### Security Platform Services { #btp-services } + +SAP BTP provides a range of platform services that your CAP applications can utilize to meet production-grade security requirements. To ensure the security of your CAP applications, it's crucial to comply with the service level agreement (SLA) of these platform services. *As the provider of the application, you play a key role in meeting these requirements by correctly configuring and using these services.* + +::: tip +SAP BTP services and the underlying platform infrastructure hold various certifications and attestations, which can be found under the naming of SAP Cloud Platform in the [SAP Trust Center](https://www.sap.com/about/trust-center/certification-compliance/compliance-finder.html?search=SAP%20Business%20Technology%20Platform%20ISO). ::: +[Webcast SAP BTP Cloud Identity and Security Services](https://assets.dm.ux.sap.com/webinars/sap-user-groups-k4u/pdfs/221117_sap_security_webcast_series_sap_btp_cloud_identity_and_security_services.pdf){.learn-more} + + +The CAP framework offers flexible APIs that you can integrate with various services, including your custom services. If you replace platform services with your custom ones, it's important to ensure that the service level agreements (SLAs) CAP depends on are still met. + +The most important services for security offered by the platform: + +#### [SAP Cloud Identity Services - Identity Authentication](https://help.sap.com/docs/IDENTITY_AUTHENTICATION) { #identity-service } + +The Identity Authentication service defines the user base for (CAP) applications and services, and allows to control access. +Customers can integrate their 3rd party or on-premise identity provider (IdP) and harden security by defining multifactor authentication or by narrowing client IP ranges. +This service helps to introduce a strict separation between platform users (provider) and business users (subscribers), a requirement of CAP. It supports various authentication methods, including SAML 2.0 and [OpenID Connect](https://openid.net/connect/), and allows for the configuration of single sign-on access. + +[Learn more in the security guide.](https://help.sap.com/docs/IDENTITY_AUTHENTICATION?#discover_task-security){.learn-more} + +#### [SAP Authorization and Trust Management Service](https://help.sap.com/docs/CP_AUTHORIZ_TRUST_MNG) + +The service allows customers to manage user authorizations in technical roles at the application level, which can be aggregated into business-level role collections for large-scale cloud scenarios. +Developers must define application roles carefully as they form the basic access rules for business data. + +[Learn more in the security guide.](https://help.sap.com/docs/btp/sap-business-technology-platform/btp-security){.learn-more} + +#### [SAP BTP Connectivity](https://help.sap.com/docs/CP_CONNECTIVITY) + +The connectivity service allows SAP BTP applications to securely access remote services that run on the Internet or on-premise. +It provides a way to establish a secure communication channel between remote endpoints that are connected via an untrusted network infrastructure. + +[Learn more in the security guide.](https://help.sap.com/docs/CP_CONNECTIVITY/cca91383641e40ffbe03bdc78f00f681/cb50b6191615478aa11d2050dada467d.html){.learn-more} + +#### [SAP Malware Scanning Service](https://help.sap.com/docs/MALWARE_SCANNING) + +This service scans transferred business documents for malware and viruses. +Currently, there is no CAP integration. A scan must be triggered explicitly by the business application. + +[Learn more in the security guide.](https://help.sap.com/docs/btp?#operate_task-security){.learn-more} + +#### [SAP Credential Store](https://help.sap.com/docs/CREDENTIAL_STORE) + +Credentials managed by applications must be stored securely. +This service provides a REST API for (CAP) applications to store and retrieve credentials at runtime. + +[Learn more in the security guide.](https://help.sap.com/docs/CREDENTIAL_STORE?#discover_task-security){.learn-more} diff --git a/guides/security/remote-authentication.md b/guides/security/remote-authentication.md new file mode 100644 index 0000000000..496a23eea4 --- /dev/null +++ b/guides/security/remote-authentication.md @@ -0,0 +1,448 @@ +--- +# layout: cookbook +label: Remote Authentication +synopsis: > + This guide explains how to authenticate remote services. +status: released +--- + + + + + +# Remote Authentication { #remote-authentication } + + + +This guide explains how to authenticate remote services. + +[[toc]] + +## Remote Service Abstraction { #remote-services } + +According to the key concept of [pluggable building blocks](./overview#key-concept-pluggable), the architecture of CAP's [Remote Services](../using-services#consuming-services) decouples protocol level (i.e., exchanged content) from connection level (i.e., established connection channel). +While the business context of the application impacts the protocol, the connectivity of the service endpoints is independent of it and mainly depends on platform-level capabilities. +The latter is frequently subject to change and therefore should not introduce application dependencies. + +![Remote Service stack architecture](./assets/remote-service-stack.drawio.svg){width="400px" } + +At the connectivity layer, the following basic tasks can be addressed generically: +- Authentication (_how to set up a trusted channel_) +- Destination (_how to find the target service_) +- User propagation (_how to transport user information_) + +CAP's connectivity component handles authentication (IAS, XSUAA, X.509, ZTID, ...), destination (local destination, BTP Destination, BTP Service Binding), and user propagation (technical provider, technical subscriber, named user) transparently through configuration. +All three service scenarios can be addressed through configuration variants of the same remote service concept, as shown in the following sections. + +CAP supports out-of-the-box consumption of various types of [remote services]( #remote-services): + +* [Co-located services](#co-located-services) as part of the same deployment and bound to the same identity instance (i.e., belong to the same trusted [application zone](./overview#application-zone)). +* [External services](#app-to-app) which can be running on non-BTP platforms. +* [BTP reuse services](#ias-reuse) consumed via service binding. + + +## Co-located Services {#co-located-services} + +Co-located services do not run in the same microservice, but are typically part of the same deployment unit and hence reside within the same trust boundary of the [application zone](./overview#application-zone). +Logically, such co-located services contribute to the application equally and could run as integrated services in the same microservice, but for technical reasons (e.g., different runtime or scaling requirements) they are separated physically, often as a result of a [late-cut microservice approach](../providing-services#late-cut-microservices). + +Technically, **they share the same identity instance, which allows direct token forwarding**: + +![Co-located services](./assets/co-located-services.drawio.svg){width="450px" } + +[Learn more about how to configure co-located services in CAP Java](/java/cqn-services/remote-services#binding-to-a-service-with-shared-identity) {.learn-more} + +You can test CAP's built-in support for co-located services in practice by modifying the [`xflights-java`](https://github.com/capire/xflights-java/tree/main) and [`xtravels-java`](https://github.com/capire/xtravels-java/tree/main) sample applications. +`xflights-java` acts as a master data provider exposing basic flight data in service [`sap.capire.flights.data`](https://github.com/capire/xflights-java/blob/6fc7c665c63bb6d73e28c11b391b1ba965b8772c/srv/data-service.cds#L24) via different protocols. +On the client side, `xtravels-java` imports this service as a CAP remote service and fetches data in a [custom handler for data federation](https://github.com/capire/xtravels-java/blob/53a5fa33caf4c9068f2e66fab25bda26f3f450ca/srv/src/main/java/sap/capire/xtravels/handler/FederationHandler.java#L63). + +::: tip +CAP offers a simplified co-located service setup by leveraging remote services that require: +- Shared identity instance +- URL for the destination +- Principal propagation mode (optional) +::: + + +To combine both applications in a co-located setup, follow these steps: + +#### 1. Prepare the CF environment { #prepare } + +Make sure that you've prepared a [local environment for CF deployments](../deployment/to-cf#prerequisites) and in addition: +- A Cloud Foundry (CF) space in a subaccount. +- [HANA Cloud instance](https://help.sap.com/docs/hana-cloud/sap-hana-cloud-administration-guide/create-sap-hana-database-instance-using-sap-hana-cloud-central) mapped to the CF space. +- [IAS tenant](./authentication#ias-ready) mapped to the subaccount. + + +#### 2. Prepare and deploy the consumer application { #co-located-consumer } + +As client, `xtravels-srv` first needs a valid configuration for the remote service `sap.capire.flights.data`: + +::: code-group + +```yaml [/srv/src/main/resources/application.yaml] +--- +spring: + config.activate.on-profile: cloud +cds: + remote.services: + xflights: + type: hcql + model: sap.capire.flights.data + binding: + name: xtravels-ias + options: + url: https:///hcql + onBehalfOf: systemUserProvider +``` +::: + +The `type` property activates the protocol for exchanging business data and must be offered by the provider [CDS service](https://github.com/capire/xflights-java/blob/6fc7c665c63bb6d73e28c11b391b1ba965b8772c/srv/data-service.cds#L24). +The `model` property needs to match the fully qualified name of the CDS service from the imported model. +You can find CDS service definition of `sap.capire.flights.data` in file `target/cds/capire/xflight-data/service.cds` resolved during CDS build step. +The `binding.name` needs to point to the shared identity instance and `option.url` provides the required location of the remote service endpoint. +Finally, `onBehalfOf: systemUserProvider` specifies that the remote call is invoked on behalf of the technical provider tenant. + + +Now you are ready to deploy the application with + +```sh +cd ./xtravels_java +cds up +``` + +❗Note that CF application `xtravels-srv` will not start successfully as long as `xflights` is not deployed yet (step 3). + +::: tip +For production deployment, it is recommended to combine both services with the shared identity instance in a [single MTA descriptor](../deployment/microservices#all-in-one-deployment). +::: + + +#### 3. Prepare and deploy the provider application { #co-located-provider } + +As server, `xflights-srv` needs to restrict service `sap.capire.flights.data` to the technical client calling from of the same application. +This can be done by adding pseudo-role [`internal-user`](./cap-users#pseudo-roles) to the service: + +::: code-group +```cds [/srv/authorization.cds] +using { sap.capire.flights.data as data } from './data-service'; + +annotate data with @(requires: 'internal-user'); +``` +::: + +::: tip +For different [user propagation](./cap-users#remote-services) modes the remote service can be configured appropriately. +The provider service authorization needs to align with the configured user propagation. +::: + +Additionally, to establish the co-located setup, the microservice needs to share the same identity instance: + +::: code-group + +```yaml [/srv/srv/main/resources/application.yaml] +resources: + - name: xflights-ias + type: org.cloudfoundry.managed-service // [!code --] + type: org.cloudfoundry.existing-service // [!code ++] + parameters: + service: identity // [!code --] + service-name: xflights-ias // [!code --] + service-name: xtravels-ias // [!code ++] + service-plan: application // [!code --] + config: // [!code --] + display-name: xflights // [!code --] +``` + +::: + +Finally, deploy and start the application with + +```sh +cd ./xflights_java +cds up +``` + +#### 4. Verify the deployment { #verify } + +First, you can check the overall deployment status at the CF CLI level. Specifically, the application services must be started successfully and the shared identity instance must be verified. + +::: details Verify: `cf apps` should show the following lines: + +::: code-group +```sh +name requested state processes routes +xflights-db-deployer stopped web:0/1 +xflights-srv started web:1/1 ... +xtravels started web:1/1 ... +xtravels-ams-policies-deployer stopped web:0/1 +xtravels-db-deployer stopped web:0/1 +xtravels-srv started web:1/1 ... +``` +::: + +::: details Verify: `cf services` should show the following lines: + +::: code-group +```sh +xflights-ias identity application +xtravels-ias identity application xtravels, xtravels-srv, xflights-srv, ... +``` +::: + +You can test the valid setup of the xtravels application by accessing the UI and logging in with an authorized test user of the IAS tenant. +To do so, assign a proper AMS policy (e.g., `admin`) to the test user as described [earlier](./cap-users#ams-deployment). + + +::: tip +The very same setup could be deployed for XSUAA-based services. +::: + + +## External Services + +In contrast to [co-located services](#co-located-services), external services do not have strong dependencies as they have a fully decoupled lifecycle and are provided by different owners. +As a consequence, external services can run cross-regionally; even non-BTP systems might be involved. +A prerequisite for external service calls is a trust federation between the consumer and the provider system. + +A seamless integration experience for external service communication is provided by [IAS App-2-App](#app-to-app) flows, which are offered by CAP via remote services. +Alternatively, remote services can be configured on top of [BTP HTTP Destinations](../using-services#using-destinations) which offer [various authentication strategies](https://help.sap.com/docs/connectivity/sap-btp-connectivity-cf/http-destinations) such as SAML 2.0 as required by many S/4 system endpoints. + + +### IAS App-2-App { #app-to-app } + +As a first-class citizen, [IAS](./authentication#ias-auth) is positioned to simplify cross-regional requests with user propagation. +Prerequisites are identity instances on both consumer and provider sides, plus a registered IAS dependency in the consumer instance. + +![External services](./assets/external-services.drawio.svg){width="500px" } + +CAP supports communication between arbitrary IAS endpoints and remains transparent for applications as it builds on the same architectural pattern of [remote services](#remote-services). +Technically, the connectivity component uses [IAS App-2-App flows](https://help.sap.com/docs/cloud-identity-services/cloud-identity-services/consume-apis-from-other-applications) in this scenario which requires a token exchange from a consumer token into a token for the provider. +The latter is issued by IAS only if the consumer is configured with a valid IAS dependency pointing to the provider accordingly. + +:::tip +CAP offers a simplified App-2-App setup by leveraging remote services that require: +- Identity instances for provider and consumer +- Configured IAS dependency from consumer to provider +- Destination with URL pointing to the provider +- Principal propagation mode (optional) +::: + +[Learn more about how to consume external application APIs with IAS](https://help.sap.com/docs/cloud-identity-services/cloud-identity-services/consume-apis-from-other-applications) {.learn-more} + + + +#### 1. Prepare and deploy the provider application + +Assuming the same local CF environment setup as [here](#prepare), clone [`xflights-java`](https://github.com/capire/xflights-java/tree/main) or, if already cloned and modified locally, reset to the remote branch. + +Similar to the [co-located](#co-located-provider) variant, `xflights` needs to expose service `sap.capire.flights.data` to technical clients. +The difference is that the consumers are not known a priori and are not part of the same application deployment. + +To expose service APIs for consumption, you can enhance the identity instance of the provider by defining API identifiers that are listed in property `provided-apis`: + +::: code-group +```yaml [mta.yaml] +resources: + - name: xflights-ias + type: org.cloudfoundry.managed-service + parameters: + [...] + config: + display-name: xflights + provided-apis: [{ # [!code ++:5] + name: DataConsumer, + description: Grants technical access to data service API + }] +``` +::: + +The entry with name `DataConsumer` represents the consumption of service `sap.capire.flights.data` and is exposed as IAS API. +The description helps administrators to configure the consumer application with the proper provider API if done on UI level. + +[Detailed description about identity instance parameters for `provided-apis`](https://github.wdf.sap.corp/pages/CPSecurity/sci-dev-guide/docs/BTP/identity-broker#service-instance-parameters){.learn-more} + +How can proper authorization be configured for _technical clients without user propagation_? +OAuth tokens presented by valid consumer requests from an App-2-App flow will have API claim `DataConsumer`, which is automatically mapped to a CAP role by the runtime. +Therefore, the corresponding CDS service can be protected by CAP role `DataConsumer` to authorize requests thoroughly: + +::: code-group +```cds [/srv/authorization.cds] +using { sap.capire.flights.data as data } from './data-service'; + +annotate data with @(requires: 'DataConsumer'); +``` +::: + +Finally, deploy and start the application with + +```sh +cd ./xflights_java +cds up +``` + + +::: tip API as CAP role +The API identifiers exposed by the IAS instance in list `provided-apis` are granted as CAP roles after successful authentication and can be used in @requires annotations. +::: + +::: warning Use different roles for technical and business users +Use different CAP roles for technical clients without user propagation and for named business users. + +Instead of using the same role, expose dedicated CDS services to technical clients which aren't accessible to business users and vice versa. +::: + +#### 2. Prepare and deploy the consumer application { #consumer } + +Like with xflights, clone [`xtravels-java`](https://github.com/capire/xtravels-java/tree/main) or, if already cloned and modified locally, reset to remote branch. + +First, a BTP destination needs to be added that points to the provider service endpoint to be called (`URL`) and that contains the information about the IAS dependency to be called (`cloudsdk.ias-dependency-name`). +The name for the IAS dependency is flexible but **needs to match the chosen name in the next step** when [connecting consumer and provider in IAS](#connect). +The destination is required by the connectivity component to prepare the HTTP call accordingly. Also note that the authentication type of the destination is `NoAuthentication`, as the destination itself does not contribute to the authentication process. + + +::: code-group + +```yaml [mta.yaml (destination instance)] + - name: xtravels-destination + type: org.cloudfoundry.managed-service + parameters: + service: destination + service-plan: lite + config: + init_data: + instance: + existing_destinations_policy: update + destinations: + - Name: xtravels-data-consumer + Type: HTTP + URL: https:///hcql + cloudsdk.ias-dependency-name: "DataConsumer" + Authentication: NoAuthentication + ProxyType: Internet + Description: "Data consumer destination for xtravels" +``` + +```yaml [mta.yaml (destination binding)] +modules: + - name: xtravels-srv + type: java + [...] + requires: + - name: xtravels-destination # [!code ++] +``` + +::: + +:::tip +Alternatively, the destination can also be created manually in the [BTP destination editor](https://help.sap.com/docs/connectivity/sap-btp-connectivity-cf/access-destinations-editor). +::: + + +Given the destination, the remote service can be configured in a very similar way as with [co-located services](#co-located-consumer). +Currently, an additional Cloud SDK dependency `scp-cf` is required to support communication with the BTP destination service: + +::: code-group + +```yaml [/srv/srv/main/resources/application.yaml] +spring: + config.activate.on-profile: cloud +cds: + remote.services: + xflights: + type: hcql + model: sap.capire.flights.data + destination: + name: xtravels-data-consumer + onBehalfOf: systemUserProvider +``` + +```xml [/srv/pom.xml] + + com.sap.cloud.sdk.cloudplatform + scp-cf + runtime + +``` + +::: + +[Learn more about simplified Remote Service configuration with destinations](/java/cqn-services/remote-services#destination-based-scenarios) {.learn-more} + +Finally, deploy and start the application with + +```sh +cd ./xtravels_java +cds up +``` + +`xtravels-srv` is not expected to start successfully; instead, you should see error log messages like this: +```yaml +Remote HCQL service responded with HTTP status code '401', ... +``` + +Technically, the remote service implementation will delegate the HTTP connection setup to the connectivity component, which can recognize by the type of destination that it needs to initiate an App-2-App flow. +It then takes the token from the request and triggers an IAS token exchange for the target [IAS dependency](#connect) according to the user propagation strategy (technical communication here). +As the IAS dependency is not created yet, IAS rejects the token exchange request and the call to the provider fails with `401` (not authenticated). + +Note that property `oauth2-configuration.token-policy.access-token-format: jwt` is set in the identity instance to ensure the exchanged token has JWT format. + +#### 3. Connect consumer with provider { #connect } + +Now let's create the missing IAS dependency to establish trust for the API service call targeting provided API with id `DataConsumer`. + +Open the Administrative Console for the IAS tenant (see prerequisites [here](./authentication#ias-admin)): + +1. Select **Applications & Resources** > **Applications**. Choose the IAS application of the `xtravels` consumer from the list. +2. In **Application APIs** select **Dependencies** and click on **Add**. +3. Type a dependency name (needs to match property value `cloudsdk.ias-dependency-name`) and pick provided API `DataConsumer` from the provider IAS application `xflights`. +4. Confirm with **Save** + +::: details Create IAS dependency in Administrative Console + +![Manage IAS dependencies in Administrative Console](assets/ias-dependencies.png) {width="500px" } + +![Create a new IAS dependency in Administrative Console](assets/add-api.png) {width="500px" } + +::: + +:::tip +Both the BTP destination and the IAS dependency can be automatically created at runtime using [UCL integration](../../java/integrating-applications/ucl#unified-customer-landscape-ucl). +::: + +Now restart the consumer application with + +```sh +cf restart xtravels-srv +``` + +to trigger a successful startup with valid flight data retrieved from the provider. + +You can now test the valid setup of the xtravels application by accessing the UI and logging in with an authorized test user of the IAS tenant. +To do so, assign a proper AMS policy (e.g., `admin`) to the test user as described [earlier](./cap-users#ams-deployment). + + +
+ + +## Pitfalls + +- **Don't write custom integration logic** for consumed services. +Leverage CAP's remote service architecture instead to ensure a seamless integration experience. + +- **Don't implement connectivity layer code** (e.g., to fetch or exchange tokens). +Instead, rely on the shared connectivity component, which ensures centralized and generic processing of outbound requests. + +- **Don't treat co-located services as external services**. +This introduces unnecessary communication overhead and increases total cost of ownership (TCO). + + diff --git a/java/_menu.md b/java/_menu.md index 3baf3672bb..bcd07a0355 100644 --- a/java/_menu.md +++ b/java/_menu.md @@ -24,7 +24,6 @@ # [Multitenancy](multitenancy) ## [Multitenancy (Classic)](multitenancy-classic) # [Security](security) - ## [IAS and AMS](../../java/ams) # [Spring Boot Integration](spring-boot-integration) # [Developing Applications](developing-applications/) ## [Building](developing-applications/building) diff --git a/java/event-handlers/request-contexts.md b/java/event-handlers/request-contexts.md index f3d89ed70c..9e234f3b27 100644 --- a/java/event-handlers/request-contexts.md +++ b/java/event-handlers/request-contexts.md @@ -116,74 +116,20 @@ For example, if the request is processed by an HTTP-based protocol adapter, `Par The CAP Java SDK allows you to create new Request Contexts and define their scope. This helps you to control, which set of parameters is used when events are processed by services. -There are a few typical use cases in a CAP-based, multitenant application on SAP BTP in which creation of new Request Contexts is necessary. These scenarios are identified by a combination of the user (technical or named) and the tenant (provider or subscribed). - -![A named user can switch to a technical user in the same/subscriber tenant using the systemUser() method. Also, a named user can switch to a technical user in the provider tenant using the systemUserProvider() method. In addition technical users provider/subscriber tenants can switch to technical users on provider/subscriber tenants using the methods systemUserProvider() or systemUser(tenant).](./assets/requestcontext.drawio.svg) - -When calling CAP Services, it's important to call them in an appropriate Request Context. Services might, for example, trigger HTTP requests to external services by deriving the target tenant from the current Request Context. - -The `RequestContextRunner` API offers convenience methods that allow an easy transition from one scenario to the other. - -| Method | Description | -|----------------------|--------------------------------------------------------------------------------------------------------------------------------------| -| systemUserProvider() | Switches to a technical user targeting the provider account. | -| systemUser() | Switches to a technical user and preserves the tenant from the current `UserInfo` (for example downgrade of a named user Request Context). | -| systemUser(tenant) | Switches to a technical user targeting a given subscriber account. | -| anonymousUser() | Switches to an anonymous user. | -| privilegedUser() | Elevates the current `UserInfo` to by-pass all authorization checks. | - -::: info Note -The [RequestContextRunner](https://www.javadoc.io/doc/com.sap.cds/cds-services-api/latest/com/sap/cds/services/runtime/RequestContextRunner.html) API does not allow you to create a Request Context based on a named user. Named user contexts are only created by the CAP Java framework as initial Request Context is based on appropriate authentication information (for example, JWT token) attached to the incoming HTTP request. -::: - -In the following a few concrete examples are given: -- [Switching to Technical User](#switching-to-technical-user) -- [Switching to Provider Tenant](#switching-to-provider-tenant) -- [Switching to a Specific Technical Tenant](#switching-to-a-specific-technical-tenant) - -### Switching to Technical User - -![The graphic is explained in the accompanying text.](./assets/nameduser.drawio.svg) - -The incoming JWT token triggers the creation of an initial RequestContext with a named user. Accesses to the database in the OData Adapter as well as the custom `On` handler are executed within tenant1 and authorization checks are performed for user JohnDoe. An additionally defined `After` handler wants to call out to an external service using a technical user without propagating the named user JohnDoe. -Therefore, the `After` handler needs to create a new Request Context. To achieve this, it's required to call `requestContext()` on the current `CdsRuntime` and use the `systemUser()` method to remove the named user from the new Request Context: ```java @After(entity = Books_.CDS_NAME) public void afterHandler(EventContext context){ - runtime.requestContext().systemUser().run(reqContext -> { - // call technical service - ... - }); -} -``` -### Switching to Technical Provider Tenant {#switching-to-provider-tenant} - -![The graphic is explained in the accompanying text.](./assets/switchprovidertenant.drawio.svg) - -The application offers an action for one of its CDS entities. Within the action, the application communicates with a remote CAP service using an internal technical user from the provider account. The corresponding `on` handler of the action needs to create a new Request Context by calling `requestContext()`. Using the `systemUserProvider()` method, the existing user information is removed and the tenant is automatically set to the provider tenant. This allows the application to perform an HTTP call to the remote CAP service, which is secured using the pseudo-role `internal-user`. - -```java -@On(entity = Books_.CDS_NAME) -public void onAction(AddToOrderContext context){ - runtime.requestContext().systemUserProvider().run(reqContext -> { - // call remote CAP service - ... + runtime.requestContext() + // [...] prepare the fully context + .run(reqContext -> { + // do service calls }); } ``` -### Switching to a Specific Technical Tenant -![The graphic is explained in the accompanying text.](./assets/switchtenant.drawio.svg) +Most important use case is to switch users, for which CAP Java provides [convenience APIs](../../guides/security/cap-users#switching-users). -The application is using a job scheduler that needs to regularly perform tasks on behalf of a certain tenant. By default, background executions (for example in a dedicated thread pool) aren't associated to any subscriber tenant and user. In this case, it's necessary to explicitly define a new Request Context based on the subscribed tenant by calling `systemUser(tenantId)`. This ensures that the Persistence Service performs the query for the specified tenant. - -```java -runtime.requestContext().systemUser(tenant).run(reqContext -> { - return persistenceService.run(Select.from(Books_.class)) - .listOf(Books.class); -}); -``` ## Modifying Request Contexts { #modifying-requestcontext} Besides the described common use cases, it's possible to modify parts of an existing Request Context. To manually add, modify or reset specific attributes within the scope of a new Request Context, you can use the [RequestContextRunner](https://www.javadoc.io/doc/com.sap.cds/cds-services-api/latest/com/sap/cds/services/runtime/RequestContextRunner.html) API. diff --git a/java/migration.md b/java/migration.md index 1697c152b1..47650c0484 100644 --- a/java/migration.md +++ b/java/migration.md @@ -94,9 +94,9 @@ Some property defaults have been adjusted: | Property | Old Value | New Value | Explanation | | --- | --- | --- | --- | -| `cds.security.authorization.deep.enabled` | false | true | [Deep Authorization](./security#deep-auth) is now enabled by default. | +| `cds.security.authorization.deep.enabled` | false | true | [Deep Authorization](../guides/security/authorization#deep-auth) is now enabled by default. | | `cds.security.authorization.instanceBased.rejectSelectedUnauthorizedEntity.enabled` | false | true | Requests that violate instance-based authorization conditions now fail with 403, instead of 404. | -| `cds.security.authorization.instanceBased.checkInputData.enabled` | false | true | [Authorization Checks On Input Data](./security#input-data-auth) are now enabled by default. | +| `cds.security.authorization.instanceBased.checkInputData.enabled` | false | true | [Authorization Checks On Input Data](../guides/security/authorization#input-data-auth) are now enabled by default. | | `cds.errors.defaultTranslations.enabled` | false | true | [Translations for Validation Error Messages](./event-handlers/indicating-errors#ootb-translated-messages) are now enabled by default. | | `cds.sql.runtimeView.mode` | resolve | cte | [Runtime views](./working-with-cql/query-execution#runtimeviews) are now by default translated into Common Table Expressions | @@ -190,8 +190,6 @@ In IAS scenarios, the [Proof-Of-Possession](https://github.com/SAP/cloud-securit Because of this, applications calling a CAP Java application will need to send a valid client certificate in addition to the JWT token. In particular, applications using an Approuter have to set `forwardAuthCertificates: true` on the Approuter destination pointing to your CAP backend. -[Learn more about Proof-Of-Possession.](./security.md#proof-of-possession){.learn-more} - ### Lazy Localization by default EDMX resources served by the OData V4 `/$metadata` endpoints are now localized lazily by default. @@ -1179,7 +1177,7 @@ With the help of these interfaces, the classic enforcement API can be mapped to | `getUserAttribute(String attributeName)` | `user.getAttribute(attributeName)` | | `isContainerSecurityEnabled()` | no substitution required | -[See section **Enforcement API & Custom Handlers in Java** for more details.](./security#enforcement-api){.learn-more} +[See section **Developing with CAP Users** for more details.](../guides/security/cap-users#developing-with-users){.learn-more} diff --git a/java/outbox.md b/java/outbox.md index 0bd0d0c2a8..1804f1df9a 100644 --- a/java/outbox.md +++ b/java/outbox.md @@ -420,7 +420,7 @@ Filters can be applied as for any other CDS defined entity, for example, to filt It is crucial to make the service `OutboxDeadLetterQueueService` accessible for internal users only as it contains sensitive data that could be exploited for malicious purposes if unauthorized changes are performed. -[Learn more about pseudo roles](../guides/security/authorization#pseudo-roles){.learn-more} +[Learn more about pseudo roles](../guides/security/cap-users#pseudo-roles){.learn-more} ::: diff --git a/java/security.md b/java/security.md index 0eba8a281f..5d2e5450eb 100644 --- a/java/security.md +++ b/java/security.md @@ -1,6 +1,6 @@ --- synopsis: > - Describes authentication and authorization in CAP Java. + Describes authentication and authorization specific for CAP Java. status: released uacp: Used as link target from Help Portal at https://help.sap.com/products/BTP/65de2977205c403bbc107264b8eccf4b/9186ed9ab00842e1a31309ff1be38792.html --- @@ -26,146 +26,87 @@ uacp: Used as link target from Help Portal at https://help.sap.com/products/BTP/ { #security} -## Overview - -For Web services, authentication is about controlling _who_ is using the service. It typically involves verifying the user's identity, tenant, and validating claims like granted roles. In contrast, authorization makes sure that the user has the required privileges to access the requested resources. Hence, authorization is about controlling _what_ the user is allowed to handle. - -Hence both, authentication and authorization, are essential for application security: -* [Authentication](#authentication) describes how to configure authentication. -* [Authorization](#auth) is about resource access control. - -[Connecting to IAS Services](#outbound-auth) describes how to authenticate outbound calls. - -::: warning -Without security configured, CDS services are exposed to public. Proper configuration of authentication __and__ authorization is required to secure your CAP application. +::: info +This chapter appends CAP Java sepcifc information only. +Consult the comprehensive [Security Guide](../guides/security/#cap-security-guide) first to learn about CAP Security features in general. ::: ## Authentication { #authentication} -Authentication rejects user requests with invalid authentication and limits the possible resource impact. +### Auto Configuration { #xsuaa-ias } -Rejecting them as soon as possible is one of the reasons why it's not an integral part of the CAP runtime and needs to be configured on the application framework level. In addition, CAP Java is based on a [modular architecture](./developing-applications/building#modular_architecture) and allows flexible configuration of any authentication method. -By default, it supports the standard BTP platform identity services [out of the box](#xsuaa-ias): +To enable auto-configuration for authentication based on platform services, following two conditions need to be met: +1. Required Maven [dependencies](#maven-dependencies) available. +2. [Binding](#bindings) to a corresponding service instance (XSUAA and/or IAS) is available at runtime. -- [SAP Cloud Identity Services Identity Authentication (IAS)](https://help.sap.com/docs/cloud-identity-services) - preferred solution integrating endpoints cross SAP-systems -- [SAP Authorization and Trust Management Service (XSUAA)](https://help.sap.com/docs/authorization-and-trust-management-service) - previous offering scoped to a BTP landscape +::: warning +Only **if both, the library dependencies and an XSUAA or IAS service binding are in place**, the CAP Java SDK activates a Spring security configuration, which enforces authentication for all endpoints **automatically**. +::: -Which are highly recommended for production usage. For specific use cases, [custom authentication](#custom-authentication) can be configured as well. -Local development and testing can be done easily with built-in [mock user](#mock-users) support. +#### Maven Dependencies { #maven-dependencies } +To ensure the proper maven dependencies, we recommend using the `cds-starter-cloudfoundry` or the `cds-starter-k8s` starter bundle. +Both can be active for the local scenario. -### Configure XSUAA and IAS Authentication { #xsuaa-ias} -To enable your application for XSUAA or IAS-authentication, we recommend using the `cds-starter-cloudfoundry` or the `cds-starter-k8s` starter bundle, which covers all required dependencies. +:::details Runtime Maven dependencies required for authentication -:::details Individual Dependencies -These are the individual dependencies that can be explicitly added in the `pom.xml` file of your service: - * `com.sap.cloud.security:resourceserver-security-spring-boot-starter` that brings [spring-security library](https://github.com/SAP/cloud-security-services-integration-library/tree/main/spring-security) - * `org.springframework.boot:spring-boot-starter-security` - * `cds-feature-identity` +- `cds-feature-identity` +- `org.springframework.boot:spring-boot-starter-security` +- `com.sap.cloud.security:resourceserver-security-spring-boot-starter` that brings [spring-security library](https://github.com/SAP/cloud-security-services-integration-library/tree/main/spring-security) ::: -In addition, your application needs to be bound to corresponding service instances depending on your scenario. The following list describes which service needs to be bound depending on the tokens your applications should accept: - * only accept tokens issued by XSUAA --> bind your application to an [XSUAA service instance](../guides/security/authorization#xsuaa-configuration) - * only accept tokens issued by IAS --> bind your application to an [IAS service instance](https://help.sap.com/docs/IDENTITY_AUTHENTICATION) - * accept tokens issued by XSUAA and IAS --> bind your application to service instances of both types. +#### Service Bindings { #bindings } + +Additionally, your application must be bound to corresponding service instances depending on your scenario. +The following list describes which service must be bound depending on the tokens your application should accept: + * only accept tokens issued by XSUAA --> bind your application to an [XSUAA service instance](../guides/security/authentication#xsuaa-auth) + * only accept tokens issued by IAS --> bind your application to an [IAS service instance](../guides/security/authentication#ias-auth) + * accept tokens issued by XSUAA and IAS --> bind your application to service instances of [both types](../guides/security/authentication#hybrid-auth). -::: tip Specify Binding +::: tip Unique Binding CAP Java picks only a single binding of each type. If you have multiple XSUAA or IAS bindings, choose a specific binding with property `cds.security.xsuaa.binding` respectively `cds.security.identity.binding`. Choose an appropriate XSUAA service plan to fit the requirements. For instance, if your service should be exposed as technical reuse service, make use of plan `broker`. ::: -#### Proof-Of-Possession for IAS { #proof-of-possession} - -Proof-Of-Possession is a technique for additional security where a JWT token is **bound** to a particular OAuth client for which the token was issued. On BTP, Proof-Of-Possession is supported by IAS and can be used by a CAP Java application. - -Typically, a caller of a CAP application provides a JWT token issued by IAS to authenticate a request. With Proof-Of-Possession in place, a mutual TLS (mTLS) tunnel is established between the caller and your CAP application in addition to the JWT token. Clients calling your CAP application need to send the certificate provided by their `identity` service instance in addition to the IAS token. - -On Cloud Foundry, the CAP application needs to be exposed under an additional route which accepts client certificates and forwards them to the application as `X-Forwarded-Client-Cert` header (for example, the `.cert.cfapps.` domain). - -
- -On Kyma, it is required to configure an additional component (i.e. a gateway in Istio) which accepts client certificates and forwards them to the application as `X-Forwarded-Client-Cert` header. An example can be found in the Bookshop sample application [here](https://github.com/SAP-samples/cloud-cap-samples-java/tree/ias-ams-kyma/k8s). Besides defining the actual `Gateway` resource, it is required to expose the application under the new domain (see the `values.yaml` [here](https://github.com/SAP-samples/cloud-cap-samples-java/blob/e9c779cb64c0937815910988387b0775d8842765/helm/values.yaml#L47). - -The Proof-Of-Possession also affects approuter calls to a CAP Java application. The approuter needs to be configured to forward the certificate to the CAP application. First, set `forwardAuthCertificates: true` on the destination pointing to your CAP backend (for more details see [the `environment destinations` section on npmjs.org](https://www.npmjs.com/package/@sap/approuter#environment-destinations)). Second, configure the destination to use the route of the CAP backend that has been configured to accept client certificates as described previously. - -When authenticating incoming requests with IAS, the Proof-Of-Possession is activated by default. This requires using at least version `3.5.1` of the [SAP BTP Spring Security Client](https://github.com/SAP/cloud-security-services-integration-library/tree/main/spring-security) library. - -You can disable the Proof-Of-Possession enforcement in your CAP Java application by setting the property `sap.spring.security.identity.prooftoken` to `false` in the `application.yaml` file. - -:::tip -CAP Java requires an AppRouter to be configured with mTLS in case of IAS authentication (`forwardAuthCertificates: true`). -::: - +### Custom Authentication { #spring-boot } -### Automatic Spring Boot Security Configuration { #spring-boot} +#### Authenticated Endpoints { #auth-endpoints } -Only if **both, the library dependencies and an XSUAA/IAS service binding are in place**, the CAP Java SDK activates a Spring security configuration, which enforces authentication for all endpoints **automatically**: +By default, the [auto-configuration](#xsuaa-ias) covers * Protocol adapter endpoints (managed by CAP such as OData V4/V2 or custom protocol adapters) * Remaining custom endpoints (not managed by CAP such as custom REST controllers or Spring Actuators) -The security auto configuration authenticates all endpoints by default, unless corresponding CDS model is not explicitly opened to public with [pseudo-role](../guides/security/authorization#pseudo-roles) `any` (configurable behaviour). -Here's an example of a CDS model and the corresponding authentication configuration: - -```cds -service BooksService @(requires: 'any') { - @readonly - entity Books @(requires: 'any') {...} - - entity Reviews {...} - - entity Orders @(requires: 'Customer') {...} -} -``` - -| Path | Authenticated ? | -|:--------------------------|:----------------:| -| `/BooksService` | | -| `/BooksService/$metadata` | | -| `/BooksService/Books` | | -| `/BooksService/Reviews` | | -| `/BooksService/Orders` | | - - -::: tip -For multitenant applications, it's required to authenticate all endpoints as the tenant information is essential for processing the request. -::: - -There are several application parameters in section `cds.security.authentication` that influence the behaviour of the auto-configuration: +There are several application parameters in section `cds.security.authentication` that influence the behaviour of the auto-configuration wit hregards to the affected endpoints: | Configuration Property | Description | Default | :---------------------------------------------------- | :----------------------------------------------------- | ------------ -| `mode` | Determines the [authentication mode](#auth-mode): `never`, `model-relaxed`, `model-strict` or `always` | `model-strict` | `authenticateUnknownEndpoints` | Determines, if security configurations enforce authentication for endpoints not managed by protocol-adapters. | `true` | `authenticateMetadataEndpoints` | Determines, if OData $metadata endpoints enforce authentication. | `true` -The following properties can be used to switch off automatic security configuration at all: - -| Configuration Property | Description | Default -| :---------------------------------------------------- | :----------------------------------------------------- | ------------ -| `cds.security.xsuaa.enabled` | Whether automatic XSUAA security configuration is enabled. | `true` -| `cds.security.identity.enabled` | Whether automatic IAS security configuration is enabled. | `true` - -#### Setting the Authentication Mode { #auth-mode} +#### Authentication Modes { #auth-mode} The property `cds.security.authentication.mode` controls the strategy used for authentication of protocol-adapter endpoints. There are four possible values: -- `never`: No endpoint requires authentication. All protocol-adapter endpoints are considered public. -- `model-relaxed`: Authentication is derived from the authorization annotations `@requires` and `@restrict`. If no such annotation is available, the endpoint is considered public. -- `model-strict`: Authentication is derived from the authorization annotations `@requires` and `@restrict`. If no such annotation is available, the endpoint is authenticated. An explicit `@requires: 'any'` makes the endpoint public. -- `always`: All endpoints require authentication. +| Configuration Property | Description | +| :---------------------------------------------------- | :----------------------------------------------------- | +| `never` | No endpoint requires authentication. All protocol-adapter endpoints are considered public. +| `model-relaxed` | Authentication is derived from the authorization annotations `@requires` and `@restrict`. If no such annotation is available, the endpoint is considered public. +| `model-strict` | Authentication is derived from the authorization annotations `@requires` and `@restrict`. If no such annotation is available, the endpoint is authenticated. An explicit `@requires: 'any'` makes the endpoint public (Default). +| `always` | All endpoints require authentication. By default the authentication mode is set to `model-strict` to comply with secure-by-default. In that case you can use the annotation `@requires: 'any'` on service-level to make the service and its entities public again. -Please note that it's only possible to make an endpoint public, if the full endpoint path is considered public as well. +You can only make an endpoint public if the full endpoint path is also considered public. For example you can only make an entity public, if the service that contains it is also considered public. + ::: tip -Please note that the authentication mode has no impact on the *authorization* behaviour. +The authentication mode has no impact on the *authorization* behaviour. ::: -#### Customizing Spring Boot Security Configuration { #custom-spring-security-config} +#### Overrule Partially { #custom-spring-security-config } If you want to explicitly change the automatic security configuration, you can add an _additional_ Spring security configuration on top that overrides the default configuration by CAP. -This can be useful, for instance, if an alternative authentication method is required for *specific endpoints* of your application. +This can be useful if an alternative authentication method is required for *specific endpoints* of your application. As the default security configurations provided by CAP act as the last line of defense and handle any request by default, you need to ensure that your custom security configurations have higher precedence. At the `SecurityFilterChain` bean method, set the `@Order` annotation with a lower numeric value, for example `1`: @@ -214,76 +155,70 @@ public class ActuatorSecurityConfig { } ``` -In case you want to write your own custom security configuration that acts as a last line of defense and handles any request you need to disable the CAP security configurations by setting cds.security.authentication.authConfig.enabled: false, as Spring Security forbids registering multiple security configurations with an any request security matcher. - -### Custom Authentication { #custom-authentication} -You're free to configure any authentication method according to your needs. CAP isn't bound to any specific authentication method or user representation such as introduced with XSUAA, it rather runs the requests based on a [user abstraction](../guides/security/authorization#user-claims). The CAP user of a request is represented by a [UserInfo](https://www.javadoc.io/doc/com.sap.cds/cds-services-api/latest/com/sap/cds/services/request/UserInfo.html) object that can be retrieved from the [RequestContext](https://www.javadoc.io/doc/com.sap.cds/cds-services-api/latest/com/sap/cds/services/request/RequestContext.html) as explained in [Enforcement API & Custom Handlers](#enforcement-api). - -Hence, if you bring your own authentication, you have to transform the authenticated user and inject as `UserInfo` to the current request. This is done by means of [UserInfoProvider](https://www.javadoc.io/doc/com.sap.cds/cds-services-api/latest/com/sap/cds/services/runtime/UserInfoProvider.html) interface that can be implemented as Spring bean as demonstrated in [Registering Global Parameter Providers](../java/event-handlers/request-contexts#global-providers). -More frequently you might have the requirement to just adapt the request's `UserInfo` which is possible with the same interface: +#### Overrule Fully { #custom-spring-security-alone } +In case you want to write your own custom security configuration that acts as a last line of defense and handles any request you need to disable the CAP security configurations by setting cds.security.authentication.authConfig.enabled: false, as Spring Security forbids registering multiple security configurations with an any request security matcher. -```java -@Component -public class CustomUserInfoProvider implements UserInfoProvider { +If you even want to deactivate OAuth token validation for XSUAA or IAS, e.g. to establish an own authentication strategy, +the following properties can be used: - private UserInfoProvider defaultProvider; +| Configuration Property | Description | Default +| :---------------------------------------------------- | :----------------------------------------------------- | ------------ +| `cds.security.xsuaa.enabled` | Whether automatic XSUAA security configuration is enabled. | `true` +| `cds.security.identity.enabled` | Whether automatic IAS security configuration is enabled. | `true` - @Override - public UserInfo get() { - ModifiableUserInfo userInfo = UserInfo.create(); - if (defaultProvider != null) { - UserInfo prevUserInfo = defaultProvider.get(); - if (prevUserInfo != null) { - userInfo = prevUserInfo.copy(); - } - } - if (userInfo != null) { - /* any modification of the resolved user goes here: */ - XsuaaUserInfo xsuaaUserInfo = userInfo.as(XsuaaUserInfo.class); - userInfo.setName(xsuaaUserInfo.getEmail() + "/" + - xsuaaUserInfo.getOrigin()); // normalizes name - } - return userInfo; - } - @Override - public void setPrevious(UserInfoProvider prev) { - this.defaultProvider = prev; - } -} -``` +## CAP Users { #custom-authentication} -In the example, the `CustomUserInfoProvider` defines an overlay on the default XSUAA-based provider (`defaultProvider`). The overlay redefines the user's name by a combination of email and origin. +CAP is not bound to any specific authentication method or user representation such as those introduced with XSUAA or IAS; it runs requests based on a [user abstraction](../guides/security/cap-users#claims). +The CAP user of a request is represented by a [UserInfo](https://www.javadoc.io/doc/com.sap.cds/cds-services-api/latest/com/sap/cds/services/request/UserInfo.html) object that can be retrieved from the [RequestContext](https://www.javadoc.io/doc/com.sap.cds/cds-services-api/latest/com/sap/cds/services/request/RequestContext.html) as explained in the [authentication guide](../guides/security/cap-users#developing-with-users). -### Mock User Authentication with Spring Boot { #mock-users} +### Mock Users { #mock-users} By default, CAP Java creates a security configuration, which accepts _mock users_ for test purposes. -::: details Requirement +::: tip Mock users are only initialized if the `org.springframework.boot:spring-boot-starter-security` dependency is present in the `pom.xml` file of your service. ::: -#### Preconfigured Mock Users +#### Preconfigured Mock Users { #preconfigured-mock-users} + +For convenience, the runtime creates default mock users reflecting the [pseudo roles](../guides/security/cap-users#pseudo-roles): + +| Name | Role | Password +| :---------------------------------------------------- | :----------------------------------------------------- | ------------ +| `authenticated` | `authenticated-user` | _empty_ +| `system` |`system-user` | _empty_ +| `privileged` | privileged mode | _empty_ + + +For example, requests sent during a Spring MVC unit test with annotation `@WithMockUser("authenticated")` will pass authorization checks that require `authenticated-user`. +The privileged user will pass any authorization checks. -For convenience, the runtime creates default mock users reflecting the [pseudo roles](../guides/security/authorization#pseudo-roles). They are named `authenticated`, `system` and `privileged` and can be used with an empty password. For instance, requests sent during a Spring MVC unit test with annotation `@WithMockUser("authenticated")` will pass authorization checks that require `authenticated-user`. The privileged user will pass any authorization checks. `cds.security.mock.defaultUsers = false` prevents the creation of default mock users at startup. +There are several properties to control behavioud of mock users: -#### Explicitly Defined Mock Users +| Configuration Property | Description | Default +| :---------------------------------------------------- | :----------------------------------------------------- | ------------ +| `cds.security.mock.defaultUsers` | Activates creation of pre-defined mock users at startup. | `true` +| `cds.security.mock.enabled` | Activates mock users. | `false` in production profile, `true` otherwise. + + +#### Custom Mock Users {#custom-mock-users} You can also define mock users explicitly. This mock user configuration only applies if: -* The service runs without an XSUAA service binding (non-productive mode) +* The service runs without a service binding (non-production mode) * Mock users are defined in the active application configuration -Define the mock users in a Spring profile, which may be only active during testing, as in the following example: +Define the mock users in a Spring profile, which may be only active in local testing, as in the following example: ::: code-group ```yaml [srv/src/main/resources/application.yaml] --- spring: - config.activate.on-profile: test + config.activate.on-profile: default cds: security: mock: @@ -307,11 +242,9 @@ cds: - "*" ``` ::: -- Mock user with name `Viewer-User` is a typical business user with SaaS-tenant `CrazyCars` who has assigned role `Viewer` and user attribute `Country` (`$user.Country` evaluates to value list `[GER, FR]`). This user also has the additional attribute `email`, which can be retrieved with `UserInfo.getAdditionalAttribute("email")`. The [features](../java/reflection-api#feature-toggles) `cruise` and `park` are enabled for this mock user. +- Mock user with name `Viewer-User` is a typical business user with SaaS tenant `CrazyCars` who has the assigned role `Viewer` and user attribute `Country` (`$user.Country` evaluates to the value list `[GER, FR]`). This user also has the additional attribute `email`, which can be retrieved with `UserInfo.getAdditionalAttribute("email")`. The [features](../java/reflection-api#feature-toggles) `cruise` and `park` are enabled for this mock user. - `Admin-User` is a user running in privileged mode. Such a user is helpful in tests that bypasses all authorization handlers. -Property `cds.security.mock.enabled = false` disables any mock user configuration (default in production profile). - A setup for Spring MVC-based tests based on the given mock users and the CDS model from [above](#spring-boot) could look like this: ```java @@ -360,403 +293,43 @@ cds: The mock user `Alice` is assigned to the mock tenant `CrazyCars` for which the features `cruise` and `park` are enabled. -## Connecting to IAS Services { #outbound-auth } - -CAP Java supports the consumption of IAS-based services of various kinds: - -* [Internal Services](#internal-app) bound to the same IAS instance of the provider application. -* [External IAS](#app-to-app) applications consumed by providing a destination. -* [BTP reuse services](#ias-reuse) consumed via service binding. - -![The TAM graphic is explained in the accompanying text.](./assets/java-ias.png){width="800px" } -Regardless of the kind of service, CAP provides a [unified integration as Remote Service](/java/cqn-services/remote-services#remote-odata-services). -Basic communication setup and user propagation is addressed under the hood, for example, an mTLS handshake is performed in case of service-2-service communication. +### Custom Users { #custom-users} -### Internal Services {#internal-app} - -For communication between adjacent CAP applications, these are CAP applications which are bound to the same identity instance, simplified configuration is explained in [Binding to a Service with Shared Identity](/java/cqn-services/remote-services#binding-to-a-service-with-shared-identity). - -### External Services (IAS App-to-App) {#app-to-app} - -CAP Java supports technical communication with any IAS-based service deployed to an SAP Cloud landscape. User propagation is supported. -For connection setup, it uses [IAS App-2-App flows](https://help.sap.com/docs/cloud-identity-services/cloud-identity-services/consume-apis-from-other-applications). - -#### Provider Application - -The CAP Java application as a _provider app_ needs to: - -1. Configure [IAS authentication](/java/security#xsuaa-ias). -2. Expose an API in the IAS service instance. - - ::: details Sample IAS instance of provider (mta.yaml) - - Add this to your `mta.yaml` resources section: +Therefore, if you bring your own authentication, you must transform the authenticated user and inject it as `UserInfo` to the current request. This is done by means of [UserInfoProvider](https://www.javadoc.io/doc/com.sap.cds/cds-services-api/latest/com/sap/cds/services/runtime/UserInfoProvider.html) interface that can be implemented as Spring bean as demonstrated in [Registering Global Parameter Providers](../java/event-handlers/request-contexts#global-providers). +More frequently you might have the requirement to just adapt the request's `UserInfo` which is possible with the same interface: - ```yaml - - name: server-identity - type: org.cloudfoundry.managed-service - parameters: - service: identity - service-plan: application - config: - multi-tenant: true - provided-apis: - - name: "review-api" - ``` - ::: +```java +@Component +public class CustomUserInfoProvider implements UserInfoProvider { -3. Prepare a CDS service endpoint for the exposed API. + private UserInfoProvider defaultProvider; - ::: details Sample CDS Service for the API + @Override + public UserInfo get() { + ModifiableUserInfo userInfo = UserInfo.create(); + if (defaultProvider != null) { + UserInfo prevUserInfo = defaultProvider.get(); + if (prevUserInfo != null) { + userInfo = prevUserInfo.copy(); + } + } + if (userInfo != null) { + /* any modification of the resolved user goes here: */ + XsuaaUserInfo xsuaaUserInfo = userInfo.as(XsuaaUserInfo.class); + userInfo.setName(xsuaaUserInfo.getEmail() + "/" + + xsuaaUserInfo.getOrigin()); // normalizes name + } - ```cds - service ReviewService @(requires: 'review-api') { - [...] + return userInfo; } - ``` - - ::: - -::: tip API as CAP role -The API identifiers exposed by the IAS instance in list `provided-apis` are granted as CAP roles after successful authentication. -::: - -::: warning Use different roles for technical and business users -Use different CAP roles for technical clients without user propagation and for named business users. - -Instead of using the same role, expose dedicated CDS services to technical clients which aren't accessible to business users and vice verse. -::: - -#### Consumer Application - -To set up a connection to such an IAS service, the _consumer app_ requires to do: - -1. Create an IAS instance that consumes the required API. - - ::: details Sample IAS instance for client (mta.yaml) - - Add this to your `mta.yaml` resources section: - - ```yaml - - name: client-identity - type: org.cloudfoundry.managed-service - parameters: - service: identity - service-plan: application - config: - multi-tenant: true - oauth2-configuration: - token-policy: - grant_types: - - "urn:ietf:params:oauth:grant-type:jwt-bearer" - ``` - - ::: - -2. Create a Remote Service based on the destination (optional). - ::: details Sample Remote Service configuration - - ```yaml - cds: - remote.services: - Reviews: - destination: - name: review-service-destination - ``` - - ::: - -To activate the App-2-App connection as a *consumer*, you need to: - -1. Create an IAS application dependency in the IAS tenant: - - Open the Cloud Identity Services admin console - - Navigate to [Application APIs / Dependencies](https://help.sap.com/docs/cloud-identity-services/cloud-identity-services/communicate-between-applications) - - Create a new dependency pointing to your provider application's API - -2. Create a dedicated [destination](https://help.sap.com/docs/connectivity/sap-btp-connectivity-cf/access-destinations-editor) with the following configuration: - * The URL pointing to the IAS-endpoint of the application. - * Authentication type `NoAuthentication`. - * Attribute `cloudsdk.ias-dependency-name` with the name of the created IAS application dependency in Step 1. - -
- - - -[Learn more about how to consume external application APIs with IAS](https://help.sap.com/docs/cloud-identity-services/cloud-identity-services/consume-apis-from-other-applications) {.learn-more} - -[Learn more about simplified Remote Service configuration with destinations](/java/cqn-services/remote-services#destination-based-scenarios) {.learn-more} - - -### BTP Reuse Services {#ias-reuse} - -IAS-based BTP reuse services can be created/consumed with CAP Java even more easily. - -The CAP reuse service (provider) needs to: - -1. Configure [IAS authentication](/java/security#xsuaa-ias). -2. Bind an IAS instance that exposes services and service plans. - - ::: details Sample IAS instance for provider - - ```yaml - - name: server-identity - type: org.cloudfoundry.managed-service - parameters: - service: identity - service-plan: application - config: - multi-tenant: true - catalog: - services: - - id: "1d5c23ee-1ce6-6130-4af4-26461bc6ef79" - name: "review-service" - plans: - - id: "2d5c23ee-1ce6-6130-4af4-26461bc6ef78" - name: "review-api" - ``` - - ::: - -3. Prepare a CDS service endpoint for the exposed API. - - ::: details Sample CDS Service for the API - - ```cds - service ReviewService @(requires: 'review-api') { - [...] + @Override + public void setPrevious(UserInfoProvider prev) { + this.defaultProvider = prev; } - ``` - - ::: - -The CAP consumer application (client) needs to: - -1. Create and bind the provided service from the marketplace. - - ::: details Create and bind service instance. - ```sh - cf create-service review-service review-api review-service-instance - cf bind-service review-service-instance --binding-name review-service-binding - ``` - ::: - -2. Create an IAS instance that consumes the required service. - - ::: details Sample IAS instance for client - - ```yaml - - name: client-identity - type: org.cloudfoundry.managed-service - parameters: - service: identity - service-plan: application - config: - multi-tenant: true - "consumed-services": [ { - "service-instance-name": "review-service-instance" - } ] - ``` - - ::: - -3. Create a Remote Service based on the binding (optional). - - ::: details Sample Remote Service configuration - - ```yaml - cds: - remote.services: - Reviews: - binding: - name: review-service-binding - onBehalfOf: currentUser - ``` - - ::: - -4. Use CQN queries to consume the reuse service (optional) - -[Learn more about simplified Remote Service configuration with bindings](/java/cqn-services/remote-services#service-binding-based-scenarios) {.learn-more} - -::: tip Service plan name as CAP role -The service plan names as specified in `consumed-services` in the IAS instance are granted as CAP roles after successful authentication. -::: - -::: warning Use different roles for technical and business users -Use different CAP roles for technical clients without user propagation and for named business users. - -Instead of using the same role, expose dedicated CDS services to technical clients which aren't accessible to business users and vice versa. -::: - - -#### How to Authorize Callbacks - -For bidirectional communication, callbacks from the reuse service to the CAP service need to be authorized as well. -Currently, there is no standadized way to achieve this in CAP so that custom codeing is required. -As a prerequisite*, the CAP service needs to know the clientId of the reuse service's IAS application which should be part of the binding exposed to the CAP service. - -::: details Sample Code for Authorization of Callbacks - -```java -private void authorizeCallback() { - UserInfo userInfo = runtime.getProvidedUserInfo(); - String azp = (String) userInfo.getAdditionalAttributes().get("azp"); - if(!userInfo.isSystemUser() || azp == null || !azp.equals(clientId)) { - throw new ErrorStatusException(ErrorStatuses.FORBIDDEN); - } - } -``` -::: - - -## Authorization { #auth} - -CAP Java SDK provides a comprehensive authorization service. By defining authorization rules declaratively via annotations in your CDS model, the runtime enforces authorization of the requests in a generic manner. Two different levels of authorization can be distinguished: - -- [Role-based authorization](../guides/security/authorization#requires) allows to restrict resource access depending on user roles. -- [Instance-based authorization](../guides/security/authorization#instance-based-auth) allows to define user privileges even on entity instance level, that is, a user can be restricted to instances that fulfill a certain condition. - -It's recommended to configure authorization declaratively in the CDS model. If necessary, custom implementations can be built on the [Authorization API](#enforcement-api). - -A precise description of the general authorization capabilities in CAP can be found in the [Authorization](../guides/security/authorization) guide. - -In addition to standard authorization, CAP Java provides additional out of the box capabilities to reduce custom code: - -### Deep Authorization { #deep-auth} - -Queries to Application Services are not only authorized by the target entity which has a `@restrict` or `@requires` annotation, but also for all __associated entities__ that are used in the statement. -__Compositions__ are neither checked nor extended with additional filters. -For instance, consider the following model: - -```cds -@(restrict: [{ grant: 'READ', to: 'Manager' }]) -entity Books {...} - -@(restrict: [{ grant: 'READ', to: 'Manager' }]) -entity Orders { - key ID: String; - items: Composition of many { - key book: Association to Books; - quantity: Integer; - } } ``` -For the following OData request `GET Orders(ID='1')/items?$expand=book`, authorizations for `Orders` and for `Books` are checked. -If the entity `Books` has a `where` clause for [instance-based authorization](/java/security#instance-based-auth), -it will be added as a filter to the sub-request with the expand. - -Custom CQL statements submitted to the [Application Service](/java/cqn-services/application-services) instances -are also authorized by the same rules including the path expressions and subqueries used in them. - -For example, the following statement checks role-based authorizations for both `Orders` and `Books`, -because the association to `Books` is used in the select list. - -```java -Select.from(Orders_.class, - f -> f.filter(o -> o.ID().eq("1")).items()) - .columns(c -> c.book().title()); -``` - -For modification statements with associated entities used in infix filters or where clauses, -role-based authorizations are checked as well. Associated entities require `READ` authorization, in contrast to the target of the statement itself. - -The following statement requires `UPDATE` authorization on `Orders` and `READ` authorization on `Books` -because an association from `Orders.items` to the book is used in the where condition. - -```java -Update.entity(Orders_.class, f -> f.filter(o -> o.ID().eq("1")).items()) - .data("quantity", 2) - .where(t -> t.book().ID().eq(1)); -``` -:::tip Modification of Statements -Be careful when you modify or extend the statements in custom handlers. -Make sure you keep the filters for authorization. -::: - -Starting with CAP Java `4.0`, deep authorization is on by default. -It can be disabled by setting cds.security.authorization.deep.enabled: false. - -[Learn more about `@restrict.where` in the instance-based authorization guide.](/guides/security/authorization#instance-based-auth){.learn-more} - -### Forbidden on 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**. -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`PATCH` and `DELETE` requests to single entities with forbidden accordingly. -The additional authorization check may affect performance. - -::: warning -To avoid to disclosure 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 behaviour otherwise. -::: - -Starting with CAP Java `4.0`, the reject behaviour is on by default. -It can be disabled by setting cds.security.authorization.instance-based.reject-selected-unauthorized-entity.enabled: false. - -[Learn more about `@restrict.where` in the instance-based authorization guide.](/guides/security/authorization#instance-based-auth){.learn-more} - -### Authorization Checks On Input Data { #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`. - -Let's assume an entity `Orders` which restricts access to users classified by assigned accounting areas: - -```cds -annotate Orders with @(restrict: [ - { grant: '*', where: 'accountingArea = $user.accountingAreas' } ]); -``` - -A user with accounting areas `[Development, Research]` is not able to send an `UPDATE` request, that changes `accountingArea` from `Research` or `Development` to `CarFleet`, for example. -Note that the `UPDATE` on instances _not matching the request user's accounting areas_ (for example, `CarFleet`) are rejected by standard instance-based authorization checks. - -Starting with CAP Java `4.0`, deep authorization is on by default. -It can be disabled by setting cds.security.authorization.instanceBased.checkInputData: false. - -[Learn more about `@restrict.where` in the instance-based authorization guide.](/guides/security/authorization#instance-based-auth){.learn-more} - - -### Enforcement API & Custom Handlers { #enforcement-api} - -The generic authorization handler performs authorization checks driven by the annotations in an early Before handler registered to all application services by default. You may override or add to the generic authorization logic by providing custom handlers. The most important piece of information is the [UserInfo](https://www.javadoc.io/doc/com.sap.cds/cds-services-api/latest/com/sap/cds/services/request/UserInfo.html) that reflects the authenticated user of the current request. You can retrieve it: - -a) from the [EventContext](https://www.javadoc.io/doc/com.sap.cds/cds-services-api/latest/com/sap/cds/services/EventContext.html): - ```java - EventContext context; - UserInfo user = context.getUserInfo(); - ``` - -b) through dependency injection within a handler bean: - - ```java - @Autowired - UserInfo user; - ``` - -The most helpful getters in `UserInfo` are listed in the following table: - -| UserInfo method | Description -| :---------------------------------------------------- | :----------------------------------------------------- | -| `getName()` | Returns the unique (logon) name of the user as configured in the IdP. Referred by `$user` and `$user.name`. | -| `getTenant()` | Returns the tenant of the user. | -| `isSystemUser()` | Indicates whether the request has been initiated by a technical service. Refers to [pseudo-role](../guides/security/authorization#pseudo-roles) `system-user`. | -| `isAuthenticated()` | True if the current user has been authenticated. Refers to [pseudo-role](../guides/security/authorization#pseudo-roles) `authenticated-user`. | -| `isPrivileged()` | Returns `true` if the current user runs in privileged (that is, unrestricted) mode | -| `hasRole(String role)` | Checks if the current user has the given role. | -| `getRoles()` | Returns the roles of the current user | -| `getAttributeValues(String attribute)` | Returns the value list of the given user attribute. Referred by `$user.`. | - -It's also possible to modify the `UserInfo` object for internal calls. See section [Request Contexts](./event-handlers/request-contexts) for more details. -For instance, you might want to run internal service calls in privileged mode that bypasses authorization checks: - -```java -cdsRuntime.requestContext().privilegedUser().run(privilegedContext -> { - assert privilegedContext.getUserInfo().isPrivileged(); - // ... Service calls in this scope pass generic authorization handler -}); -``` +In the example, the `CustomUserInfoProvider` defines an overlay on the default XSUAA-based provider (`defaultProvider`). The overlay redefines the user's name by a combination of email and origin. diff --git a/menu.md b/menu.md index 53a289d38e..7cea86d492 100644 --- a/menu.md +++ b/menu.md @@ -76,8 +76,11 @@ ## [Security](guides/security/) - ### [CDS-based Authorization](guides/security/authorization) - ### [Platform Security](guides/security/overview) + ### [Overview](guides/security/overview) + ### [Authentication](guides/security/authentication) + ### [CAP Users](guides/security/cap-users) + ### [CAP Authorization](guides/security/authorization) + ### [Remote Authentication](guides/security/remote-authentication) ### [Security Aspects](guides/security/aspects) ### [Data Protection & Privacy](guides/security/data-protection-privacy) ### [Product Standard Compliance](../guides/security/product-standards) diff --git a/tools/cds-lint/rules/auth-valid-restrict-to/index.md b/tools/cds-lint/rules/auth-valid-restrict-to/index.md index 6b6c7fe2fa..f22577ccca 100644 --- a/tools/cds-lint/rules/auth-valid-restrict-to/index.md +++ b/tools/cds-lint/rules/auth-valid-restrict-to/index.md @@ -14,7 +14,7 @@ status: released ## Rule Details -The `to` property of a `@restrict` privilege defines one or more [user roles](../../../../guides/security/authorization#roles) or [pseudo roles](../../../../guides/security/authorization#pseudo-roles) that the privilege applies to. This rule checks that the values of `@restrict.to` are valid, that is, roles cannot be missing or misspelled and that roles including `any` should be simplified to just `any`. +The `to` property of a `@restrict` privilege defines one or more [user roles](../../../../guides/security/cap-users#roles) or [pseudo roles](../../../../guides/security/cap-users#pseudo-roles) that the privilege applies to. This rule checks that the values of `@restrict.to` are valid, that is, roles cannot be missing or misspelled and that roles including `any` should be simplified to just `any`. ## Examples