Skip to content

Commit 789890d

Browse files
Java library v4 (#24)
--------- Co-authored-by: Daniel Kuntze <daniel.kuntze@sap.com>
1 parent 85e3683 commit 789890d

22 files changed

Lines changed: 2244 additions & 449 deletions

.gitignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,4 +22,5 @@ cache
2222
*.ntvs*
2323
*.njsproj
2424
*.sln
25-
*.sw?
25+
*.sw?
26+
/sci-developer-guide.iml

docs/.vitepress/config.js

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -31,9 +31,8 @@ export default defineConfig(withMermaid({
3131
text: 'Authorization',
3232
items: [
3333
{ text: 'Getting Started', link: '/Authorization/GettingStarted' },
34-
{ text: 'Authorization Policies', link: '/Authorization/AuthorizationPolicies' },
34+
{ text: 'Authorization Bundle', link: '/Authorization/AuthorizationBundle' },
3535
{ text: 'Authorization Checks', link: '/Authorization/AuthorizationChecks' },
36-
{ text: 'Startup Check', link: '/Authorization/StartupCheck' },
3736
{ text: 'Testing', link: '/Authorization/Testing' },
3837
{ text: 'Technical Communication', link: '/Authorization/TechnicalCommunication' },
3938
{ text: 'Deploying DCL', link: '/Authorization/DeployDCL' },
@@ -57,9 +56,21 @@ export default defineConfig(withMermaid({
5756
text: 'Java',
5857
collapsed: true,
5958
items: [
60-
{ text: 'jakarta-ams', link: '/Libraries/java/jakarta-ams/jakarta-ams' },
61-
{ text: 'spring-ams', link: '/Libraries/java/spring-ams/spring-ams' },
62-
{ text: 'cap-ams-support', link: '/Libraries/java/cap-ams-support/cap-ams-support' }
59+
{ text: 'Changelog', link: '/Libraries/java/changelog' },
60+
{ text: 'ams-core', link: '/Libraries/java/ams-core' },
61+
{ text: 'ams-test', link: '/Libraries/java/ams-test' },
62+
{ text: 'cap-ams', link: '/Libraries/java/cap-ams' },
63+
{ text: 'spring-boot-ams', link: '/Libraries/java/spring-boot-ams' },
64+
{
65+
text: 'Version 3.x',
66+
collapsed: true,
67+
items: [
68+
{ text: 'Migration Guide (→ 4.x)', link: '/Libraries/java/v3/migration-v3-to-v4' },
69+
{ text: 'jakarta-ams', link: '/Libraries/java/v3/jakarta-ams' },
70+
{ text: 'cap-ams-support', link: '/Libraries/java/v3/cap-ams-support' },
71+
{ text: 'spring-ams', link: '/Libraries/java/v3/spring-ams' },
72+
]
73+
}
6374
]
6475
},
6576
{
@@ -70,7 +81,8 @@ export default defineConfig(withMermaid({
7081
{ text: '@sap/ams-dev', link: 'https://www.npmjs.com/package/@sap/ams-dev' }
7182
]
7283
},
73-
{ text: 'Go',
84+
{
85+
text: 'Go',
7486
collapsed: true,
7587
items: [
7688
{ text: 'cloud-identity-authorizations-golang-library', link: '/Libraries/go/go-ams' }
Lines changed: 182 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,182 @@
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

Comments
 (0)