com.sap.cds.feature.xsuaa.XsUaaToken Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of cds-feature-xsuaa Show documentation
Show all versions of cds-feature-xsuaa Show documentation
API implementation to handle oauth2 tokens (JWT) in XSUAA format
The newest version!
/**************************************************************************
* (C) 2019-2024 SAP SE or an SAP affiliate company. All rights reserved. *
**************************************************************************/
package com.sap.cds.feature.xsuaa;
import java.util.ArrayList;
import java.util.Base64;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import com.fasterxml.jackson.annotation.JsonAnySetter;
import com.fasterxml.jackson.annotation.JsonInclude.Include;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.sap.cds.services.utils.CdsErrorStatuses;
import com.sap.cds.services.utils.ErrorStatusException;
/**
* A parser that converts Base64-encoded bearer tokens issued by XSUAA.
*/
public class XsUaaToken {
private static final String BEARER_TOKEN = "bearer";
private static final String CLAIM_GIVEN_NAME = "given_name";
private static final String CLAIM_FAMILY_NAME = "family_name";
private static final String CLAIM_ZDN = "zdn";
// mandatory properties:
private String id;
private String tenant;
private String grantType;
// optional properties:
@JsonProperty("user_name")
private String name;
@JsonProperty("scope")
private List scopes = new ArrayList<>();
@JsonProperty("client_id")
private String clientId;
@JsonProperty("xs.user.attributes")
private Map> userAttributes = new HashMap<>();
@JsonProperty("xs.system.attributes")
private Map> systemAttributes = new HashMap<>();
@JsonProperty("ext_attr")
private Map extendedAttributes = new HashMap<>();
@JsonAnySetter
// all other not explicitly mapped attributes are collected in this map:
private Map additionalAttributes = new HashMap<>();
/**
* Grant types of special interest
*/
public static enum GrantType {
CLIENT_CREDENTIALS("client_credentials"),
CLIENT_X509("client_x509");
public final String id;
GrantType(String id) {
this.id = id;
}
@Override
public String toString() {
return this.id;
}
}
/**
* The shared JSON object mapper
*/
private static ObjectMapper jsonMapper = createObjectMapper();
/**
* Constructor for JSON parser. user_id, zid and grant_typ must be present for
* construction.
*
* @param id The user id
* @param zid The tenant
* @param grantType The grant type of this token
*/
private XsUaaToken(
@JsonProperty(value = "user_id", required = false) String id,
@JsonProperty(value = "zid", required = true) String zid,
@JsonProperty(value = "grant_type", required = true) String grantType
) {
this.id = id;
this.tenant = zid;
this.grantType = grantType;
}
/**
* Tries to extract the token representation from the raw authorization header value.
*
* @param authorizationHeader The authorization header value
* @return The token representation
*
* @throws IllegalArgumentException in case the token could not be parsed.
*/
public static XsUaaToken parse(String authorizationHeader) {
XsUaaToken decodedJwtToken = decodeJwtToken(authorizationHeader);
decodedJwtToken.initComputedAttributes();
return decodedJwtToken;
}
private void initComputedAttributes() {
// provide whole ext_attr structure
additionalAttributes.put("ext_attr", extendedAttributes);
// some extension attributes overrule the direct token attributes
String givenName = tryCastString(getExtensionAttributes().get(CLAIM_GIVEN_NAME));
if(givenName == null) {
givenName = tryCastString(additionalAttributes.get(CLAIM_GIVEN_NAME));
}
additionalAttributes.put("givenName", givenName); // needs to match XsuaaUserInfo::getGivenName()
String familyName = tryCastString(getExtensionAttributes().get(CLAIM_FAMILY_NAME));
if(familyName == null) {
familyName = tryCastString(additionalAttributes.get(CLAIM_FAMILY_NAME));
}
additionalAttributes.put("familyName", familyName); // needs to match XsuaaUserInfo::getFamilyName()
String subDomain = tryCastString(getExtensionAttributes().get(CLAIM_ZDN));
additionalAttributes.put("subDomain", subDomain); // needs to match XsuaaUserInfo::getSubDomain()
// TODO: ensure method names
// XsuaaUserInfo.class.getMethod(givenName, parameterTypes)
}
private String tryCastString(Object value) {
return value instanceof String s ? s : null;
}
private static XsUaaToken decodeJwtToken(String authorizationHeader) {
String jwtRaw = authorizationHeader.trim();
if (jwtRaw.substring(0, Math.min(BEARER_TOKEN.length(), authorizationHeader.length() - 1)).toLowerCase(Locale.ENGLISH).equals(BEARER_TOKEN)) {
jwtRaw = jwtRaw.substring(BEARER_TOKEN.length());
jwtRaw = jwtRaw.trim();
}
String[] jwtParts = jwtRaw.split("\\.");
if(jwtParts.length == 3) {
String base64EncodedBody = jwtParts[1];
String jwtInfo = new String(Base64.getUrlDecoder().decode(base64EncodedBody)); // ISO-8859-1 //NOSONAR
try {
return jsonMapper.readValue(jwtInfo, XsUaaToken.class);
} catch(Exception e) { // NOSONAR
throw new ErrorStatusException(CdsErrorStatuses.TOKEN_PARSING_FAILED, e);
}
}
throw new ErrorStatusException(CdsErrorStatuses.TOKEN_PARSING_FAILED);
}
public String getId() {
return id;
}
public String getName() {
return name;
}
public String getTenant() {
return tenant;
}
public String getGrantType() {
return grantType;
}
public String getClientId() {
return clientId;
}
public List getScopes() {
return scopes; // NOSONAR
}
public Map> getUserAttributes() {
return userAttributes;
}
public Map> getSystemAttributes() {
return systemAttributes;
}
public Map getExtensionAttributes() {
return extendedAttributes;
}
public Map getAdditionalAttributes() {
return additionalAttributes;
}
@Override
public String toString() {
try {
return jsonMapper.writeValueAsString(this);
} catch (JsonProcessingException e) {
return "";
}
}
private static ObjectMapper createObjectMapper() {
ObjectMapper mapper = new ObjectMapper();
// there are much more properties which we don't interpret (and which could change any time)
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
// attributes value lists often have only a single item which then are not encoded as arrays but as single value
mapper.configure(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY, true);
// this makes the serialization (toString()) omitting null properties
mapper.setSerializationInclusion(Include.NON_NULL);
return mapper;
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy