/******************************************************************************* *******************************************************************************/ package at.gv.egiz.eaaf.core.impl.idp.auth; import java.io.IOException; import java.security.cert.X509Certificate; import java.util.ArrayList; import java.util.Enumeration; import java.util.List; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.apache.commons.lang3.StringUtils; import org.apache.commons.text.StringEscapeUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import at.gv.egiz.eaaf.core.api.IRequest; import at.gv.egiz.eaaf.core.api.IRequestStorage; import at.gv.egiz.eaaf.core.api.data.EAAFConstants; import at.gv.egiz.eaaf.core.api.idp.IConfiguration; import at.gv.egiz.eaaf.core.api.idp.ISPConfiguration; import at.gv.egiz.eaaf.core.api.idp.auth.IAuthenticationManager; import at.gv.egiz.eaaf.core.api.idp.auth.ISSOManager; import at.gv.egiz.eaaf.core.api.idp.process.ExecutionContext; import at.gv.egiz.eaaf.core.api.idp.process.ProcessEngine; import at.gv.egiz.eaaf.core.api.logging.IRevisionLogger; import at.gv.egiz.eaaf.core.exceptions.EAAFException; import at.gv.egiz.eaaf.core.exceptions.EAAFSSOException; import at.gv.egiz.eaaf.core.exceptions.NoPassivAuthenticationException; import at.gv.egiz.eaaf.core.exceptions.ProcessExecutionException; import at.gv.egiz.eaaf.core.exceptions.TaskExecutionException; import at.gv.egiz.eaaf.core.impl.idp.auth.modules.ModuleRegistration; import at.gv.egiz.eaaf.core.impl.idp.controller.protocols.RequestImpl; import at.gv.egiz.eaaf.core.impl.idp.process.ExecutionContextImpl; import at.gv.egiz.eaaf.core.impl.utils.TransactionIDUtils; public abstract class AbstractAuthenticationManager implements IAuthenticationManager { private static final Logger log = LoggerFactory.getLogger(AbstractAuthenticationManager.class); private static List reqParameterWhiteListeForModules = new ArrayList(); private static List reqHeaderWhiteListeForModules = new ArrayList(); public static final String MOA_SESSION = "MoaAuthenticationSession"; public static final String MOA_AUTHENTICATED = "MoaAuthenticated"; public static final int SLOTIMEOUT = 30 * 1000; //30 sec @Autowired(required=true) protected IConfiguration authConfig; @Autowired(required=true) private ProcessEngine processEngine; @Autowired(required=true) private IRequestStorage requestStoreage; @Autowired(required=true) protected IRevisionLogger revisionsLogger; @Autowired(required=false) protected ISSOManager ssoManager; /* (non-Javadoc) * @see at.gv.egiz.eaaf.core.impl.idp.auth.IAuthenticationManager#addParameterNameToWhiteList(java.lang.String) */ @Override public final void addParameterNameToWhiteList(String httpReqParam) { if (StringUtils.isNotEmpty(httpReqParam)) reqParameterWhiteListeForModules.add(httpReqParam); } /* (non-Javadoc) * @see at.gv.egiz.eaaf.core.impl.idp.auth.IAuthenticationManager#addHeaderNameToWhiteList(java.lang.String) */ @Override public final void addHeaderNameToWhiteList(String httpReqParam) { if (StringUtils.isNotEmpty(httpReqParam)) reqHeaderWhiteListeForModules.add(httpReqParam.toLowerCase()); } /* (non-Javadoc) * @see at.gv.egiz.eaaf.core.impl.idp.auth.IAuthenticationManager#addHeaderNameToWhiteList(java.lang.String) */ @Override public final boolean doAuthentication(HttpServletRequest httpReq, HttpServletResponse httpResp, IRequest pendingReq) throws EAAFException { if (!(pendingReq instanceof RequestImpl)) { log.error("Requests that need authentication MUST be of type 'RequestImpl'"); throw new RuntimeException("Requests that need authentication HAS TO BE of type 'RequestImpl'"); } //load OA configuration from pending request ISPConfiguration oaParam = pendingReq.getServiceProviderConfiguration(); //set logging context and log unique OA identifier to revision log TransactionIDUtils.setServiceProviderId(oaParam.getUniqueIdentifier()); revisionsLogger.logEvent(pendingReq, EVENT_AUTHENTICATION_PROCESS_FOR_SP, pendingReq.getSPEntityId()); //generic authentication request validation if (pendingReq.isPassiv() && pendingReq.forceAuth()) { // conflict! throw new NoPassivAuthenticationException(); } //check Single Sign-On functionality if SSOManager is available boolean isValidSSOSession = false; if (ssoManager != null) { log.trace("SSOManager is loaded. Starting SSO session validation ... "); //check if SSO is allowed for this service provider ssoManager.isSSOAllowedForSP(pendingReq, httpReq); //check if SSO session is active and valid isValidSSOSession = ssoManager.checkAndValidateSSOSession(pendingReq, httpReq, httpResp); } //check if session is already authenticated //boolean isSessionAuthenticated = tryPerformAuthentication((RequestImpl) pendingReq, isValidSSOSession); //boolean isSessionAuthenticated = isValidSSOSession && StringUtils.isNotEmpty(pendingReq.getSSOSessionIdentifier()); //force new authentication authentication process if (pendingReq.forceAuth()) { startAuthenticationProcess(httpReq, httpResp, (RequestImpl) pendingReq); return false; //perform SSO-Consents evaluation if it it required } else if (isValidSSOSession && pendingReq.isNeedUserConsent()) { sendSingleSignOnConsentsEvaluation(httpReq, httpResp, (RequestImpl) pendingReq); return false; } else if (pendingReq.isPassiv()) { if (isValidSSOSession && StringUtils.isNotEmpty(pendingReq.getSSOSessionIdentifier()) ) { // Passive authentication ok! --> Populate pending request from SSO session ssoManager.populatePendingRequestWithSSOInformation(pendingReq); revisionsLogger.logEvent(pendingReq, EVENT_AUTHENTICATION_PROCESS_FINISHED); return true; } else { throw new NoPassivAuthenticationException(); } } else { if (isValidSSOSession && StringUtils.isNotEmpty(pendingReq.getSSOSessionIdentifier())) { // Is authenticated .. proceed ssoManager.populatePendingRequestWithSSOInformation(pendingReq); revisionsLogger.logEvent(pendingReq, EVENT_AUTHENTICATION_PROCESS_FINISHED); return true; } else { // Start authentication! startAuthenticationProcess(httpReq, httpResp, (RequestImpl) pendingReq); return false; } } } public final void performOnlyIDPLogOut(HttpServletRequest request, HttpServletResponse response, IRequest pendingReq) { log.debug("Close session. Remove pending request ... "); requestStoreage.removePendingRequest(pendingReq.getPendingRequestId()); if (ssoManager != null) { try { log.trace("'SSOManager' active. Search for active SSO sessions ... "); if (ssoManager.destroySSOSessionOnIDPOnly(request, response, pendingReq)) log.info("SSO session successfully closed"); else log.info("Closing SSO session NOT successfully"); } catch (EAAFSSOException e) { log.warn("Destroying of SSO session FAILED. Reason: " + e.getMessage(), e); } } } /** * Populate process execution context and start process engine * * @param httpReq * @param httpResp * @param pendingReq * @throws ServletException * @throws IOException * @throws EAAFException */ private void startAuthenticationProcess(HttpServletRequest httpReq, HttpServletResponse httpResp, RequestImpl pendingReq) throws EAAFException { log.info("Starting authentication ..."); revisionsLogger.logEvent(pendingReq, EVENT_AUTHENTICATION_PROCESS_STARTED); //create authentication process execution context ExecutionContext executionContext = new ExecutionContextImpl(); //set oaIdentifeir executionContext.put(EAAFConstants.PROCESS_ENGINE_SERVICE_PROVIDER_ENTITYID, pendingReq.getServiceProviderConfiguration().getUniqueIdentifier()); //add X509 SSL client certificate if exist if (httpReq.getAttribute("javax.servlet.request.X509Certificate") != null) { log.debug("Find SSL-client-certificate on request --> Add it to context"); executionContext.put(EAAFConstants.PROCESS_ENGINE_SSL_CLIENT_CERTIFICATE, ((X509Certificate[])httpReq.getAttribute("javax.servlet.request.X509Certificate"))); pendingReq.setGenericDataToSession(EAAFConstants.PROCESS_ENGINE_SSL_CLIENT_CERTIFICATE, ((X509Certificate[])httpReq.getAttribute("javax.servlet.request.X509Certificate"))); } //add additional http request parameter to context if (!reqParameterWhiteListeForModules.isEmpty()) { Enumeration reqParamNames = httpReq.getParameterNames(); while(reqParamNames.hasMoreElements()) { String paramName = reqParamNames.nextElement(); if (StringUtils.isNotEmpty(paramName) && reqParameterWhiteListeForModules.contains(paramName) ) executionContext.put(paramName, StringEscapeUtils.escapeHtml4(httpReq.getParameter(paramName))); } } //add additional http request parameter to context if (!reqHeaderWhiteListeForModules.isEmpty()) { Enumeration reqHeaderNames = httpReq.getHeaderNames(); while(reqHeaderNames.hasMoreElements()) { String paramName = reqHeaderNames.nextElement(); if (StringUtils.isNotEmpty(paramName) && reqHeaderWhiteListeForModules.contains(paramName.toLowerCase()) ) executionContext.put(paramName, StringEscapeUtils.escapeHtml4(httpReq.getHeader(paramName))); } } //populate more IDP specific information to execution context populateExecutionContext(executionContext, pendingReq, httpReq); //start process engine startProcessEngine(pendingReq, executionContext); } /** * * * @throws EAAFException */ abstract protected void populateExecutionContext(ExecutionContext executionContext, RequestImpl pendingReq, HttpServletRequest httpReq) throws EAAFException; /** * Starting a user consent evaluation * * @param request * @param response * @param pendingReq * @throws ServletException * @throws IOException * @throws EAAFException */ private void sendSingleSignOnConsentsEvaluation(HttpServletRequest request, HttpServletResponse response, RequestImpl pendingReq) throws EAAFException { log.debug("Starting SSO user-consents evaluation ..."); //set authenticated flag to false, because user consents is required pendingReq.setAuthenticated(false); //create execution context ExecutionContext executionContext = new ExecutionContextImpl(); executionContext.put(ISSOManager.PROCESS_ENGINE_SSO_CONSENTS_EVALUATION, true); //start process engine startProcessEngine(pendingReq, executionContext); } /** * Select a specific process and starting process engine * * @param pendingReq * @param executionContext * @throws EAAFException */ private void startProcessEngine(RequestImpl pendingReq, ExecutionContext executionContext) throws EAAFException { try { //put pending-request ID on execurtionContext executionContext.put(EAAFConstants.PROCESS_ENGINE_PENDINGREQUESTID, pendingReq.getPendingRequestId()); // create process instance String processDefinitionId = ModuleRegistration.getInstance().selectProcess(executionContext); if (processDefinitionId == null) { log.warn("No suitable process found for PendingReqId " + pendingReq.getPendingRequestId() ); throw new EAAFException( "process.02", new Object[] {pendingReq.getPendingRequestId()} ,"No suitable process found for PendingReqId " + pendingReq.getPendingRequestId()); } String processInstanceId = processEngine.createProcessInstance(processDefinitionId, executionContext); // keep process instance id in protocol pending-request pendingReq.setProcessInstanceId(processInstanceId); //store pending-request requestStoreage.storePendingRequest(pendingReq); // start process processEngine.start(pendingReq); } catch (ProcessExecutionException e) { Throwable cause = e.getCause(); if (cause != null && cause instanceof TaskExecutionException) { Throwable taskCause = cause.getCause(); if (taskCause != null && taskCause instanceof EAAFException) { EAAFException moaTaskCause = (EAAFException) taskCause; log.warn(taskCause.getMessage(), taskCause); throw moaTaskCause; } } throw new EAAFException( "process.01", new Object[] { pendingReq.getProcessInstanceId(), pendingReq.getPendingRequestId() }, "Authentication process execution FAILED", e); } } }