/* * Copyright 2017 Graz University of Technology EAAF-Core Components has been developed in a * cooperation between EGIZ, A-SIT Plus, A-SIT, and Graz University of Technology. * * Licensed under the EUPL, Version 1.2 or - as soon they will be approved by the European * Commission - subsequent versions of the EUPL (the "Licence"); You may not use this work except in * compliance with the Licence. You may obtain a copy of the Licence at: * https://joinup.ec.europa.eu/news/understanding-eupl-v12 * * Unless required by applicable law or agreed to in writing, software distributed under the Licence * is distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express * or implied. See the Licence for the specific language governing permissions and limitations under * the Licence. * * This product combines work with different licenses. See the "NOTICE" text file for details on the * various modules and licenses. The "NOTICE" text file is part of the distribution. Any derivative * works that you distribute must include a readable copy of the "NOTICE" text file. */ 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; @Autowired ModuleRegistration moduleRegistration; /* * (non-Javadoc) * * @see at.gv.egiz.eaaf.core.impl.idp.auth.IAuthenticationManager# * addParameterNameToWhiteList(java.lang .String) */ @Override public final void addParameterNameToWhiteList(final 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(final 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(final HttpServletRequest httpReq, final HttpServletResponse httpResp, final 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 final 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) && pendingReq.needSingleSignOnFunctionality(); } // 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, (RequestImpl) pendingReq); return false; // perform SSO-Consents evaluation if it it required } else if (isValidSsoSession && pendingReq.isNeedUserConsent()) { sendSingleSignOnConsentsEvaluation((RequestImpl) pendingReq); return false; } else if (pendingReq.isPassiv()) { if (isValidSsoSession && StringUtils.isNotEmpty(pendingReq.getInternalSsoSessionIdentifier())) { // 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.getInternalSsoSessionIdentifier())) { // Is authenticated .. proceed ssoManager.populatePendingRequestWithSsoInformation(pendingReq); revisionsLogger.logEvent(pendingReq, EVENT_AUTHENTICATION_PROCESS_FINISHED); return true; } else { // Start authentication! startAuthenticationProcess(httpReq, (RequestImpl) pendingReq); return false; } } } @Override public final void performOnlyIdpLogOut(final HttpServletRequest request, final HttpServletResponse response, final 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 (final EaafSsoException e) { log.warn("Destroying of SSO session FAILED. Reason: " + e.getMessage(), e); } } } /** * Populate process execution context and start process engine. * * @param httpReq http request * @param pendingReq current pending request * @throws ServletException In case of a servlet error * @throws IOException In case of an IO error * @throws EaafException In case of EAAF processing error */ private void startAuthenticationProcess(final HttpServletRequest httpReq, final RequestImpl pendingReq) throws EaafException { log.info("Starting authentication ..."); revisionsLogger.logEvent(pendingReq, EVENT_AUTHENTICATION_PROCESS_STARTED); // create authentication process execution context final 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.setRawDataToTransaction(EaafConstants.PROCESS_ENGINE_SSL_CLIENT_CERTIFICATE, httpReq.getAttribute("javax.servlet.request.X509Certificate")); } // add additional http request parameter to context if (!reqParameterWhiteListeForModules.isEmpty()) { final Enumeration reqParamNames = httpReq.getParameterNames(); while (reqParamNames.hasMoreElements()) { final 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()) { final Enumeration reqHeaderNames = httpReq.getHeaderNames(); while (reqHeaderNames.hasMoreElements()) { final String paramName = reqHeaderNames.nextElement(); if (StringUtils.isNotEmpty(paramName) && at.gv.egiz.eaaf.core.impl.utils.ArrayUtils .containsCaseInsensitive(paramName, reqHeaderWhiteListeForModules) // reqHeaderWhiteListeForModules.contains(paramName.toLowerCase()) ) { executionContext.put(paramName.toLowerCase(), StringEscapeUtils.escapeHtml4(httpReq.getHeader(paramName))); } } } // populate more IDP specific information to execution context populateExecutionContext(executionContext, pendingReq, httpReq); // start process engine startProcessEngine(pendingReq, executionContext); } /** * Add additional parameters into context of process-engine. * * @param executionContext Process-engine context * @param pendingReq Current pending request * @param httpReq http request * * @throws EaafException In case of an error */ protected abstract void populateExecutionContext(ExecutionContext executionContext, RequestImpl pendingReq, HttpServletRequest httpReq) throws EaafException; /** * Starting a user consent evaluation. * * @param pendingReq current pending request * @throws ServletException In case of a servlet error * @throws IOException In case of an IO error * @throws EaafException In case of a EAAF processing error */ private void sendSingleSignOnConsentsEvaluation(final 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 final 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 current pending request * @param executionContext current context for process-engine * @throws EaafException In case of an process-engine error */ private void startProcessEngine(final RequestImpl pendingReq, final ExecutionContext executionContext) throws EaafException { try { // put pending-request ID on execurtionContext executionContext.put(EaafConstants.PROCESS_ENGINE_PENDINGREQUESTID, pendingReq.getPendingRequestId()); // create process instance final String processDefinitionId = moduleRegistration.selectProcess(executionContext, pendingReq); if (processDefinitionId == null) { log.warn("No suitable process found for PendingReqId " + pendingReq.getPendingRequestId()); throw new EaafException("process.02", new Object[] { pendingReq.getPendingRequestId() }); } final 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 (final ProcessExecutionException e) { final Throwable cause = e.getCause(); if (cause != null && cause instanceof TaskExecutionException) { final Throwable taskCause = cause.getCause(); if (taskCause != null && taskCause instanceof EaafException) { final EaafException moaTaskCause = (EaafException) taskCause; log.warn(taskCause.getMessage(), taskCause); throw moaTaskCause; } } throw new EaafException("process.01", new Object[] { pendingReq.getProcessInstanceId(), pendingReq.getPendingRequestId() }, e); } } }