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

com.sap.cloud.security.ams.api.PrincipalBuilder Maven / Gradle / Ivy

/************************************************************************
* © 2019-2023 SAP SE or an SAP affiliate company. All rights reserved. *
************************************************************************/
package com.sap.cloud.security.ams.api;

import com.sap.cloud.security.ams.dcl.client.pdp.Attributes;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.*;

import static com.sap.cloud.security.ams.api.Principal.*;

/**
 * Builder for Principals. This allows adding the assigned Authorization Management
 * principals.
 */
@SuppressWarnings("java:S3740")
public class PrincipalBuilder {
	private static final String CLASS_SECURITY_CONTEXT_HOLDER = "org.springframework.security.core.context.SecurityContextHolder";
	private static final String CLASS_SECURITY_CONTEXT = "com.sap.cloud.security.token.SecurityContext";
	private static final String CLASS_SPRING_SECURITY_CONTEXT = "com.sap.cloud.security.adapter.spring.SpringSecurityContext";
	static Class securityContextHolder; // spring
	static Class securityContext; // java-security
	static Class springSecurityContext; // java-security (adapter package)
	private final PrincipalImpl principal;
	private String authorizationParty;
	private static final Logger LOGGER = LoggerFactory.getLogger(PrincipalBuilder.class);
	static final String CLAIM_TEST_POLICIES = "test_policies";
	static final String CLAIM_EMAIL = "email";
	static final String CLAIM_GROUPS = "groups";
	static final String CLAIM_AZP = "azp";
	static final String CLAIM_AUD = "aud";
	static final List ATTRIBUTES_PROCESSORS = AttributesProcessor.getAll();

	static {
		try {
			Class.forName(CLASS_SECURITY_CONTEXT_HOLDER, false, PrincipalBuilder.class.getClassLoader());
			securityContextHolder = Class.forName(CLASS_SECURITY_CONTEXT_HOLDER);
		} catch (ClassNotFoundException e) {
			LOGGER.info(
					"PrincipalBuilder cannot instantiate Principal from SecurityContextHolder: ClassNotFoundException");
		}
		try {
			Class.forName(CLASS_SECURITY_CONTEXT, false, PrincipalBuilder.class.getClassLoader());
			securityContext = Class.forName(CLASS_SECURITY_CONTEXT);
		} catch (ClassNotFoundException e) {
			LOGGER.info("PrincipalBuilder cannot instantiate Principal from SecurityContext: ClassNotFoundException");
		}
		try {
			Class.forName(CLASS_SPRING_SECURITY_CONTEXT, false, PrincipalBuilder.class.getClassLoader());
			springSecurityContext = Class.forName(CLASS_SPRING_SECURITY_CONTEXT);
		} catch (ClassNotFoundException e) {
			LOGGER.info(
					"PrincipalBuilder cannot instantiate Principal from SpringSecurityContext: ClassNotFoundException");
		}
	}

	private PrincipalBuilder() {
		principal = new PrincipalImpl();
	}

	/**
	 * Instantiates a free-style builder instance.
	 *
	 * @return principal builder.
	 */
	public static PrincipalBuilder create() {
		return new PrincipalBuilder();
	}

	/**
	 * Instantiates a principal builder instance with given oidc token attributes.
	 * The tenant identifier is taken from "app_tid" or alternatively from "zid". The
	 * principal id is taken from "user_uuid" or alternatively from "sub".
	 *
	 * @param oidcTokenAttributes
	 *            key-value map of attributes and its values, e.g. from the OIDC
	 *            token.
	 * @return principal builder.
	 */
	public static PrincipalBuilder create(Map oidcTokenAttributes) {
		String appTid = (String) oidcTokenAttributes.getOrDefault(CLAIM_APP_TID_KEY,
				oidcTokenAttributes.get(Principal.CLAIM_ZID));
		String userId = (String) oidcTokenAttributes.getOrDefault(CLAIM_USER_UUID_KEY,
				oidcTokenAttributes.get(Principal.CLAIM_SUBJECT));
		Object groups = oidcTokenAttributes.getOrDefault(CLAIM_GROUPS, Collections.emptyList());
		Object policies = oidcTokenAttributes.getOrDefault(CLAIM_TEST_POLICIES, Collections.emptyList());

		PrincipalBuilder principalBuilder = new PrincipalBuilder()
				.appTid(appTid)
				.id(userId)
				.scimId((String) oidcTokenAttributes.get(CLAIM_SCIM_ID_KEY))
				.clientId((String) getClientId(oidcTokenAttributes))
				.email((String) oidcTokenAttributes.get(CLAIM_EMAIL));
		if (policies instanceof Collection) {
			principalBuilder.addPolicies((Collection) policies);
		}
		if (groups instanceof List) {
			principalBuilder.groups((List) groups);
		}
		return principalBuilder;
	}

	private static Object getClientId(Map oidcTokenAttributes) {
		if (oidcTokenAttributes.containsKey(CLAIM_AZP)) {
			return oidcTokenAttributes.get(CLAIM_AZP);
		} else if (oidcTokenAttributes.containsKey(CLAIM_AUD)) {
			Object aud = oidcTokenAttributes.get(CLAIM_AUD);
			if (aud instanceof String) {
				return aud;
			} else if (aud instanceof List) {
				return ((List) aud).get(0);
			}
		}
		return null;
	}

	/**
	 * Instantiates a builder instance with a given tenant id and principal identifier.
	 *
	 * @param appTid
	 *            the unique tenant identifier
	 * @param id
	 *            in case of user principal - the user id
	 *
	 * @return principal builder.
	 */
	public static PrincipalBuilder create(String appTid, String id) {
		return new PrincipalBuilder().appTid(appTid).id(id);
	}

	/**
	 * Tries to build a {@link Principal} instance with an OIDC token that is stored
	 * thread-locally in the {@code com.sap.cloud.security.token.SecurityContext}
	 * class.
	 * 

* The SecurityContext class has to be provided with this dependency: {@code * * com.sap.cloud.security * java-api * * } Use {@code Principal.create()}. * * @return principal */ static Principal buildFromSecurityContext() { if (securityContext == null) { return null; } try { LOGGER.debug("Try to build Principal based on token in SecurityContext."); return createPrincipalFromSecurityContext(securityContext); } catch (ReflectiveOperationException e) { LOGGER.warn("Principal cannot be instantiated from SecurityContext: {}", e.getMessage()); } return null; } /** * Tries to build a {@link Principal} instance with a token that is stored in * the Spring's * {@code org.springframework.security.core.context.SecurityContextHolder} * class. *

* The SecurityContextHolder class has to be prepared with this dependency: * {@code * * com.sap.cloud.security.ams.client * spring-ams * * * com.sap.cloud.security * spring-security * * } * * Use {@code Principal.create()}. * * @return principal */ static Principal buildFromSecurityContextHolder() { if (securityContextHolder == null) { return null; } if (springSecurityContext != null) { try { LOGGER.debug("Try to build Principal based on token in SpringSecurityContext."); return createPrincipalFromSecurityContext(springSecurityContext); } catch (ReflectiveOperationException e) { // this is always true for Spring oauth2client integration LOGGER.info("Principal cannot be instantiated from SpringSecurityContext.getToken(): {}", e.getMessage()); } } try { LOGGER.debug("Try to build Principal based on token in SecurityContextHolder."); return fallbackForOidcTestPolicies(securityContextHolder); } catch (ReflectiveOperationException e) { LOGGER.error("Principal cannot be instantiated from SecurityContextHolder: {}", e.getMessage()); } return null; } private static Principal fallbackForOidcTestPolicies(Class securityContextHolder) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException { Method getContext = securityContextHolder.getDeclaredMethod("getContext"); Object securityContext = getContext.invoke(null); Method getAuthentication = securityContext.getClass().getMethod("getAuthentication"); Object authentication = getAuthentication.invoke(securityContext); Method isAuthenticated = authentication.getClass().getMethod("isAuthenticated"); if ((boolean) isAuthenticated.invoke(authentication)) { Method getPrincipal = authentication.getClass().getMethod("getPrincipal"); Object token = getPrincipal.invoke(authentication); String tokenClass = token.getClass().getName(); LOGGER.debug("Try to instantiate Principal from SecurityContextHolder (Token class: {})", tokenClass); if (tokenClass.startsWith("org.springframework.security.oauth2.core.oidc.user")) { Method getClaims = token.getClass().getMethod("getClaims"); return PrincipalBuilder.create((Map) getClaims.invoke(token)).build(); } } return null; } private static Principal createPrincipalFromSecurityContext(Class securityContext) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException { Method getToken = securityContext.getDeclaredMethod("getToken"); Object tokenClass = getToken.invoke(null); if (tokenClass == null) { return null; } Method getAppTid = tokenClass.getClass().getMethod("getAppTid"); Object appTid = getAppTid.invoke(tokenClass); Method getClaimAsString = tokenClass.getClass().getMethod("getClaimAsString", String.class); Object userId = getClaimAsString.invoke(tokenClass, CLAIM_USER_UUID_KEY); if (Objects.isNull(userId)) { userId = getClaimAsString.invoke(tokenClass, Principal.CLAIM_SUBJECT); } Object scimId = getClaimAsString.invoke(tokenClass, CLAIM_SCIM_ID_KEY); Object email = getClaimAsString.invoke(tokenClass, CLAIM_EMAIL); Method getClaimAsStringList = tokenClass.getClass().getMethod("getClaimAsStringList", String.class); Object groups = getClaimAsStringList.invoke(tokenClass, CLAIM_GROUPS); Object policies = getClaimAsStringList.invoke(tokenClass, CLAIM_TEST_POLICIES); Method getClientId = tokenClass.getClass().getMethod("getClientId"); Object clientId = getClientId.invoke(tokenClass); return PrincipalBuilder.create() .clientId((String) clientId) .appTid(Objects.nonNull(appTid) ? appTid.toString() : null) .id(Objects.nonNull(userId) ? userId.toString() : null) .scimId(Objects.nonNull(scimId) ? scimId.toString() : null) .email(Objects.nonNull(email) ? email.toString() : null) .groups(Objects.nonNull(groups) ? (List) groups : null) .addPolicies((Collection) policies).build(); } /** * Sets the unique principal id. * * @param id * if principal is user, the user id * @return the builder itself */ public PrincipalBuilder id(String id) { if (id != null && !id.equals(authorizationParty)) { // make sure it's NOT a credential type token principal.setId(id); } return this; } PrincipalBuilder scimId(String scimId) { principal.setScimId(scimId); return this; } /** * @deprecated use {@link #appTid(String)} instead * Will be removed with version 0.16.0 */ @Deprecated public PrincipalBuilder zoneId(String zoneId) { principal.setAppTid(zoneId); return this; } /** * Sets the unique tenant id * * @param appTid * the unique tenant identifier * @return the builder itself */ public PrincipalBuilder appTid(String appTid) { principal.setAppTid(appTid); return this; } /** * Sets the authorization party to make sure that only in case of OIDC token the * user id is set. * * @param authorizationParty * the authorization party * @return the builder itself */ PrincipalBuilder clientId(String authorizationParty) { this.authorizationParty = authorizationParty; if (authorizationParty != null && authorizationParty.equals(principal.getId())) { principal.setId(null); // in this case it's a credential type token } return this; } /** * Sets the Email of principal. * * @param email * the email * @return the builder itself */ public PrincipalBuilder email(String email) { principal.setEmail(email); return this; } /** * Sets the groups of principal. * * @param groups * the groups * @return the builder itself */ public PrincipalBuilder groups(List groups) { principal.setGroups(groups); return this; } /** * Adds a single Authorization Management policy. * * @param policy, * the policy to be added * @return the builder itself */ public PrincipalBuilder addPolicy(String policy) { if (Objects.nonNull(policy)) { principal.addPolicy(policy); } return this; } /** * Adds a list of Authorization Management policies. * * @param policies, * the list of policies * @return the builder itself */ public PrincipalBuilder addPolicies(Collection policies) { if (Objects.nonNull(policies)) { principal.addPolicies(policies); } return this; } /** * Builds and returns the principal object. * * @return the build principal or null, if the data is insufficient. */ public Principal build() { if (Objects.nonNull(principal.getAppTid()) || principal.hasPolicies()) { LOGGER.debug("Created {}.", principal); principal.setAttributes(createPredefinedAttributes()); for (AttributesProcessor processor : ATTRIBUTES_PROCESSORS) { processor.processAttributes(principal); LOGGER.debug("Overwrite attributes with customers: {}.", principal.getAttributes()); } return principal; } LOGGER.warn("Principal creation for Id '{}' (Tenant '{}') failed.", principal.getId(), principal.getAppTid()); LOGGER.debug("Principal has test policies ? {}.", principal.hasPolicies()); return null; } private Attributes createPredefinedAttributes() { HashMap userMap = new HashMap<>(); Attributes attributes = Attributes.create(); if (principal.hasPolicies()) { LOGGER.debug("Attributes.setPolicies({})", principal.getPolicies()); attributes.setPolicies(principal.getPolicies()); } else if (principal.getScimId() != null || principal.getId() != null) { LOGGER.debug("Attributes.setPrincipalToPolicy({})", principal.getToPolicyMapping()); attributes.setPrincipalToPolicies(principal.getToPolicyMapping()); } if (principal.hasAppTid()) { attributes.setTenant(principal.getAppTid()); } if (principal.getEmail() != null) { userMap.put(CLAIM_EMAIL, principal.getEmail()); } if (principal.getGroups() != null && !principal.getGroups().isEmpty()) { userMap.put(CLAIM_GROUPS, principal.getGroups()); } if (principal.getId() != null) { userMap.put(CLAIM_USER_UUID_KEY, principal.getId()); } if (!userMap.isEmpty()) { attributes.env().value("$user", userMap).attributes(); } return attributes; } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy