/************************************************************************* * * * EJBCA Community: The OpenSource Certificate Authority * * * * This software is free software; you can redistribute it and/or * * modify it under the terms of the GNU Lesser General Public * * License as published by the Free Software Foundation; either * * version 2.1 of the License, or any later version. * * * * See terms of license at gnu.org. * * * *************************************************************************/ package org.ejbca.core.ejb.approval; import java.util.Date; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import javax.ejb.EJB; import javax.ejb.Stateless; import javax.ejb.TransactionAttribute; import javax.ejb.TransactionAttributeType; import org.apache.log4j.Logger; import org.cesecore.ErrorCode; import org.cesecore.audit.enums.EventStatus; import org.cesecore.audit.log.SecurityEventsLoggerSessionLocal; import org.cesecore.authentication.AuthenticationFailedException; import org.cesecore.authentication.tokens.AuthenticationToken; import org.cesecore.authorization.AuthorizationDeniedException; import org.cesecore.authorization.AuthorizationSessionLocal; import org.cesecore.authorization.control.StandardRules; import org.cesecore.configuration.GlobalConfigurationSessionLocal; import org.cesecore.jndi.JndiConstants; import org.ejbca.config.GlobalConfiguration; import org.ejbca.core.ejb.audit.enums.EjbcaEventTypes; import org.ejbca.core.ejb.audit.enums.EjbcaModuleTypes; import org.ejbca.core.ejb.audit.enums.EjbcaServiceTypes; import org.ejbca.core.ejb.ca.caadmin.CAAdminSessionLocal; import org.ejbca.core.ejb.ra.EndEntityManagementSessionLocal; import org.ejbca.core.model.InternalEjbcaResources; import org.ejbca.core.model.approval.AdminAlreadyApprovedRequestException; import org.ejbca.core.model.approval.Approval; import org.ejbca.core.model.approval.ApprovalDataText; import org.ejbca.core.model.approval.ApprovalDataVO; import org.ejbca.core.model.approval.ApprovalException; import org.ejbca.core.model.approval.ApprovalRequest; import org.ejbca.core.model.approval.ApprovalRequestExecutionException; import org.ejbca.core.model.approval.ApprovalRequestExpiredException; import org.ejbca.core.model.approval.SelfApprovalException; import org.ejbca.core.model.approval.approvalrequests.ActivateCATokenApprovalRequest; import org.ejbca.core.model.approval.approvalrequests.AddEndEntityApprovalRequest; import org.ejbca.core.model.approval.approvalrequests.ChangeStatusEndEntityApprovalRequest; import org.ejbca.core.model.approval.approvalrequests.EditEndEntityApprovalRequest; import org.ejbca.core.model.approval.approvalrequests.KeyRecoveryApprovalRequest; import org.ejbca.core.model.approval.approvalrequests.RevocationApprovalRequest; import org.ejbca.core.model.approval.profile.ApprovalPartition; import org.ejbca.core.model.approval.profile.ApprovalProfile; import org.ejbca.core.model.approval.profile.ApprovalStep; import org.ejbca.core.model.authorization.AccessRulesConstants; /** * Handles execution of approved tasks. Separated from ApprovealSessionBean to avoid * circular dependencies, since execution will require SSBs that originally created the * approval request. * * @version $Id: ApprovalExecutionSessionBean.java 29010 2018-05-23 13:09:53Z jekaterina_b_helmes $ */ @SuppressWarnings("deprecation") @Stateless(mappedName = JndiConstants.APP_JNDI_PREFIX + "ApprovalExecutionSessionRemote") @TransactionAttribute(TransactionAttributeType.REQUIRED) public class ApprovalExecutionSessionBean implements ApprovalExecutionSessionLocal, ApprovalExecutionSessionRemote { private static final Logger log = Logger.getLogger(ApprovalExecutionSessionBean.class); private static final InternalEjbcaResources intres = InternalEjbcaResources.getInstance(); @EJB private AuthorizationSessionLocal authorizationSession; @EJB private ApprovalSessionLocal approvalSession; @EJB private ApprovalProfileSessionLocal approvalProfileSession; @EJB private CAAdminSessionLocal caAdminSession; @EJB private EndEntityManagementSessionLocal endEntityManagementSession; @EJB private GlobalConfigurationSessionLocal globalConfigurationSession; @EJB private SecurityEventsLoggerSessionLocal auditSession; @Override public void approve(AuthenticationToken admin, int approvalId, Approval approval) throws ApprovalRequestExpiredException, ApprovalRequestExecutionException, AuthorizationDeniedException, AdminAlreadyApprovedRequestException, ApprovalException, SelfApprovalException, AuthenticationFailedException { if (log.isTraceEnabled()) { log.trace(">approve: hash="+approvalId); } final ApprovalData approvalData = approvalSession.findNonExpiredApprovalDataLocal(approvalId); if (approvalData == null) { String msg = intres.getLocalizedMessage("approval.notexist", approvalId); log.info(msg); throw new ApprovalException(ErrorCode.APPROVAL_REQUEST_ID_NOT_EXIST, msg); } assertAuthorizedToApprove(admin, approvalData.getApprovalDataVO()); checkApprovalPossibility(admin, approvalData, approval); approval.setApprovalAdmin(true, admin); try { //Retrieve the latest non-stale version of the approval profile from the approval request (as the copy of the profile stored in the request may //contain metadata which was added during the approval process. final Integer approvalProfileId = approvalData.getApprovalRequest().getApprovalProfile().getProfileId(); ApprovalProfile approvalProfile = null; if(approvalProfileId != null) { approvalProfile = approvalProfileSession.getApprovalProfile(approvalData.getApprovalRequest().getApprovalProfile().getProfileId().intValue()); } else { approvalProfile = approvalData.getApprovalDataVO().getApprovalRequest().getApprovalProfile(); } final List approvalsPerformed = approvalData.getApprovals(); if (approvalData.getStatus() != ApprovalDataVO.STATUS_WAITINGFORAPPROVAL) { throw new ApprovalException("Wrong status of approval request, expected STATUS_WAITINGFORAPPROVAL(-1): "+approvalData.getStatus()); } // Check if the approval is applicable, i.e belongs to and satisfies a certain partition, as well as that all previous steps have been satisfied if (!approvalProfile.isApprovalAuthorized(approvalsPerformed, approval)) { throw new AuthorizationDeniedException("Administrator " + approval.getAdmin().toString() + " was not authorized to partition " + approval.getPartitionId() + " in step " + approval.getStepId() + " of approval profile " + approvalProfile.getProfileName()); } approvalsPerformed.add(approval); if (approvalData.hasRequestOrApprovalExpired()) { approvalSession.sendApprovalNotifications(approvalData.getApprovalRequest(), approvalProfile, approvalData, false); throw new ApprovalRequestExpiredException(); } final boolean readyToCheckExecution = approvalProfile.canApprovalExecute(approvalsPerformed); approvalSession.setApprovals(approvalData, approvalsPerformed); if (readyToCheckExecution) { //Kept for legacy reasons to allow for 100% uptime, can be removed once upgrading from 6.6.0 is no longer supported. approvalData.setRemainingapprovals(0); final ApprovalRequest approvalRequest = approvalData.getApprovalRequest(); if (approvalRequest.isExecutable()) { try { if (approvalRequest instanceof ActivateCATokenApprovalRequest) { ((ActivateCATokenApprovalRequest) approvalRequest).execute(caAdminSession); } else if (approvalRequest instanceof AddEndEntityApprovalRequest) { ((AddEndEntityApprovalRequest) approvalRequest).execute(endEntityManagementSession, approvalSession.getIdFromApprovalId(approvalId), admin); } else if (approvalRequest instanceof ChangeStatusEndEntityApprovalRequest) { ((ChangeStatusEndEntityApprovalRequest) approvalRequest).execute(endEntityManagementSession, approvalSession.getIdFromApprovalId(approvalId), admin); } else if (approvalRequest instanceof EditEndEntityApprovalRequest) { ((EditEndEntityApprovalRequest) approvalRequest).execute(endEntityManagementSession, approvalSession.getIdFromApprovalId(approvalId), admin); } else if (approvalRequest instanceof KeyRecoveryApprovalRequest) { ((KeyRecoveryApprovalRequest) approvalRequest).execute(endEntityManagementSession); } else if (approvalRequest instanceof RevocationApprovalRequest) { ((RevocationApprovalRequest) approvalRequest).execute(endEntityManagementSession, approvalSession.getIdFromApprovalId(approvalId), admin); } else { approvalRequest.execute(); } approvalData.setStatus(ApprovalDataVO.STATUS_EXECUTED); } catch (ApprovalRequestExecutionException e) { approvalData.setStatus(ApprovalDataVO.STATUS_EXECUTIONFAILED); throw e; } approvalData.setStatus(ApprovalDataVO.STATUS_EXECUTED); approvalData.setExpireDate(new Date()); } else { approvalData.setStatus(ApprovalDataVO.STATUS_APPROVED); approvalData.setExpiredate((new Date()).getTime() + approvalRequest.getApprovalValidity()); } } // Notify all administrators affected by the work flow update approvalSession.sendApprovalNotifications(approvalData.getApprovalRequest(), approvalProfile, approvalData, false); final Map details = new LinkedHashMap(); details.put("msg", intres.getLocalizedMessage("approval.approved", approvalData.getId())); List texts = approvalData.getApprovalRequest().getNewRequestDataAsText(admin); for (ApprovalDataText text : texts) { details.put(text.getHeader(), text.getData()); } auditSession.log(EjbcaEventTypes.APPROVAL_APPROVE, EventStatus.SUCCESS, EjbcaModuleTypes.APPROVAL, EjbcaServiceTypes.EJBCA, admin.toString(), String.valueOf(approvalData.getCaid()), null, null, details); } catch (ApprovalRequestExpiredException e) { final Map details = new LinkedHashMap(); details.put("msg", intres.getLocalizedMessage("approval.expired", approvalData.getId())); auditSession.log(EjbcaEventTypes.APPROVAL_APPROVE, EventStatus.FAILURE, EjbcaModuleTypes.APPROVAL, EjbcaServiceTypes.EJBCA, admin.toString(), String.valueOf(approvalData.getCaid()), null, null, details); throw e; } catch (ApprovalRequestExecutionException e) { final Map details = new LinkedHashMap(); details.put("msg", intres.getLocalizedMessage("approval.errorexecuting", approvalData.getId())); details.put("error", e.getMessage()); auditSession.log(EjbcaEventTypes.APPROVAL_APPROVE, EventStatus.FAILURE, EjbcaModuleTypes.APPROVAL, EjbcaServiceTypes.EJBCA, admin.toString(), String.valueOf(approvalData.getCaid()), null, null, details); throw e; } if (log.isTraceEnabled()) { log.trace("reject: hash="+approvalId); final ApprovalData approvalData = approvalSession.findNonExpiredApprovalDataLocal(approvalId); if (approvalData == null) { String msg = intres.getLocalizedMessage("approval.notexist", approvalId); log.info(msg); throw new ApprovalException(ErrorCode.APPROVAL_REQUEST_ID_NOT_EXIST, msg); } assertAuthorizedToApprove(admin, approvalData.getApprovalDataVO()); checkApprovalPossibility(admin, approvalData, approval); approval.setApprovalAdmin(false, admin); try { //Retrieve the latest non-stale version of the approval profile from the approval request (as the copy of the profile stored in the request may //contain metadata which was added during the approval process. ApprovalProfile approvalProfile = approvalProfileSession.getApprovalProfile(approvalData.getApprovalRequest().getApprovalProfile().getProfileId()); if(approvalProfile == null) { approvalProfile = approvalData.getApprovalDataVO().getApprovalRequest().getApprovalProfile(); } final List approvalsPerformed = approvalData.getApprovals(); // Check if the approval is applicable, i.e belongs to and satisfies a certain partition, as well as that all previous steps have been satisfied if (!approvalProfile.isApprovalAuthorized(approvalsPerformed, approval)) { throw new AuthorizationDeniedException("Administrator " + approval.getAdmin().toString() + " was not authorized to partition " + approval.getPartitionId() + " in step " + approval.getStepId() + " of approval profile " + approvalProfile.getProfileName()); } approvalsPerformed.add(approval); if (approvalData.hasRequestOrApprovalExpired()) { approvalSession.sendApprovalNotifications(approvalData.getApprovalRequest(), approvalProfile, approvalData, true); throw new ApprovalRequestExpiredException(); } if (approvalData.getStatus() != ApprovalDataVO.STATUS_WAITINGFORAPPROVAL) { throw new ApprovalException("Wrong status of approval request."); } approvalSession.setApprovals(approvalData, approvalsPerformed); //Retrieve the approval profile just to make sure that the state is still valid //Kept for legacy reasons approvalData.setRemainingapprovals(0); if (approvalData.getApprovalRequest().isExecutable()) { approvalData.setStatus(ApprovalDataVO.STATUS_EXECUTIONDENIED); approvalData.setExpireDate(new Date()); } else { approvalData.setStatus(ApprovalDataVO.STATUS_REJECTED); approvalData.setExpiredate((new Date()).getTime() + approvalData.getApprovalRequest().getApprovalValidity()); } approvalSession.sendApprovalNotifications(approvalData.getApprovalRequest(), approvalProfile, approvalData, false); final Map details = new LinkedHashMap(); details.put("msg", intres.getLocalizedMessage("approval.rejected", approvalData.getId())); List texts = approvalData.getApprovalRequest().getNewRequestDataAsText(admin); for (ApprovalDataText text : texts) { details.put(text.getHeader(), text.getData()); } auditSession.log(EjbcaEventTypes.APPROVAL_REJECT, EventStatus.SUCCESS, EjbcaModuleTypes.APPROVAL, EjbcaServiceTypes.EJBCA, admin.toString(), String.valueOf(approvalData.getCaid()), null, null, details); } catch (ApprovalRequestExpiredException e) { log.info(intres.getLocalizedMessage("approval.expired", approvalData.getId())); throw e; } log.trace(" partitions = nextStep.getPartitions(); for (ApprovalPartition partition : partitions.values()) { try { if (approvalProfile.canApprovePartition(admin, partition)) { allowed = true; break; } } catch (AuthenticationFailedException e) { // If this admin cannot approve this partition, check the next partition } } } if (!allowed) { final String msg = intres.getLocalizedMessage("authorization.notauthorizedtoapprovalrequest", admin, approvalData.getApprovalId(), approvalProfile.getProfileId()); throw new AuthorizationDeniedException(msg); } } }