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

pl.edu.icm.unity.engine.forms.reg.RegistrationsManagementImpl Maven / Gradle / Ivy

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

import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.UUID;

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 pl.edu.icm.unity.MessageSource;
import pl.edu.icm.unity.base.capacityLimit.CapacityLimitName;
import pl.edu.icm.unity.base.msgtemplates.reg.AcceptRegistrationTemplateDef;
import pl.edu.icm.unity.base.msgtemplates.reg.InvitationTemplateDef;
import pl.edu.icm.unity.base.msgtemplates.reg.RejectRegistrationTemplateDef;
import pl.edu.icm.unity.base.msgtemplates.reg.SubmitRegistrationTemplateDef;
import pl.edu.icm.unity.base.msgtemplates.reg.UpdateRegistrationTemplateDef;
import pl.edu.icm.unity.base.utils.Log;
import pl.edu.icm.unity.engine.api.RegistrationsManagement;
import pl.edu.icm.unity.engine.api.authn.LoginSession;
import pl.edu.icm.unity.engine.api.notification.NotificationProducer;
import pl.edu.icm.unity.engine.api.registration.FormAutomationSupport;
import pl.edu.icm.unity.engine.authz.AuthzCapability;
import pl.edu.icm.unity.engine.authz.InternalAuthorizationManager;
import pl.edu.icm.unity.engine.capacityLimits.InternalCapacityLimitVerificator;
import pl.edu.icm.unity.engine.credential.CredentialReqRepository;
import pl.edu.icm.unity.engine.events.InvocationEventProducer;
import pl.edu.icm.unity.engine.forms.BaseFormValidator;
import pl.edu.icm.unity.engine.forms.InvitationPrefillInfo;
import pl.edu.icm.unity.engine.forms.RegistrationConfirmationSupport;
import pl.edu.icm.unity.engine.forms.RegistrationConfirmationSupport.Phase;
import pl.edu.icm.unity.exceptions.EngineException;
import pl.edu.icm.unity.store.api.generic.InvitationDB;
import pl.edu.icm.unity.store.api.generic.RegistrationFormDB;
import pl.edu.icm.unity.store.api.generic.RegistrationRequestDB;
import pl.edu.icm.unity.store.api.tx.Transactional;
import pl.edu.icm.unity.store.api.tx.TransactionalRunner;
import pl.edu.icm.unity.types.registration.AdminComment;
import pl.edu.icm.unity.types.registration.RegistrationContext;
import pl.edu.icm.unity.types.registration.RegistrationContext.TriggeringMode;
import pl.edu.icm.unity.types.registration.RegistrationForm;
import pl.edu.icm.unity.types.registration.RegistrationFormNotifications;
import pl.edu.icm.unity.types.registration.RegistrationRequest;
import pl.edu.icm.unity.types.registration.RegistrationRequestAction;
import pl.edu.icm.unity.types.registration.RegistrationRequestState;
import pl.edu.icm.unity.types.registration.RegistrationRequestStatus;
import pl.edu.icm.unity.types.registration.invite.InvitationParam.InvitationType;
import pl.edu.icm.unity.types.registration.invite.InvitationWithCode;

/**
 * Implementation of registrations subsystem.
 * 
 * @author K. Benedyczak
 */
@Component
@Primary
@InvocationEventProducer
public class RegistrationsManagementImpl implements RegistrationsManagement
{
	private static final Logger log = Log.getLogger(Log.U_SERVER_FORMS, RegistrationsManagementImpl.class);
	private RegistrationFormDB formsDB;
	private InvitationDB invitationDB;
	private RegistrationRequestDB requestDB;
	private CredentialReqRepository credentialReqRepository;
	private RegistrationConfirmationSupport confirmationsSupport;
	private InternalAuthorizationManager authz;
	private NotificationProducer notificationProducer;

	private SharedRegistrationManagment internalManagment;
	private MessageSource msg;
	private TransactionalRunner tx;
	private RegistrationRequestPreprocessor registrationRequestValidator;
	private BaseFormValidator baseValidator;
	private InternalCapacityLimitVerificator capacityLimitVerificator;

	@Autowired
	public RegistrationsManagementImpl(RegistrationFormDB formsDB, InvitationDB invitationDB,
			RegistrationRequestDB requestDB, CredentialReqRepository credentialReqDB,
			RegistrationConfirmationSupport confirmationsSupport, InternalAuthorizationManager authz,
			NotificationProducer notificationProducer, SharedRegistrationManagment internalManagment, MessageSource msg,
			TransactionalRunner tx, RegistrationRequestPreprocessor registrationRequestValidator,
			BaseFormValidator baseValidator, InternalCapacityLimitVerificator capacityLimitVerificator)
	{
		this.formsDB = formsDB;
		this.invitationDB = invitationDB;
		this.requestDB = requestDB;
		this.credentialReqRepository = credentialReqDB;
		this.confirmationsSupport = confirmationsSupport;
		this.authz = authz;
		this.notificationProducer = notificationProducer;
		this.internalManagment = internalManagment;
		this.msg = msg;
		this.tx = tx;
		this.registrationRequestValidator = registrationRequestValidator;
		this.baseValidator = baseValidator;
		this.capacityLimitVerificator = capacityLimitVerificator;
	}

	@Override
	@Transactional
	public void addForm(RegistrationForm form) throws EngineException
	{
		authz.checkAuthorization(AuthzCapability.maintenance);
		capacityLimitVerificator.assertInSystemLimitForSingleAdd(CapacityLimitName.RegistrationFormsCount,
				() -> formsDB.getCount());
		validateFormContents(form);
		formsDB.create(form);
	}

	@Override
	@Transactional
	public void removeForm(String formId, boolean dropRequests) throws EngineException
	{
		authz.checkAuthorization(AuthzCapability.maintenance);
		internalManagment.dropOrValidateFormRequests(formId, dropRequests);
		formsDB.delete(formId);
	}

	@Override
	@Transactional
	public void removeFormWithoutDependencyChecking(String formId) throws EngineException
	{
		authz.checkAuthorization(AuthzCapability.maintenance);
		internalManagment.dropOrValidateFormRequests(formId, true);
		formsDB.deleteWithoutDependencyChecking(formId);
	}

	@Override
	@Transactional
	public void updateForm(RegistrationForm updatedForm, boolean ignoreRequestsAndInvitations) throws EngineException
	{
		authz.checkAuthorization(AuthzCapability.maintenance);
		validateFormContents(updatedForm);
		String formId = updatedForm.getName();
		if (!ignoreRequestsAndInvitations)
		{
			internalManagment.validateIfHasPendingRequests(formId);
			internalManagment.validateIfHasInvitations(updatedForm, InvitationType.REGISTRATION);
		}
		formsDB.update(updatedForm);
	}

	@Override
	@Transactional
	public List getForms() throws EngineException
	{
		authz.checkAuthorization(AuthzCapability.readInfo);
		return formsDB.getAll();
	}

	@Override
	public String submitRegistrationRequest(RegistrationRequest request, final RegistrationContext context)
			throws EngineException
	{
		authz.checkAuthorization(AuthzCapability.maintenance);
		RegistrationRequestState requestFull = new RegistrationRequestState();
		requestFull.setStatus(RegistrationRequestStatus.pending);
		requestFull.setRequest(request);
		requestFull.setRequestId(UUID.randomUUID().toString());
		requestFull.setTimestamp(new Date());
		requestFull.setRegistrationContext(context);
		
		FormWithInvitation formWithInvitation = recordRequestAndReturnForm(requestFull);

		sendSubmitNotificationToAdminGroup(formWithInvitation.form, requestFull);
		internalManagment.sendInvitationProcessedNotificationIfNeeded(formWithInvitation.form, formWithInvitation.invitation, requestFull);
		
		Long entityId = tryAutoProcess(formWithInvitation.form, requestFull, context);

		tx.runInTransactionThrowing(() ->
		{
			confirmationsSupport.sendAttributeConfirmationRequest(requestFull, entityId, formWithInvitation.form, Phase.ON_SUBMIT);
			confirmationsSupport.sendIdentityConfirmationRequest(requestFull, entityId, formWithInvitation.form, Phase.ON_SUBMIT);
		});

		return requestFull.getRequestId();
	}

	private FormWithInvitation recordRequestAndReturnForm(RegistrationRequestState requestFull) throws EngineException
	{
		return tx.runInTransactionRetThrowing(() ->
		{
			RegistrationRequest request = requestFull.getRequest();
			RegistrationForm form = formsDB.get(request.getFormId());
			InvitationPrefillInfo invitationPrefillInfo;
			if (isCredentialsValidationSkipped(requestFull.getRegistrationContext().triggeringMode))
				invitationPrefillInfo = registrationRequestValidator.validateSubmittedRequestExceptCredentials(form, request, true);
			else
				invitationPrefillInfo = registrationRequestValidator.validateSubmittedRequest(form, request, true);
			requestDB.create(requestFull);
			return new FormWithInvitation(form, invitationPrefillInfo);
		});
	}

	/**
	 * When user enters the registration form after selecting an option of remote
	 * authentication to fill out a form, the credentials are filtered out by
	 * default and not available to the user. This behavior is fixed, meaning no
	 * configuration option to control this.
	 */
	private boolean isCredentialsValidationSkipped(TriggeringMode mode)
	{
		return mode == TriggeringMode.afterRemoteLoginFromRegistrationForm;
	}

	private void sendSubmitNotificationToAdminGroup(RegistrationForm form, RegistrationRequestState requestFull)
			throws EngineException
	{
		RegistrationFormNotifications notificationsCfg = form.getNotificationsConfiguration();
		if (notificationsCfg.getSubmittedTemplate() != null && notificationsCfg.getAdminsNotificationGroup() != null)
		{
			Map params = internalManagment.getBaseNotificationParams(form.getName(),
					requestFull.getRequestId());
			notificationProducer.sendNotificationToGroup(notificationsCfg.getAdminsNotificationGroup(),
					notificationsCfg.getSubmittedTemplate(), params, msg.getDefaultLocaleCode());
		}
	}

	private Long tryAutoProcess(RegistrationForm form, RegistrationRequestState requestFull,
			RegistrationContext context) throws EngineException
	{
		try
		{
			return tx.runInTransactionRetThrowing(() ->
			{
				return internalManagment.autoProcess(form, requestFull, "Automatic processing of the request "
						+ requestFull.getRequestId() + " of form " + form.getName() + " invoked, action: {0}");
			});
		} catch (Exception e)
		{
			log.warn("Auto processing of a request failed", e);
			recordAutoAcceptFailureInRequestComment(requestFull, e);
			throw e;
		}
	}

	private void recordAutoAcceptFailureInRequestComment(RegistrationRequestState requestFull, Exception e)
	{
		try
		{
			tx.runInTransaction(() ->
			{
				RegistrationRequestState unprocessedRequest = requestDB.get(requestFull.getRequestId());
				unprocessedRequest.getAdminComments()
						.add(new AdminComment("Automatic request processing failed: " + e.getMessage(), 0, false));
				requestDB.update(unprocessedRequest);
			});
		} catch (Exception e2)
		{
			log.error("Can not record failure of auto-processing in requests messages", e2);
		}
	}

	@Override
	@Transactional
	public List getRegistrationRequests() throws EngineException
	{
		authz.checkAuthorization(AuthzCapability.read);
		return requestDB.getAll();
	}

	@Override
	@Transactional
	public RegistrationRequestState getRegistrationRequest(String id) throws EngineException
	{
		authz.checkAuthorization(AuthzCapability.read);
		return requestDB.get(id);
	}

	@Override
	@Transactional
	public boolean hasForm(String id)
	{
		authz.checkAuthorizationRT("/", AuthzCapability.read);
		return formsDB.exists(id);
	}

	@Override
	@Transactional
	public void processRegistrationRequest(String id, RegistrationRequest finalRequest,
			RegistrationRequestAction action, String publicCommentStr, String internalCommentStr) throws EngineException
	{
		authz.checkAuthorization(AuthzCapability.credentialModify, AuthzCapability.attributeModify,
				AuthzCapability.identityModify, AuthzCapability.groupModify);
		RegistrationRequestState currentRequest = requestDB.get(id);

		LoginSession client = internalManagment.preprocessRequest(finalRequest, currentRequest, action);

		AdminComment publicComment = internalManagment.preprocessComment(currentRequest, publicCommentStr, client,
				true);
		AdminComment internalComment = internalManagment.preprocessComment(currentRequest, internalCommentStr, client,
				false);

		RegistrationForm form = formsDB.get(currentRequest.getRequest().getFormId());

		switch (action)
		{
		case drop:
			internalManagment.dropRequest(id);
			break;
		case reject:
			internalManagment.rejectRequest(form, currentRequest, publicComment, internalComment);
			break;
		case update:
			updateRequest(form, currentRequest, publicComment, internalComment);
			break;
		case accept:
			internalManagment.acceptRequest(form, currentRequest, publicComment, internalComment, true);
			break;
		}
	}

	private void updateRequest(RegistrationForm form, RegistrationRequestState currentRequest,
			AdminComment publicComment, AdminComment internalComment) throws EngineException
	{
		registrationRequestValidator.validateSubmittedRequest(form, currentRequest.getRequest(), false);
		requestDB.update(currentRequest);
		RegistrationFormNotifications notificationsCfg = form.getNotificationsConfiguration();
		internalManagment.sendProcessingNotification(notificationsCfg.getUpdatedTemplate(), currentRequest,
				form.getName(), false, publicComment, internalComment, notificationsCfg);
	}

	private void validateFormContents(RegistrationForm form) throws EngineException
	{
		baseValidator.validateBaseFormContents(form);

		if (form.isByInvitationOnly())
		{
			if (!form.isPubliclyAvailable())
				throw new IllegalArgumentException("Registration form which " + "is by invitation only must be public");
			if (form.getRegistrationCode() != null)
				throw new IllegalArgumentException(
						"Registration form which " + "is by invitation only must not have a static registration code");
		}

		if (form.getDefaultCredentialRequirement() == null)
			throw new IllegalArgumentException("Credential requirement must be set for the form");
		if (credentialReqRepository.get(form.getDefaultCredentialRequirement()) == null)
			throw new IllegalArgumentException(
					"Credential requirement " + form.getDefaultCredentialRequirement() + " does not exist");

		RegistrationFormNotifications notCfg = form.getNotificationsConfiguration();
		if (notCfg == null)
			throw new IllegalArgumentException("NotificationsConfiguration must be set in the form.");
		baseValidator.checkTemplate(notCfg.getAcceptedTemplate(), AcceptRegistrationTemplateDef.NAME,
				"accepted registration request");
		baseValidator.checkTemplate(notCfg.getRejectedTemplate(), RejectRegistrationTemplateDef.NAME,
				"rejected registration request");
		baseValidator.checkTemplate(notCfg.getSubmittedTemplate(), SubmitRegistrationTemplateDef.NAME,
				"submitted registration request");
		baseValidator.checkTemplate(notCfg.getUpdatedTemplate(), UpdateRegistrationTemplateDef.NAME,
				"updated registration request");
		baseValidator.checkTemplate(notCfg.getInvitationTemplate(), InvitationTemplateDef.NAME, "invitation");
		if (form.getCaptchaLength() > RegistrationForm.MAX_CAPTCHA_LENGTH)
			throw new IllegalArgumentException(
					"Captcha can not be longer then " + RegistrationForm.MAX_CAPTCHA_LENGTH + " characters");
		if (form.getTitle2ndStage() != null)
		{
			baseValidator.validateFreemarkerTemplate("Second stage title", form.getTitle2ndStage());
		}

	}

	@Override
	@Transactional
	public FormAutomationSupport getFormAutomationSupport(RegistrationForm form)
	{
		return confirmationsSupport.getRegistrationFormAutomationSupport(form);
	}

	@Override
	@Transactional
	public RegistrationForm getForm(String id) throws EngineException
	{
		return formsDB.get(id);
	}

	public InvitationWithCode getInvitation(String code) throws EngineException
	{
		return tx.runInTransactionRet(() -> invitationDB.get(code));
	}
	
	private static class FormWithInvitation
	{
		public final RegistrationForm form;
		public final InvitationPrefillInfo invitation;
		
		public FormWithInvitation(RegistrationForm form, InvitationPrefillInfo invitation)
		{	
			this.form = form;
			this.invitation = invitation;
		}
	}
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy