|
| 1 | +# Authorization Bundle |
| 2 | + |
| 3 | +The *Authorization Bundle* is a container for [authorization policies](#authorization-policies) that sets the context for authorization checks in an application. Bundles are compiled centrally by the Authorization Management Service (**AMS**) from where client applications download the bundle of their respective [AMS service instance](/Authorization/GettingStarted#provisioning-of-ams-instances) to perform authorization checks. |
| 4 | + |
| 5 | +The bundle contains both the authorization policies defined as part of the application's source code as well as the policies created by administrators at runtime. For this reason, clients regularly poll for changes to keep the local copy up to date when administrators make changes (see [Client Library Initialization](#client-library-initialization)). |
| 6 | + |
| 7 | +## Authorization Policies |
| 8 | + |
| 9 | +Authorization policies grant the right to perform actions on protected resources in an application. They can be assigned to users to control access to various parts of the application. |
| 10 | + |
| 11 | +Developers can define a set of base policies that can be assigned directly or used as building blocks by the application administrators to create additional so-called admin policies at runtime. |
| 12 | + |
| 13 | +### DCL |
| 14 | + |
| 15 | +Authorization policies are defined in a domain-specific language called Data Control Language (**DCL**) that supports conditions that can be used to grant fine-grained access to resources. |
| 16 | + |
| 17 | +#### Example |
| 18 | +Here is an example of authorization policies defined in DCL: |
| 19 | + |
| 20 | +```dcl |
| 21 | +SCHEMA { |
| 22 | + category: String; |
| 23 | +} |
| 24 | +
|
| 25 | +POLICY ReadProducts { |
| 26 | + GRANT read ON products WHERE category IS NOT RESTRICTED; |
| 27 | +} |
| 28 | +
|
| 29 | +POLICY ReadOfficeSupplies { |
| 30 | + USE ReadProducts RESTRICT category = 'OfficeSupplies'; |
| 31 | +} |
| 32 | +``` |
| 33 | + |
| 34 | +#### Specification |
| 35 | +The complete specification for DCL can be found in the [SAP Help Portal](https://help.sap.com/docs/cloud-identity-services/cloud-identity-services/data-control-language-dcl). |
| 36 | + |
| 37 | +#### Deployment |
| 38 | +Please refer to the [Deploying DCL](/Authorization/DeployDCL) page for instructions on how to deploy DCL policies to an AMS service instance. |
| 39 | + |
| 40 | +## Client Library Initialization |
| 41 | + |
| 42 | +To initialize the AMS client libraries, an instance of the `AuthorizationManagementService` class must be created. In production, applications create an instance from **certificate-based** credentials for mTLS authentication with the AMS service to download the authorization bundle. These credentials are typically provided in the form of a SAP BTP service binding for the SAP Cloud Identity Services (**SCI**) offering. |
| 43 | + |
| 44 | +::: code-group |
| 45 | + |
| 46 | +```js [Node.js] |
| 47 | +const { AuthorizationManagementService } = require("@sap/ams"); |
| 48 | + |
| 49 | +// pass your @sap/xssec IdentityService instance used for authentication |
| 50 | +// which was created from certificate-based SCI credentials or |
| 51 | +// alternatively, a fixed { credentials ... } object directly |
| 52 | +const ams = AuthorizationManagementService |
| 53 | + .fromIdentityService(identityService); |
| 54 | +``` |
| 55 | + |
| 56 | +```java [Java] |
| 57 | +import com.sap.cloud.security.ams.api.AuthorizationManagementService; |
| 58 | +// from com.sap.cloud.environment.servicebinding:java-sap-vcap-services |
| 59 | +import com.sap.cloud.environment.servicebinding.api.DefaultServiceBindingAccessor; |
| 60 | + |
| 61 | + |
| 62 | +ServiceBinding identityServiceBinding = DefaultServiceBindingAccessor |
| 63 | + .getInstance().getServiceBindings().stream() |
| 64 | + .filter(binding -> "identity".equals(binding.getServiceName().orElse(null))) |
| 65 | + .findFirst() |
| 66 | + .orElse(null); |
| 67 | + |
| 68 | + AuthorizationManagementService ams = AuthorizationManagementService |
| 69 | + .fromIdentityServiceBinding(identityServiceBinding); |
| 70 | +``` |
| 71 | + |
| 72 | +::: |
| 73 | + |
| 74 | +::: danger Important |
| 75 | +After creating the `AuthorizationManagementService` instance, the application must ensure with a [startup check](#startup-check) that the instance is ready before accepting traffic that requires authorization checks. |
| 76 | +::: |
| 77 | + |
| 78 | +::: tip |
| 79 | +The AMS client libraries integrate into different web frameworks, such as [CAP](https://cap.cloud.sap/docs/) or [Spring Security](https://spring.io/projects/spring-security). The respective [Spring Boot starters](/Authorization/GettingStarted#java) and [Node.js CAP plugin](/Authorization/GettingStarted#node-js) automatically create the `AuthorizationManagementService` instance from the SCI service binding in the application's environment, so manual initialization is not required in these cases. |
| 80 | +::: |
| 81 | + |
| 82 | +## Startup Check |
| 83 | + |
| 84 | +While it is possible to synchronously block application startup until the AMS module becomes ready, we recommend including AMS in the application's **readiness probes**. This allows the application process to become healthy for the cloud platform but prevent traffic from being routed to the process until the AMS module is ready to serve authorization checks. |
| 85 | + |
| 86 | +If an application does not provide readiness probes, it can alternatively include the AMS readiness state in its **health endpoint**. |
| 87 | + |
| 88 | +::: code-group |
| 89 | +```js [Node.js (CAP)] |
| 90 | +// server.js |
| 91 | +const cds = require('@sap/cds'); |
| 92 | +const { amsCapPluginRuntime } = require("@sap/ams"); |
| 93 | + |
| 94 | +cds.on('served', async () => { |
| 95 | + // effectively a synchronous startup check that prevents the server |
| 96 | + // from serving requests for up to 30s before throwing an error |
| 97 | + await amsCapPluginRuntime.ams.whenReady(30); |
| 98 | + console.log("AMS has become ready."); |
| 99 | +}); |
| 100 | + |
| 101 | +// *better*: use amsCapPluginRuntime.ams.isReady() in health or readiness endpoint |
| 102 | +``` |
| 103 | + |
| 104 | +```js [Node.js] |
| 105 | +// uses readiness status in health endpoint |
| 106 | + |
| 107 | +let isReady = false; |
| 108 | +const healthCheck = (req, res) => { |
| 109 | + if (isReady) { |
| 110 | + res.json({ status: 'UP' }); |
| 111 | + } else { |
| 112 | + res.status(503).json({ status: 'DOWN', message: 'Service is not ready' }); |
| 113 | + } |
| 114 | +}; |
| 115 | + |
| 116 | +const amsStartupCheck = async () => { |
| 117 | + try { |
| 118 | + await ams.whenReady(AMS_STARTUP_TIMEOUT); |
| 119 | + isReady = true; |
| 120 | + console.log("AMS is ready now."); |
| 121 | + } catch (e) { |
| 122 | + console.error("AMS didn't get ready in time:", e); |
| 123 | + process.exit(1); |
| 124 | + } |
| 125 | +}; |
| 126 | + |
| 127 | +app.get('/health', healthCheck); |
| 128 | +const server = app.listen(PORT, () => { |
| 129 | + console.log(`Server is listening on port ${PORT}`); |
| 130 | +}); |
| 131 | + |
| 132 | +amsStartupCheck(); |
| 133 | +``` |
| 134 | + |
| 135 | +```java [Java] |
| 136 | +import java.util.concurrent.TimeUnit; |
| 137 | +import java.util.concurrent.atomic.AtomicBoolean; |
| 138 | + |
| 139 | +// Synchronous startup check: |
| 140 | +// throws an error if the AMS module doesn't get ready within 30 seconds |
| 141 | +ams.whenReady().get(30, TimeUnit.SECONDS); |
| 142 | + |
| 143 | +// Asynchronous startup check: uses readiness status in health endpoint |
| 144 | +private static final AtomicBoolean isReady = new AtomicBoolean(false); |
| 145 | + |
| 146 | +app.get("/health", ctx -> { |
| 147 | + if (isReady.get()) { |
| 148 | + ctx.json(HealthStatus.up()); |
| 149 | + } else { |
| 150 | + ctx.status(503).json(HealthStatus.down("Service is not ready")); |
| 151 | + } |
| 152 | +}); |
| 153 | + |
| 154 | +// Wait up to 30s for AMS to become ready |
| 155 | +ams.whenReady().orTimeout(30, TimeUnit.SECONDS).thenRun(() -> { |
| 156 | + isReady.set(true); |
| 157 | + LOG.info("AMS is ready, application is now ready to serve requests"); |
| 158 | +}).exceptionally(ex -> { |
| 159 | + LOG.error("AMS failed to become ready within the timeout", ex); |
| 160 | + System.exit(1); |
| 161 | + return null; |
| 162 | +}); |
| 163 | +``` |
| 164 | + |
| 165 | +```java [Spring Boot Readiness] |
| 166 | +// The spring-boot-starter-ams-readiness module provides an auto-config for a |
| 167 | +// AmsReadinessContributor bean. It autowires an AuthorizationManagementService |
| 168 | +// instance and uses AvailabilityChangeEvent.publish |
| 169 | +// to integrate its readiness state with Spring Boot's availability state. |
| 170 | +// The AMS readiness starter is included transitively in all AMS Spring Boot starters. |
| 171 | +``` |
| 172 | + |
| 173 | +```java [Spring Boot Health Actuator] |
| 174 | +// The spring-boot-starter-ams-health and spring-boot-3-starter-ams-health modules |
| 175 | +// provide an auto-config for a HealthIndicator bean that integrates with the |
| 176 | +// Spring Boot Actuator health endpoint. It autowires an |
| 177 | +// AuthorizationManagementService instance and includes its readiness state |
| 178 | +// in the health status. |
| 179 | +// The AMS health starter is NOT included transitively in any AMS Spring Boot starter. |
| 180 | +``` |
| 181 | + |
| 182 | +::: |
0 commit comments