Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
117 changes: 73 additions & 44 deletions docs/Authorization/AuthorizationChecks.md
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,12 @@ The `SciAuthorizationsProvider` (Node.js: `IdentityServiceAuthProvider`) is the
using SAP Cloud Identity Services for authentication. It derives authorizations from SAP Identity Service token
principals.

::: tip Custom `$user` attributes are populated automatically
Any `String` or `String[]` attribute declared in your AMS schema under `$user` is automatically populated from the token claim of the
same name (e.g. the `department` claim populates `$user.department`). You only need to
[customize the default input](#overriding-methods) for attributes whose claim has a different name or type, or if it is a computed value.
:::

::: code-group

```js [Node.js]
Expand Down Expand Up @@ -144,13 +150,15 @@ described on that page in detail.

##### Overriding Methods

You can override the `getUserAuthorizations`,
`getClientAuthorizations` and the methods for building default input for authorization checks to derive a [custom implementation](#custom-implementation) from the class if necessary.
You can override `getUserAuthorizations` and `getClientAuthorizations` to customize which policies apply for a
principal, or override the methods for building default input to add custom attribute values for authorization checks.
This lets you derive a [custom implementation](#custom-implementation) from the standard class while reusing its token
handling.

**Example: Customizing Default Input for Authorization Checks**
**Example: Granting Additional Policies Based on Token Attributes**

In this example, we override `getDefaultInput` to include a custom user attribute (`$user.division`) from a token
claim that is not included by default:
In this example, we override `getUserAuthorizations` to grant an additional policy based on a token claim: users from
the `Sales` department receive the `shopping.SalesDashboard` policy on top of their assigned policies.

::: code-group

Expand All @@ -162,15 +170,15 @@ class CustomAuthProvider extends IdentityServiceAuthProvider {
/**
* @param {import("@sap/xssec").IdentityServiceSecurityContext} securityContext
*/
getInput(securityContext) {
const defaultInput = super.getInput(securityContext);
async getUserAuthorizations(securityContext) {
const userAuthorizations = await super.getUserAuthorizations(securityContext);

const division = securityContext.token.payload.division;
if (division) {
defaultInput["$user.division"] = division;
// grant an additional policy to users from the Sales department
if (userAuthorizations && securityContext.token.payload.department === 'Sales') {
userAuthorizations.policySet.policies.push('shopping.SalesDashboard');
}

return defaultInput;
return userAuthorizations;
}
}

Expand All @@ -187,15 +195,15 @@ class CustomAuthProvider extends IdentityServiceAuthProvider {
/**
* @param {import("@sap/xssec").IdentityServiceSecurityContext} securityContext
*/
getInput(securityContext) {
const defaultInput = super.getInput(securityContext);
async getUserAuthorizations(securityContext) {
const userAuthorizations = await super.getUserAuthorizations(securityContext);

const division = securityContext.token.payload.division;
if (division) {
defaultInput["$user.division"] = division;
// grant an additional policy to users from the Sales department
if (userAuthorizations && securityContext.token.payload.department === 'Sales') {
userAuthorizations.policySet.policies.push('shopping.SalesDashboard');
}

return defaultInput;
return userAuthorizations;
}
}
```
Expand All @@ -206,9 +214,10 @@ import org.springframework.context.annotation.Bean;
import com.sap.cloud.security.ams.core.SciAuthorizationsProvider;
import com.sap.cloud.security.ams.api.*;
import com.sap.cloud.security.ams.cap.api.*;
import com.sap.cloud.security.ams.api.expression.AttributeName;
import com.sap.cloud.security.ams.api.PolicyName;

import java.util.Map;
import java.util.HashSet;
import java.util.Set;

// Define in a @Configuration class
@Bean
Expand All @@ -217,16 +226,22 @@ public AuthorizationsProvider<CdsAuthorizations> customAmsAuthProvider(Authoriza
}

public class CustomAuthorizationsProvider extends SciAuthorizationsProvider<Authorizations> {
private static final AttributeName $USER_DIVISION = AttributeName.of("$user.division");
private static final PolicyName SALES_DASHBOARD = PolicyName.of("shopping.SalesDashboard");

@Override
protected Map<AttributeName, Object> getDefaultInput(Principal principal) {
Map<AttributeName, Object> defaultInput = super.getDefaultInput(principal);

principal.getClaimAsString("division")
.ifPresent(division -> defaultInput.put($USER_DIVISION, division));
protected Authorizations getUserAuthorizations(Principal principal) {
Authorizations userAuthorizations = super.getUserAuthorizations(principal);

// grant an additional policy to users from the Sales department
boolean isSales = principal.getClaimAsString("department")
.filter("Sales"::equals).isPresent();
if (userAuthorizations != null && isSales) {
Set<PolicyName> policies = new HashSet<>(userAuthorizations.getPolicies());
policies.add(SALES_DASHBOARD);
userAuthorizations.setPolicies(policies);
}

return defaultInput;
return userAuthorizations;
}
}
```
Expand All @@ -236,9 +251,10 @@ import org.springframework.context.annotation.Bean;

import com.sap.cloud.security.ams.core.SciAuthorizationsProvider;
import com.sap.cloud.security.ams.api.*;
import com.sap.cloud.security.ams.api.expression.AttributeName;
import com.sap.cloud.security.ams.api.PolicyName;

import java.util.Map;
import java.util.HashSet;
import java.util.Set;

// Define in a @Configuration class
@Bean
Expand All @@ -247,38 +263,51 @@ public AuthorizationsProvider<Authorizations> customAmsAuthProvider(Authorizatio
}

public class CustomAuthorizationsProvider extends SciAuthorizationsProvider<Authorizations> {
private static final AttributeName $USER_DIVISION = AttributeName.of("$user.division");
private static final PolicyName SALES_DASHBOARD = PolicyName.of("shopping.SalesDashboard");

@Override
protected Map<AttributeName, Object> getDefaultInput(Principal principal) {
Map<AttributeName, Object> defaultInput = super.getDefaultInput(principal);

principal.getClaimAsString("division")
.ifPresent(division -> defaultInput.put($USER_DIVISION, division));
protected Authorizations getUserAuthorizations(Principal principal) {
Authorizations userAuthorizations = super.getUserAuthorizations(principal);

// grant an additional policy to users from the Sales department
boolean isSales = principal.getClaimAsString("department")
.filter("Sales"::equals).isPresent();
if (userAuthorizations != null && isSales) {
Set<PolicyName> policies = new HashSet<>(userAuthorizations.getPolicies());
policies.add(SALES_DASHBOARD);
userAuthorizations.setPolicies(policies);
}

return defaultInput;
return userAuthorizations;
}
}
```

```java [Java]
import com.sap.cloud.security.ams.core.SciAuthorizationsProvider;
import com.sap.cloud.security.ams.api.*;
import com.sap.cloud.security.ams.api.expression.AttributeName;
import com.sap.cloud.security.ams.api.PolicyName;

import java.util.Map;
import java.util.HashSet;
import java.util.Set;

public class CustomAuthorizationsProvider extends SciAuthorizationsProvider<Authorizations> {
private static final AttributeName $USER_DIVISION = AttributeName.of("$user.division");
private static final PolicyName SALES_DASHBOARD = PolicyName.of("shopping.SalesDashboard");

@Override
protected Map<AttributeName, Object> getDefaultInput(Principal principal) {
Map<AttributeName, Object> defaultInput = super.getDefaultInput(principal);

principal.getClaimAsString("division")
.ifPresent(division -> defaultInput.put($USER_DIVISION, division));
protected Authorizations getUserAuthorizations(Principal principal) {
Authorizations userAuthorizations = super.getUserAuthorizations(principal);

// grant an additional policy to users from the Sales department
boolean isSales = principal.getClaimAsString("department")
.filter("Sales"::equals).isPresent();
if (userAuthorizations != null && isSales) {
Set<PolicyName> policies = new HashSet<>(userAuthorizations.getPolicies());
policies.add(SALES_DASHBOARD);
userAuthorizations.setPolicies(policies);
}

return defaultInput;
return userAuthorizations;
}
}
```
Expand Down
7 changes: 6 additions & 1 deletion docs/Libraries/java/changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,11 @@

## Version 4

### 4.4.1

- `SciAuthorizationsProvider` now populates any String/String[] `$user` attributes from the AMS schema based on token claims with the same name (e.g. `department` -> `$user.department`), not just the default `$user` attributes.
- The `AmsBundleLoader` now sets a timeout of 30s for all requests for improved resilience.

### 4.4.0

- Feature: Synchronous startup readiness check in Spring Boot starters. The `AuthorizationManagementService` bean now blocks during application startup until the initial authorization bundle has been loaded. This is enabled by default; see [Startup Check](/Authorization/AuthorizationBundle#startup-check) for details, including how to adjust the timeout or opt out. The new utility method `AuthorizationManagementService.awaitReady(Duration)` simplifies synchronously awaiting the first bundle outside Spring.
Expand Down Expand Up @@ -71,7 +76,7 @@ The CAP Spring Boot starter already wraps the standard `Authorizations` in a `Cd
- Improved [Spring Security beans](/Libraries/java/spring-boot-ams#auto-configuration) for custom authorization checks
- New [event logging API](/Libraries/java/ams-core#events-logging) for logging authorization events
- Configuration options for [technical communication](/Authorization/TechnicalCommunication) scenarios via SAP Identity Service
- Customization of authorization strategy via `AuthorizationsProvider` interface, e.g. [custom user attribute injection](/Authorization/AuthorizationChecks#overriding-methods)
- Customization of authorization strategy via `AuthorizationsProvider` interface, e.g. [granting additional policies based on token attributes](/Authorization/AuthorizationChecks#overriding-methods)
- JUnit 5+ extension for unit testing policy semantics without a full-blown integration test using [`ams-test`](/Libraries/java/ams-test).
- Detailed [**DEBUG**](/Troubleshooting) logging about construction of `Authorizations` from token
- **TRACE** logging of authorization bundle content and logic engine evaluations, showing how conditions are built and grounded with attribute input and how the predicates were evaluated
Expand Down
Loading