diff --git a/CHANGELOG.md b/CHANGELOG.md index 9f180d191..9d2f865a2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,13 @@ # Change Log All notable changes to this project will be documented in this file. +## 4.1.0 + +- Expose the `sap_id_type` claim on `SapIdToken` + - New `SapIdToken#getIdType()` returning a typed `SapIdType` enum (`USER`, `APP`); resolves to `null` if the claim is absent or carries an unknown value + - New `TokenClaims.SAP_ID_TYPE` constant + - `DefaultIdTokenExtension#isTechnicalUser` now prefers the `sap_id_type` claim and falls back to the `sub == azp` heuristic for tokens issued before the claim was introduced + ## 4.0.7 - Fix mTLS handshake regression in `SSLContextFactory` diff --git a/java-api/src/main/java/com/sap/cloud/security/token/SapIdType.java b/java-api/src/main/java/com/sap/cloud/security/token/SapIdType.java new file mode 100644 index 000000000..df604ac3a --- /dev/null +++ b/java-api/src/main/java/com/sap/cloud/security/token/SapIdType.java @@ -0,0 +1,54 @@ +/** + * SPDX-FileCopyrightText: 2018-2023 SAP SE or an SAP affiliate company and Cloud Security Client Java contributors + *
+ * SPDX-License-Identifier: Apache-2.0 + */ +package com.sap.cloud.security.token; + +import jakarta.annotation.Nullable; + +/** + * Type of the principal an IAS-issued token belongs to. Mirrors the {@code sap_id_type} JWT claim + * issued by SAP Cloud Identity Service. + */ +public enum SapIdType { + /** Human end-user principal. */ + USER("user"), + /** Technical / application principal. */ + APP("app"); + + private final String claimValue; + + SapIdType(String claimValue) { + this.claimValue = claimValue; + } + + /** + * Returns the raw claim value used on the wire. + * + * @return the raw claim value as it appears in the JWT (e.g. {@code "user"} or {@code "app"}). + */ + public String claimValue() { + return claimValue; + } + + /** + * Resolves a {@link SapIdType} from a raw {@code sap_id_type} claim value. + * + * @param value the claim value, may be {@code null} + * @return the matching {@link SapIdType}, or {@code null} if {@code value} is {@code null} or + * does not match a known type (forward-compatibility for future values) + */ + @Nullable + public static SapIdType fromClaimValue(@Nullable String value) { + if (value == null) { + return null; + } + for (SapIdType t : values()) { + if (t.claimValue.equals(value)) { + return t; + } + } + return null; + } +} \ No newline at end of file diff --git a/java-api/src/main/java/com/sap/cloud/security/token/TokenClaims.java b/java-api/src/main/java/com/sap/cloud/security/token/TokenClaims.java index be8d7111d..38ef68be5 100644 --- a/java-api/src/main/java/com/sap/cloud/security/token/TokenClaims.java +++ b/java-api/src/main/java/com/sap/cloud/security/token/TokenClaims.java @@ -43,6 +43,13 @@ private TokenClaims() { public static final String SAP_GLOBAL_ZONE_ID = "zone_uuid"; // legacy claim public static final String SAP_GLOBAL_APP_TID = "app_tid"; // tenant GUID + /** + * Indicates the type of the token principal. Issued by SAP Cloud Identity Service. Values + * are {@code "user"} for human end-users and {@code "app"} for technical/application + * principals. See {@link SapIdType} for the typed accessor. + */ + public static final String SAP_ID_TYPE = "sap_id_type"; + public static final String GROUPS = "groups"; // scim groups public static final String AUTHORIZATION_PARTY = "azp"; // Authorization party contains OAuth client identifier public static final String CNF = "cnf"; // X509 certificate ("cnf" (confirmation)) claim diff --git a/java-security/src/main/java/com/sap/cloud/security/token/DefaultIdTokenExtension.java b/java-security/src/main/java/com/sap/cloud/security/token/DefaultIdTokenExtension.java index 69ac75f63..3689fad2e 100644 --- a/java-security/src/main/java/com/sap/cloud/security/token/DefaultIdTokenExtension.java +++ b/java-security/src/main/java/com/sap/cloud/security/token/DefaultIdTokenExtension.java @@ -27,7 +27,8 @@ *
A token is considered to belong to a technical user if the {@code sub} (subject) claim - * equals the {@code azp} (authorized party / client ID) claim. + *
Prefers the {@code sap_id_type} claim ({@link SapIdType#APP}) when present. For tokens
+ * issued before the claim was introduced, falls back to comparing {@code sub} with
+ * {@code azp}.
*
* @param token the token to inspect
* @return {@code true} if the token belongs to a technical user
*/
private boolean isTechnicalUser(Token token) {
- String subject = token.getClaimAsString("sub");
+ if (token instanceof SapIdToken idToken) {
+ SapIdType idType = idToken.getIdType();
+ if (idType != null) {
+ return idType == SapIdType.APP;
+ }
+ }
+ String subject = token.getClaimAsString(TokenClaims.SUBJECT);
String azp = token.getClientId();
if (subject == null || azp == null || subject.isBlank() || azp.isBlank()) {
return false;
diff --git a/java-security/src/main/java/com/sap/cloud/security/token/SapIdToken.java b/java-security/src/main/java/com/sap/cloud/security/token/SapIdToken.java
index 099abcc77..834bd4f6c 100644
--- a/java-security/src/main/java/com/sap/cloud/security/token/SapIdToken.java
+++ b/java-security/src/main/java/com/sap/cloud/security/token/SapIdToken.java
@@ -59,4 +59,17 @@ public String getIssuer() {
public String getCnfX509Thumbprint() {
return getAttributeFromClaimAsString(TokenClaims.CNF, TokenClaims.CNF_X5T);
}
+
+ /**
+ * Returns the principal type carried by the {@code sap_id_type} claim. Issued by SAP Cloud
+ * Identity Service to disambiguate human end-users ({@link SapIdType#USER}) from
+ * technical/application principals ({@link SapIdType#APP}).
+ *
+ * @return the resolved {@link SapIdType}, or {@code null} if the claim is absent or carries
+ * an unknown value (e.g. a future principal type).
+ */
+ @Nullable
+ public SapIdType getIdType() {
+ return SapIdType.fromClaimValue(getClaimAsString(TokenClaims.SAP_ID_TYPE));
+ }
}
diff --git a/java-security/src/test/java/com/sap/cloud/security/token/DefaultIdTokenExtensionTest.java b/java-security/src/test/java/com/sap/cloud/security/token/DefaultIdTokenExtensionTest.java
index 2615d84af..c2c9bb006 100644
--- a/java-security/src/test/java/com/sap/cloud/security/token/DefaultIdTokenExtensionTest.java
+++ b/java-security/src/test/java/com/sap/cloud/security/token/DefaultIdTokenExtensionTest.java
@@ -231,4 +231,53 @@ public void resolveIdToken_singleTenantToken_doesNotIncludeAppTid()
Map