All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.sap.cds.feature.xsuaa.XsuaaUserInfoProvider Maven / Gradle / Ivy

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