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

pl.edu.icm.unity.engine.attribute.AttributesManagementImpl Maven / Gradle / Ivy

/*
 * Copyright (c) 2013 ICM Uniwersytet Warszawski All rights reserved.
 * See LICENCE file for licensing information.
 */
package pl.edu.icm.unity.engine.attribute;

import static java.util.Collections.emptyList;
import static java.util.stream.Collectors.toList;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.apache.logging.log4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Primary;
import org.springframework.stereotype.Component;

import com.google.common.collect.ImmutableMap;

import pl.edu.icm.unity.base.attribute.Attribute;
import pl.edu.icm.unity.base.attribute.AttributeExt;
import pl.edu.icm.unity.base.attribute.AttributeType;
import pl.edu.icm.unity.base.attribute.IllegalAttributeValueException;
import pl.edu.icm.unity.base.audit.AuditEventAction;
import pl.edu.icm.unity.base.audit.AuditEventTag;
import pl.edu.icm.unity.base.audit.AuditEventType;
import pl.edu.icm.unity.base.entity.EntityParam;
import pl.edu.icm.unity.base.exceptions.EngineException;
import pl.edu.icm.unity.base.group.Group;
import pl.edu.icm.unity.base.utils.Log;
import pl.edu.icm.unity.engine.api.AttributesManagement;
import pl.edu.icm.unity.engine.api.attributes.AttributeClassHelper;
import pl.edu.icm.unity.engine.api.attributes.AttributeMetadataProvider;
import pl.edu.icm.unity.engine.api.attributes.AttributeMetadataProvidersRegistry;
import pl.edu.icm.unity.engine.api.authn.AuthorizationException;
import pl.edu.icm.unity.engine.api.confirmation.EmailConfirmationManager;
import pl.edu.icm.unity.engine.api.exceptions.RuntimeEngineException;
import pl.edu.icm.unity.engine.api.exceptions.SchemaConsistencyException;
import pl.edu.icm.unity.engine.api.group.GroupPattern;
import pl.edu.icm.unity.engine.api.group.IllegalGroupValueException;
import pl.edu.icm.unity.engine.api.identity.EntityResolver;
import pl.edu.icm.unity.engine.api.registration.GroupPatternMatcher;
import pl.edu.icm.unity.engine.audit.AuditEventTrigger;
import pl.edu.icm.unity.engine.audit.AuditPublisher;
import pl.edu.icm.unity.engine.authz.AuthzCapability;
import pl.edu.icm.unity.engine.authz.InternalAuthorizationManager;
import pl.edu.icm.unity.engine.authz.RoleAttributeTypeProvider;
import pl.edu.icm.unity.engine.events.InvocationEventProducer;
import pl.edu.icm.unity.engine.session.AdditionalAuthenticationService;
import pl.edu.icm.unity.store.api.AttributeDAO;
import pl.edu.icm.unity.store.api.AttributeTypeDAO;
import pl.edu.icm.unity.store.api.MembershipDAO;
import pl.edu.icm.unity.base.tx.Transactional;
import pl.edu.icm.unity.store.api.tx.TransactionalRunner;

/**
 * Implements attributes operations.
 * @author K. Benedyczak
 */
@Component
@Primary
@InvocationEventProducer
public class AttributesManagementImpl implements AttributesManagement
{
	private static final Logger log = Log.getLogger(Log.U_SERVER_CORE, AttributesManagementImpl.class);
	private AttributeClassUtil acUtil;
	private AttributeTypeDAO attributeTypeDAO;
	private AttributeDAO dbAttributes;
	private EntityResolver idResolver;
	private InternalAuthorizationManager authz;
	private AttributesHelper attributesHelper;
	private EmailConfirmationManager confirmationManager;
	private TransactionalRunner txRunner;
	private AttributeMetadataProvidersRegistry atMetaProvidersRegistry;
	private AdditionalAuthenticationService additionalAuthnService;
	private final AuditPublisher audit;
	private final MembershipDAO membershipDAO;

	@Autowired
	public AttributesManagementImpl(AttributeClassUtil acUtil,
			AttributeTypeDAO attributeTypeDAO, AttributeDAO dbAttributes,
			EntityResolver idResolver, InternalAuthorizationManager authz,
			AttributesHelper attributesHelper, EmailConfirmationManager confirmationManager,
			TransactionalRunner txRunner,
			AttributeMetadataProvidersRegistry atMetaProvidersRegistry,
			AdditionalAuthenticationService repeatedAuthnService,
			AuditPublisher audit,
			MembershipDAO membershipDAO)
	{
		this.acUtil = acUtil;
		this.attributeTypeDAO = attributeTypeDAO;
		this.dbAttributes = dbAttributes;
		this.idResolver = idResolver;
		this.authz = authz;
		this.attributesHelper = attributesHelper;
		this.confirmationManager = confirmationManager;
		this.txRunner = txRunner;
		this.atMetaProvidersRegistry = atMetaProvidersRegistry;
		this.additionalAuthnService = repeatedAuthnService;
		this.audit = audit;	
		this.membershipDAO = membershipDAO;
	}

	

	@Override
	public void createAttribute(EntityParam entity, Attribute attribute) throws EngineException
	{
		setAttribute(entity, attribute, false, true);
	}

	@Override
	public void setAttribute(EntityParam entity, Attribute attribute) throws EngineException
	{
		setAttribute(entity, attribute, true, true);
	}

	@Override
	public void createAttributeSuppressingConfirmation(EntityParam entity, Attribute attribute)
			throws EngineException
	{
		setAttribute(entity, attribute, false, false);
	}

	@Override
	public void setAttributeSuppressingConfirmation(EntityParam entity, Attribute attribute)
			throws EngineException
	{
		setAttribute(entity, attribute, true, false);
	}
	
	@Override
	public void setAttribute(EntityParam entity, Attribute attribute, boolean allowUpdate)
			throws EngineException
	{
		setAttribute(entity, attribute, allowUpdate, true);
	}
	
	private void setAttribute(EntityParam entity, Attribute attribute, boolean allowUpdate, 
			boolean sendConfirmations) throws EngineException
	{
		entity.validateInitialization(); 
		txRunner.runInTransactionThrowing(() -> {
			//Important - attributes can be also set as a result of addMember and addEntity.
			//  when changing this method, verify if those needs an update too.
			
			long entityId = idResolver.getEntityId(entity);
			AttributeType at = attributeTypeDAO.get(attribute.getName());
			boolean fullAuthz = checkSetAttributeAuthz(entityId, at, attribute);
			if (!fullAuthz)
				checkAdditionalAuthn(at);
			checkIfAllowed(entityId, attribute.getGroupPath(), attribute.getName());
			attributesHelper.addAttribute(entityId, attribute, at, allowUpdate, fullAuthz);
		});

		//this is merely to propagate the change to authz layer more quickly in typical situations. It does 
		// not guarantee that authz cache is cleared after all possible situations when roles are be altered. 
		if (RoleAttributeTypeProvider.AUTHORIZATION_ROLE.equals(attribute.getName()))
			authz.clearCache();
		if (sendConfirmations)
			confirmationManager.sendVerificationQuietNoTx(entity, attribute, false);
	}

	private void checkAdditionalAuthn(AttributeType at)
	{
		if (isSensitiveAttributeChange(at))
		{
			log.info("Additional authentication triggered for sensitive >{}< attribute change", at.getName());
			additionalAuthnService.checkAdditionalAuthenticationRequirements();
		}
	}
	
	private boolean isSensitiveAttributeChange(AttributeType at)
	{
		Set metadataSet = at.getMetadata().keySet();
		for (String meta: metadataSet)
		{
			AttributeMetadataProvider metaInfo = atMetaProvidersRegistry.getByName(meta);
			if (metaInfo.isSecuritySensitive())
				return true;
		}
		return false;
	}
	
	private boolean checkSetAttributeAuthz(long entityId, AttributeType at, Attribute attribute) 
			throws AuthorizationException
	{
		if (RoleAttributeTypeProvider.AUTHORIZATION_ROLE.equals(attribute.getName()))
		{
			authz.checkAuthZAttributeChangeAuthorization(authz.isSelf(entityId), attribute);
			return true;
		}
		
		boolean fullAuthz = hasFullAuthzToChangeAttr(attribute.getGroupPath());

		//even if we have fullAuthz we need to check authZ (e.g. to get outdated credential error)
		authz.checkAuthorization(at.isSelfModificable() && authz.isSelf(entityId), 
				attribute.getGroupPath(), AuthzCapability.attributeModify);
		return fullAuthz;
	}
	
	private boolean hasFullAuthzToChangeAttr(String groupPath) throws AuthorizationException
	{
		Set nonSelfCapabilities = authz.getCapabilities(false, groupPath);
		return nonSelfCapabilities.contains(AuthzCapability.attributeModify);
	}
	
	@Override
	@Transactional
	public void removeAttribute(EntityParam entity, String groupPath, String attributeTypeId)
			throws EngineException
	{
		if (groupPath == null)
			throw new IllegalGroupValueException("Group must not be null");
		if (attributeTypeId == null)
			throw new IllegalAttributeValueException("Attribute name must not be null");
		entity.validateInitialization();

		long entityId = idResolver.getEntityId(entity);
		AttributeType at = attributeTypeDAO.get(attributeTypeId);
		if (at.isInstanceImmutable())
			throw new SchemaConsistencyException("The attribute with name " + at.getName() + 
					" can not be manually modified");
		authz.checkAuthorization(at.isSelfModificable() && authz.isSelf(entityId),
				groupPath, AuthzCapability.attributeModify);
		boolean fullAuthz = hasFullAuthzToChangeAttr(groupPath);
		if (!fullAuthz)
			checkAdditionalAuthn(at);
		
		checkIfMandatory(entityId, groupPath, attributeTypeId);
		
		dbAttributes.deleteAttribute(attributeTypeId, entityId, groupPath);
		
		audit.log(AuditEventTrigger.builder()
				.type(AuditEventType.ATTRIBUTE)
				.action(AuditEventAction.REMOVE)
				.name(attributeTypeId)
				.subject(entityId)
				.details(ImmutableMap.of("group", groupPath))
				.tags(AuditEventTag.USERS));
	}

	@Override
	@Transactional
	public Collection getAttributes(EntityParam entity, String groupPath,
			String attributeTypeId) throws EngineException
	{
		Collection ret = getAllAttributesInternal(entity, true, groupPath,
				attributeTypeId, new AuthzCapability[] {AuthzCapability.read}, false
		);
		return filterSecuritySensitive(ret);
	}

	private Collection filterSecuritySensitive(Collection ret)
	{
		return ret.stream()
				.filter(SensitiveAttributeMatcher::isNotSensitive)
				.collect(toList());
	}


	@Override
	@Transactional
	public Collection getAllAttributes(EntityParam entity, boolean effective, String groupPath,
			String attributeTypeId, boolean allowDegrade) throws EngineException
	{
		try
		{
			return getAllAttributesInternal(entity, effective, groupPath, attributeTypeId,
				new AuthzCapability[] {AuthzCapability.readHidden, AuthzCapability.read}, true);
		} catch (AuthorizationException e)
		{
			if (allowDegrade)
			{
				return getAllAttributesInternal(entity, effective,
					groupPath, attributeTypeId,
					new AuthzCapability[] {AuthzCapability.read}, false);
			} else
				throw e;
		}
	}

	@Override
	@Transactional
	public Collection getAllAttributes(EntityParam entity, boolean effective,
			List groupPathPatterns, String attributeTypeId, boolean allowDegrade) throws EngineException
	{
		try
		{
			return getAllAttributesInternal(entity, effective, groupPathPatterns, attributeTypeId,
				new AuthzCapability[] {AuthzCapability.readHidden, AuthzCapability.read}, true);
		} catch (AuthorizationException e)
		{
			if (allowDegrade)
			{
				return getAllAttributesInternal(entity, effective,
					groupPathPatterns, attributeTypeId,
					new AuthzCapability[] {AuthzCapability.read}, false);
			} else
				throw e;
		}
	}

	@Override
	@Transactional
	public Collection getAllDirectAttributes(EntityParam entity)
	{
		authz.checkAuthorizationRT(AuthzCapability.readHidden, AuthzCapability.read);
		try
		{
			long entityId = idResolver.getEntityId(entity);
			return attributesHelper.getAllEntityAttributesMap(entityId)
					.values().stream()
					.map(Map::values)
					.flatMap(Collection::stream)
					.collect(toList());
		} catch (EngineException e)
		{
			throw new RuntimeEngineException(e);
		}
	}

	private Collection getAllAttributesInternal(EntityParam entity, boolean effective,
			List groupPathPatterns, String attributeTypeName, AuthzCapability[] requiredCapability,
			boolean allowDisabled) throws EngineException
	{
		entity.validateInitialization();
		long entityId = idResolver.getEntityId(entity);

		List groupsPaths = getGroupsPaths(groupPathPatterns, entityId);
		if(groupsPaths.isEmpty())
			return emptyList();

		for (String group : groupsPaths)
		{
			authz.checkAuthorization(authz.isSelf(entityId), group, requiredCapability);
		}
		return attributesHelper.getAttributesInternal(entityId,
				effective, groupsPaths, attributeTypeName, allowDisabled);
	}

	private Collection getAllAttributesInternal(EntityParam entity, boolean effective,
			String groupPath, String attributeTypeName, AuthzCapability[] requiredCapability, boolean allowDisabled)
			throws EngineException
	{
		entity.validateInitialization();
		long entityId = idResolver.getEntityId(entity);
		authz.checkAuthorization(authz.isSelf(entityId), groupPath, requiredCapability);
		return attributesHelper.getAttributesInternal(entityId,
			effective, groupPath, attributeTypeName, allowDisabled);
	}

	private List getGroupsPaths(List groupPathPatterns, long entityId)
	{
		List entityGroups = membershipDAO.getEntityMembershipGroups(entityId);
		List groupsPaths = new ArrayList<>();
		for(GroupPattern groupPathPattern : groupPathPatterns)
		{
			groupsPaths.addAll(getMatchingGroups(entityGroups, groupPathPattern.pattern));
		}
		return groupsPaths;
	}

	private List getMatchingGroups(List entityGroups, String groupPathPattern)
	{
		return GroupPatternMatcher.filterMatching(entityGroups, groupPathPattern).stream()
			.map(Group::getName)
			.collect(toList());
	}

	/**
	 * Verifies if the attribute is allowed wrt attribute classes defined for the entity in the respective group.
	 */
	private void checkIfAllowed(long entityId, String groupPath, String attributeTypeId) 
			throws EngineException
	{
		AttributeClassHelper acHelper = acUtil.getACHelper(entityId, groupPath);
		if (!acHelper.isAllowed(attributeTypeId))
			throw new SchemaConsistencyException("The attribute with name " + attributeTypeId + 
					" is not allowed by the entity's attribute classes in the group " + groupPath);
	}

	/**
	 * Verifies if the attribute is allowed wrt attribute classes defined for the entity in the respective group.
	 */
	private void checkIfMandatory(long entityId, String groupPath, String attributeTypeId) 
			throws EngineException
	{
		AttributeClassHelper acHelper = acUtil.getACHelper(entityId, groupPath);
		if (acHelper.isMandatory(attributeTypeId))
			throw new SchemaConsistencyException("The attribute with name " + attributeTypeId + 
					" is required by the entity's attribute classes in the group " + groupPath);
	}
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy