Skip to content

feat: Expose sap_id_type claim via SapIdToken#getIdType#1983

Open
NiklasHerrmann21 wants to merge 1 commit into
mainfrom
feature/sap-id-type
Open

feat: Expose sap_id_type claim via SapIdToken#getIdType#1983
NiklasHerrmann21 wants to merge 1 commit into
mainfrom
feature/sap-id-type

Conversation

@NiklasHerrmann21

Copy link
Copy Markdown
Contributor

Summary

  • New SapIdToken#getIdType() returning a typed SapIdType enum (USER / APP) backed by the sap_id_type JWT claim issued by SAP Cloud Identity Service.
  • New TokenClaims.SAP_ID_TYPE constant.
  • DefaultIdTokenExtension#isTechnicalUser now prefers the claim and falls back to the sub == azp heuristic for tokens issued before the claim was introduced — no behavior change for pre-sap_id_type bindings, but new tokens drive the right decision when the heuristic would have been wrong.

Why

The Node library exposes the same claim (token.idType) and uses it to replace the sub === azp heuristic at decision points like DefaultIdTokenExtension._getIdToken and XsuaaLegacyExtension.appliesTo. This brings the Java library in line.

Design notes

  • SapIdType lives in java-api next to TokenClaims so it can be referenced from both java-api and java-security.
  • The enum returns null for both an absent claim and an unknown value (e.g. a future third principal type). This keeps consumers forward-compatible without throwing on tokens issued by newer IAS versions.
  • isTechnicalUser keeps the legacy sub == azp path as a fallback to avoid breaking technical-user detection for tokens that don't yet carry sap_id_type.

Test plan

  • mvn -pl java-api,java-security -am test — 330 tests green, including new coverage:
    • SapIdTokenTest: getIdType for user, app, missing claim, unknown value
    • DefaultIdTokenExtensionTest: sap_id_type=app is treated as technical user; sap_id_type=user overrides the sub == azp heuristic
  • mvn install -DskipTests (full repo compile + javadoc) — green

Release

CHANGELOG entry added under 4.1.0 (minor bump for additive feature). The pom version bump will follow in a separate chore: Release 4.1.0 commit once any in-flight Renovate PRs are merged.

SAP Cloud Identity Service issues the sap_id_type claim to disambiguate
human end-users ("user") from technical/application principals ("app").
Until now, code had to rely on the sub == azp heuristic to detect technical
users, which is brittle.

Add a typed SapIdType enum (USER/APP) and a SapIdToken#getIdType() accessor
that resolves the claim, returning null for absent or unknown values so
callers can stay forward-compatible with future principal types.

DefaultIdTokenExtension#isTechnicalUser now prefers the claim and falls
back to the sub == azp comparison for tokens issued before the claim was
introduced.

@finkmanAtSap finkmanAtSap left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I have some doubts about the strongly types enum approach. Let's discuss whether you see it the same way as me or not.

Comment on lines +72 to +74
public SapIdType getIdType() {
return SapIdType.fromClaimValue(getClaimAsString(TokenClaims.SAP_ID_TYPE));
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The enum return type is the cleanest approach but it has the problem that it is not forward-compatible if new claim values are added, e.g. "agent" perhaps. You will need to do new releases and consumers will need to update before being able to read the claim correctly. With the old version, it will look like the claim is missing which might be misleading.

I would go for a String return value and export String constants for the currently known values, i.e. APP and USER that consumers can compare with today. It's less nice than comparing with an enum but it's honest about the fact that the claim could have more than these two values and allows programming already against that.

This is important when high-lever modules in the library stack like CAP use this feature: they cannot simply set a minimum version of the BTP sec libs for applications and begin to use newer enum values. Instead, they need to work also when older versions of the library are installed and where no enum value exists for new claim values.

* @param token the token to inspect
* @return {@code true} if the token belongs to a technical user
*/
private boolean isTechnicalUser(Token token) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Not sure how it is implemented in Node.js (probably like this) but I think it would be best to be conservative and demand "user" before attempting a token exchange. Theoretically, in this case, there could be new types introduced later for which the extension should also do the exchange but it does not do it because it requires "user". However, this is better than the alternative where the extension checks for "app" and attempts an exchange in all other cases, even when it should not (e.g. when new non-named-user values like "agent" are introduced).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants