com.sap.cds.feature.xsuaa.XsuaaUserInfoProvider 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 static com.sap.cds.feature.xsuaa.XsUaaToken.GrantType.CLIENT_CREDENTIALS;
import static com.sap.cds.feature.xsuaa.XsUaaToken.GrantType.CLIENT_X509;
import java.text.MessageFormat;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import java.util.stream.Collectors;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.sap.cds.services.ErrorStatuses;
import com.sap.cds.services.ServiceException;
import com.sap.cds.services.authentication.AuthenticationInfo;
import com.sap.cds.services.authentication.JwtTokenAuthenticationInfo;
import com.sap.cds.services.request.UserInfo;
import com.sap.cds.services.runtime.CdsRuntime;
import com.sap.cds.services.runtime.UserInfoProvider;
import com.sap.cds.services.utils.ClassMethods;
import com.sap.cds.services.utils.ErrorStatusException;
import com.sap.cloud.environment.servicebinding.api.ServiceBinding;
public class XsuaaUserInfoProvider implements UserInfoProvider {
private final static Logger logger = LoggerFactory.getLogger(XsuaaUserInfoProvider.class);
private final ServiceBinding uaaInstance;
private final CdsRuntime runtime;
public XsuaaUserInfoProvider(ServiceBinding uaaInstance, CdsRuntime runtime) {
this.uaaInstance = uaaInstance;
this.runtime = runtime;
}
@Override
public UserInfo get() {
AuthenticationInfo authenticationInfo = runtime.getProvidedAuthenticationInfo();
if(authenticationInfo != null && authenticationInfo.is(JwtTokenAuthenticationInfo.class)) {
try {
JwtTokenAuthenticationInfo accessToken = authenticationInfo.as(JwtTokenAuthenticationInfo.class);
XsUaaToken jwt = XsUaaToken.parse(accessToken.getToken());
logger.debug("Decoded XSUAA token: {}", jwt.toString());
UserInfo xsuaaUser = new XsuaaUserInfoImpl(jwt);
logger.debug("Resolved {}", xsuaaUser);
return xsuaaUser;
} catch (ServiceException e) {
throw e; // just rethrow
} catch (Exception e) { // NOSONAR
throw new ErrorStatusException(ErrorStatuses.UNAUTHORIZED, e); // if there is a authentication the user must be extracted
}
} else {
return null;
}
}
/**
* Similar implementation of {@link UserInfo} as in cds-feature-identity.
* Adjustments in this class potentially need to be double maintained there as long as cds-features-xsuaa
* has not been removed.
*/
private class XsuaaUserInfoImpl implements UserInfo {
private final XsUaaToken jwt;
private final String name;
private final boolean isSystemUser;
private final boolean isInternalUser;
private final Set roles;
private final Map> attributes;
private static final String SPECIAL_ATTRIBUTE_TENANT = "tenant";
private static final String SPECIAL_ATTRIBUTE_LOGON_NAME = "logonName";
private static final String EXTENSION_ATTRIBUTES = "ext_attr";
private static final String BINDING_CLIENT_ID = "clientid";
private static final String SERVICEINSTANCEID_ATTRIBUTE = "serviceinstanceid";
private static final String SPECIAL_ATTRIBUTE_SERVICEINSTANCEID = EXTENSION_ATTRIBUTES + "." + SERVICEINSTANCEID_ATTRIBUTE;
private static final String SYSTEM_USER_NAME = "system";
private static final String INTERNAL_USER_NAME = "system-internal";
private static final String ORIGIN = "origin";
private XsuaaUserInfoImpl(XsUaaToken jwt) {
this.jwt = jwt;
this.isSystemUser = jwt.getGrantType() != null && (
jwt.getGrantType().equals(CLIENT_CREDENTIALS.toString()) || jwt.getGrantType().equals(CLIENT_X509.toString()) );
// an internal user is a technical user from the same client
this.isInternalUser = this.isSystemUser && jwt.getClientId() != null && jwt.getClientId().equals( uaaInstance.getCredentials().get(BINDING_CLIENT_ID) );
if (this.isInternalUser) {
// on CF system users have no name
this.name = INTERNAL_USER_NAME;
} else if (this.isSystemUser) {
this.name = SYSTEM_USER_NAME;
} else {
this.name = jwt.getName();
}
// filter the scopes. "$XSAPPNAME." - prefix is always directly in front of the scope (if prefixed).
// There might be additional prefixes on top, e.g. "$SERVICEINSTANCEID."
String scopePrefix = (String) uaaInstance.getCredentials().get("xsappname") + ".";
this.roles = jwt.getScopes().stream().map(scope -> {
int pos = scope.indexOf(scopePrefix);
if (pos >= 0) {
return scope.substring(pos + scopePrefix.length());
}
return scope;
}).collect(Collectors.toSet());
// add some specific user attributes ($user.tenant and $user.ext_attr.serviceinstanceid)
this.attributes = new TreeMap<>(jwt.getUserAttributes());
this.attributes.put(SPECIAL_ATTRIBUTE_TENANT, Collections.singletonList(jwt.getTenant()));
Object serviceInstanceId = jwt.getExtensionAttributes().get(SERVICEINSTANCEID_ATTRIBUTE);
if (serviceInstanceId != null && serviceInstanceId instanceof String string) {
this.attributes.put(SPECIAL_ATTRIBUTE_SERVICEINSTANCEID, Collections.singletonList(string));
}
// add logon name as created by audit log server when $USER is used
if (!this.isSystemUser) {
Object origin = jwt.getAdditionalAttributes().get(ORIGIN);
if (origin instanceof String) {
getAdditionalAttributes().put(SPECIAL_ATTRIBUTE_LOGON_NAME,
"user/%s/%s".formatted(origin, this.name));
}
}
}
@Override
public String getId() {
return jwt.getId();
}
@Override
public String getName() {
return name;
}
@Override
public String getTenant() {
return jwt.getTenant();
}
@Override
public Set getRoles() {
return roles; // NOSONAR
}
@Override
public boolean isSystemUser() {
return isSystemUser;
}
@Override
public boolean isInternalUser() {
return isInternalUser; // implies isSystemUser
}
@Override
public boolean isAuthenticated() {
// as there is an accepted token we can be sure that the user is authenticated
return true;
}
@Override
public boolean isPrivileged() {
return false; // XSUAA user is never a privileged user!
}
@Override
public Map> getAttributes() {
return attributes; // NOSONAR
}
@Override
public Map getAdditionalAttributes() {
return jwt.getAdditionalAttributes(); // NOSONAR
}
@Override
public T as(Class userInfoClazz) {
return ClassMethods.as(userInfoClazz, UserInfo.class, this, this::getAdditionalAttributes);
}
@Override
public String toString() {
return MessageFormat.format("XsuaaUserInfo [id=''{0}'', name=''{1}'', roles=''{2}'', attributes=''{3}''",
getId(), getName(), getRoles(), getAttributes());
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy