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

com.sap.cloud.security.ams.spring.adapter.PolicyDecisionPointPermissionEvaluator Maven / Gradle / Ivy

Go to download

Client Library for integrating Spring applications with SAP Authorization Management Service (AMS)

There is a newer version: 2.0.0
Show newest version
/************************************************************************
* © 2019-2023 SAP SE or an SAP affiliate company. All rights reserved. *
************************************************************************/
package com.sap.cloud.security.ams.spring.adapter;

import static org.springframework.web.bind.annotation.ValueConstants.DEFAULT_NONE;

import com.sap.cloud.security.ams.api.Principal;
import com.sap.cloud.security.ams.dcl.client.el.AttributeName;
import com.sap.cloud.security.ams.dcl.client.pdp.Attributes;
import com.sap.cloud.security.ams.dcl.client.pdp.PolicyDecisionPoint;
import com.sap.cloud.security.ams.dcl.client.language.DataControlLanguageScanner;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.lang.Nullable;
import org.springframework.util.ObjectUtils;

import java.util.List;
import java.util.Objects;
import java.util.Set;

class PolicyDecisionPointPermissionEvaluator {
	private static final Logger LOGGER = LoggerFactory.getLogger(PolicyDecisionPointPermissionEvaluator.class);
	public static final String MESSAGE_ATTRIBUTE_FORMAT = "An attribute shall have the " +
			"following format: :=";

	private static final String AUTHORITY_RESOURCE_PROPERTY = "sap.security.authorization.dcl.authority-resource";
	private static final String AUTHORITY_RESOURCE = System.getProperty(AUTHORITY_RESOURCE_PROPERTY, "").trim();

	private static final String NULL_VALUE = "null";

	private PolicyDecisionPointPermissionEvaluator() {
	} // hide implicit public one

	/**
	 * Calls {@code PolicyDecisionPoint} whether principal is allowed to perform the
	 * action on resource. It maps the attributes from SPEL to typed values and adds
	 * it to {@code Attributes}.
	 *
	 * @param policyDecisionPoint
	 *            the decision point
	 * @param principal
	 *            the principal for which the permissions are requested
	 * @param resource
	 *            the resource to access
	 * @param action
	 *            describes the action on resource, e.g. read / write
	 * @param attributes
	 *            an optional array of attributes (can be skipped)
	 * @return true when principal is authorized
	 * @throws NullPointerException
	 *             in case policyDecisionPoint or principal is null
	 * @throws IllegalArgumentException
	 *             in case no action and no resource and no attributes is provided
	 */
	static boolean hasPermission(PolicyDecisionPoint policyDecisionPoint, Principal principal,
			String resource,
			String action, String... attributes) {
		Objects.requireNonNull(policyDecisionPoint, "Can not check permission as policyDecisionPoint is null.");
		Objects.requireNonNull(principal, "Can not check permission as principal is null / anonymous.");
		if (Objects.isNull(action) && Objects.isNull(resource)
				&& (Objects.isNull(attributes) || attributes.length == 0)) {
			throw new IllegalArgumentException(
					"Can not check permission when action, resource and attributes are unspecified.");
		}
		Attributes input = principal.getAttributes()
				.setAction(action)
				.setResource(resource);

		if (!ObjectUtils.isEmpty(attributes)) {
			DataControlLanguageScanner scanner = DataControlLanguageScanner.create();
			for (String attribute : attributes) {

				AttributeName attributeName = parseAttributeName(scanner, attribute);
				String attributeType = parseAttributeValueType(scanner);
				Object attributeValue = parseAttributeObject(scanner, attributeType);

				if (attributeValue == null)
					break;
				input.setValue(attributeName, attributeValue);
			}
		}

		boolean isAuthorized = policyDecisionPoint.allow(input);
		LOGGER.debug("Received policyDecisionPoint.allow({}): {}", input, isAuthorized);
		LOGGER.debug(
				"Is {} authorized to perform action '{}' on resource '{}' and attributes '{}' ? {}",
				principal, action, resource, attributes, isAuthorized);
		return isAuthorized;
	}

	/**
	 * Calls {@code PolicyDecisionPoint} whether principal is allowed to perform the
	 * action on resource. It maps the attributes from SPEL to typed values and adds
	 * it to {@code Attributes}.
	 *
	 * @param policyDecisionPoint
	 *            the decision point
	 * @param principal
	 *            the principal for which the permissions are requested
	 * @param action
	 *            describes the action on resource, e.g. read / write
	 * @return true when principal is authorized
	 * @throws NullPointerException
	 *             in case policyDecisionPoint or principal is null
	 * @throws IllegalArgumentException
	 *             in case no action and no resource and no attributes is provided
	 */
	static boolean hasPermission(PolicyDecisionPoint policyDecisionPoint, Principal principal,
			String action) {
		Objects.requireNonNull(policyDecisionPoint, "Can not check permission as policyDecisionPoint is null.");
		Objects.requireNonNull(principal, "Can not check permission as principal is null.");
		Objects.requireNonNull(action, "Can not check permission as action is null.");

		Attributes input = principal.getAttributes()
				.setAction(action);
		if (!AUTHORITY_RESOURCE.isEmpty()) {
			input.setResource(AUTHORITY_RESOURCE);
		}
		boolean isAuthorized = policyDecisionPoint.allow(input);
		LOGGER.debug("Received policyDecisionPoint.allow({}): {}", input, isAuthorized);
		return isAuthorized;
	}

	private static AttributeName parseAttributeName(DataControlLanguageScanner scanner, String stringAttribute) {
		scanner.input(stringAttribute);
		scanner.skipWhitespace();

		AttributeName attributeName = scanAttributeName(scanner);
		scanner.throwOnErrror();

		scanner.skipWhitespace();
		if (!scanner.consume(':')) {
			throw new IllegalArgumentException("Missing attribute type separator ':'. " + MESSAGE_ATTRIBUTE_FORMAT);
		}
		scanner.skipWhitespace();
		return attributeName;
	}

	private static String parseAttributeValueType(DataControlLanguageScanner scanner) {
		String attributeType = scanner.scanIdentifier();
		if (!scanner.isOk()) {
			throw new IllegalArgumentException("Missing attribute type. " + MESSAGE_ATTRIBUTE_FORMAT);
		}
		if (!scanner.consume('=')) {
			throw new IllegalArgumentException("Missing attribute value separator '='. " + MESSAGE_ATTRIBUTE_FORMAT);
		}
		return attributeType;
	}

	@Nullable
	private static Object parseAttributeObject(DataControlLanguageScanner scanner, String attributeType) {
		if (scanner.getRemainingCount() == 0) {
			return null;
		}
		String attributeValue = scanner.getRemainingInput();

		switch (attributeType) {
		case "string":
			if (DEFAULT_NONE.equals(attributeValue)) {
				return null;
			}
			return attributeValue;
		case "boolean":
			if (NULL_VALUE.equals(attributeValue)) {
				return null;
			}
			return Boolean.valueOf(attributeValue);
		case "number":
			if (NULL_VALUE.equals(attributeValue)) {
				return null;
			}
			try {
				return Integer.parseInt(attributeValue);
			} catch (NumberFormatException e) {
				return Double.parseDouble(attributeValue);
			}
		default:
			throw new IllegalArgumentException(
					"Attribute type '" + attributeType
							+ "' is not supported, use one of these: string, number or boolean.");
		}
	}

	static private AttributeName scanAttributeName(DataControlLanguageScanner scanner) {
		List segments = scanner.scanQualifiedName(true);
		if (segments != null) {
			AttributeName result = AttributeName.create(segments);
			segments.clear();
			return result;
		}
		return null;
	}

	/**
	 * This is a special case, that was required with Spring integration as part of
	 * Security Configuration. The list of potential policies should never be
	 * exposed!
	 *
	 * @param policyDecisionPoint
	 *            the decision point
	 * @param principal
	 *            the principal for which the permissions are requested
	 * @return potential permission or an empty set
	 * @throws NullPointerException
	 *             in case policyDecisionPoint or principal is null
	 */
	static Set getPotentialPermissions(PolicyDecisionPoint policyDecisionPoint, Principal principal) {
		Objects.requireNonNull(policyDecisionPoint,
				"Can not derive potential permissions as policyDecisionPoint is null.");
		Objects.requireNonNull(principal, "Can not derive potential permissions as principal is null.");
		PolicyDecisionPointDclAuthoritiesExtractor extractor = new PolicyDecisionPointDclAuthoritiesExtractor(principal)
				.policyDecisionPoint(policyDecisionPoint);
		return extractor.derivePotentialAuthorities();
	}

	static boolean hasPartialPermission(final String springAuthority, final String action, final String resource) {
		LOGGER.debug("hasPartialPermission({}, {}, {})", springAuthority, action, resource);
		DclAuthority dclAuthority = DclAuthority.parseSpringAuthority(springAuthority);
		if (Objects.isNull(dclAuthority)
				|| Objects.isNull(dclAuthority.getAction())
				|| Objects.isNull(dclAuthority.getResource())) {
			return false;
		}
		DataControlLanguageScanner scanner = DataControlLanguageScanner.create();
		scanner.input(Objects.nonNull(action) ? action : "*");

		String requestedAction = scanner.scanIdentifierOrAny();
		if (requestedAction == null) {
			return false;
		}

		if (requestedAction.equals(dclAuthority.getAction())
				|| dclAuthority.getAction().equals("*")
				|| requestedAction.equals("*")) {
			scanner.input(Objects.nonNull(resource) ? resource : "*");
			String requestedResource = scanner.scanIdentifierOrAny();
			if (requestedResource == null) {
				return false;
			}
			return requestedResource.equals(dclAuthority.getResource())
					|| dclAuthority.getResource().equals("*")
					|| requestedResource.equals("*");
		}
		return false;
	}
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy