diff options
Diffstat (limited to 'eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/auth/services/ProtocolAuthenticationService.java')
-rw-r--r-- | eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/auth/services/ProtocolAuthenticationService.java | 458 |
1 files changed, 458 insertions, 0 deletions
diff --git a/eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/auth/services/ProtocolAuthenticationService.java b/eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/auth/services/ProtocolAuthenticationService.java new file mode 100644 index 00000000..85c609e0 --- /dev/null +++ b/eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/auth/services/ProtocolAuthenticationService.java @@ -0,0 +1,458 @@ +/******************************************************************************* + * Copyright 2019 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.services; + +import java.io.IOException; +import java.io.PrintWriter; +import java.io.StringWriter; + +import javax.naming.ConfigurationException; +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 org.springframework.context.ApplicationContext; +import org.springframework.stereotype.Service; + +import at.gv.egiz.components.eventlog.api.EventConstants; +import at.gv.egiz.eaaf.core.api.IRequest; +import at.gv.egiz.eaaf.core.api.IRequestStorage; +import at.gv.egiz.eaaf.core.api.IStatusMessenger; +import at.gv.egiz.eaaf.core.api.data.EAAFConstants; +import at.gv.egiz.eaaf.core.api.gui.IGUIBuilderConfiguration; +import at.gv.egiz.eaaf.core.api.gui.IGUIBuilderConfigurationFactory; +import at.gv.egiz.eaaf.core.api.gui.IGUIFormBuilder; +import at.gv.egiz.eaaf.core.api.gui.ModifyableGuiBuilderConfiguration; +import at.gv.egiz.eaaf.core.api.idp.IAction; +import at.gv.egiz.eaaf.core.api.idp.IAuthData; +import at.gv.egiz.eaaf.core.api.idp.IAuthenticationDataBuilder; +import at.gv.egiz.eaaf.core.api.idp.IModulInfo; +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.auth.services.IProtocolAuthenticationService; +import at.gv.egiz.eaaf.core.api.idp.slo.SLOInformationInterface; +import at.gv.egiz.eaaf.core.api.logging.IRevisionLogger; +import at.gv.egiz.eaaf.core.api.logging.IStatisticLogger; +import at.gv.egiz.eaaf.core.api.storage.ITransactionStorage; +import at.gv.egiz.eaaf.core.exceptions.AuthnRequestValidatorException; +import at.gv.egiz.eaaf.core.exceptions.EAAFAuthenticationException; +import at.gv.egiz.eaaf.core.exceptions.EAAFException; +import at.gv.egiz.eaaf.core.exceptions.EAAFSSOException; +import at.gv.egiz.eaaf.core.exceptions.GUIBuildException; +import at.gv.egiz.eaaf.core.exceptions.InvalidProtocolRequestException; +import at.gv.egiz.eaaf.core.exceptions.ProcessExecutionException; +import at.gv.egiz.eaaf.core.exceptions.ProtocolNotActiveException; +import at.gv.egiz.eaaf.core.impl.utils.HTTPUtils; + +@Service +public class ProtocolAuthenticationService implements IProtocolAuthenticationService { + private static final Logger log = LoggerFactory.getLogger(ProtocolAuthenticationService.class); + + @Autowired(required=true) private ApplicationContext applicationContext; + @Autowired(required=true) private ITransactionStorage transactionStorage; + @Autowired(required=true) private IAuthenticationManager authmanager; + @Autowired(required=true) private IAuthenticationDataBuilder authDataBuilder; + @Autowired(required=true) private IGUIFormBuilder guiBuilder; + @Autowired(required=true) private IGUIBuilderConfigurationFactory guiConfigFactory; + @Autowired(required=true) private IStatusMessenger statusMessager; + @Autowired(required=true) private IRequestStorage requestStorage; + + @Autowired private ISSOManager ssoManager; + @Autowired private IStatisticLogger statisticLogger; + @Autowired private IRevisionLogger revisionsLogger; + + /* (non-Javadoc) + * @see at.gv.egiz.eaaf.core.impl.idp.auth.services.IProtocolAuthenticationService#performAuthentication(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse, at.gv.egiz.eaaf.core.api.IRequest) + */ + @Override + public void performAuthentication(final HttpServletRequest req, final HttpServletResponse resp, + final IRequest pendingReq) throws IOException, EAAFException { + try { + if (pendingReq.isNeedAuthentication()) { + //request needs authentication --> start authentication process ... + + //load Parameters from OnlineApplicationConfiguration + final ISPConfiguration oaParam = pendingReq.getServiceProviderConfiguration(); + + if (oaParam == null) + throw new EAAFAuthenticationException( + IStatusMessenger.CODES_INTERNAL_ERROR_AUTH_NOSPCONFIG, + new Object[] { pendingReq.getSPEntityId() }); + + if (authmanager.doAuthentication(req, resp, pendingReq)) { + //pending request is already authenticated --> protocol-specific postProcessing can start directly + finalizeAuthentication(req, resp, pendingReq); + + //transaction is finished, log transaction finished event + revisionsLogger.logEvent(EventConstants.TRANSACTION_DESTROYED, pendingReq.getUniqueTransactionIdentifier()); + + } + + } else { + executeProtocolSpecificAction(req, resp, pendingReq, null); + + } + + } catch (final Exception e) { + buildProtocolSpecificErrorResponse(e, req, resp, pendingReq); + authmanager.performOnlyIDPLogOut(req, resp, pendingReq); + + } + } + + /* (non-Javadoc) + * @see at.gv.egiz.eaaf.core.impl.idp.auth.services.IProtocolAuthenticationService#finalizeAuthentication(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse, at.gv.egiz.eaaf.core.api.IRequest) + */ + @Override + public void finalizeAuthentication(final HttpServletRequest req, final HttpServletResponse resp, final IRequest pendingReq) throws EAAFException, IOException{ + log.debug("Finalize PendingRequest with ID " + pendingReq.getPendingRequestId()); + try { + + //check if pending-request has 'abortedByUser' flag set + if (pendingReq.isAbortedByUser()) { + //send authentication aborted error to Service Provider + buildProtocolSpecificErrorResponse( + new EAAFAuthenticationException( + IStatusMessenger.CODES_INTERNAL_ERROR_AUTH_USERSTOP, + new Object[] {}), + req, resp, pendingReq); + + //do not remove the full active SSO-Session + // in case of only one Service-Provider authentication request is aborted + if ( !pendingReq.needSingleSignOnFunctionality()) { + transactionStorage.remove(pendingReq.getPendingRequestId()); + + } + + //check if pending-request are authenticated + } else if (pendingReq.isAuthenticated()) { + internalFinalizeAuthenticationProcess(req, resp, pendingReq); + + } else { + //suspect state: pending-request is not aborted but also are not authenticated + log.error("PendingRequest is NOT authenticated --> Abort authentication process!"); + handleErrorNoRedirect( + new EAAFException( + "auth.20", + null), req, resp, true); + + } + + } catch (Exception e) { + log.error("Finalize authentication protocol FAILED." , e); + buildProtocolSpecificErrorResponse(e, req, resp, pendingReq); + + if (pendingReq != null) + transactionStorage.remove(pendingReq.getPendingRequestId()); + + } + + //remove pending-request + if (pendingReq != null) { + requestStorage.removePendingRequest(pendingReq.getPendingRequestId()); + revisionsLogger.logEvent(EventConstants.TRANSACTION_DESTROYED, pendingReq.getUniqueTransactionIdentifier()); + + } + } + + + @Override + public void buildProtocolSpecificErrorResponse(final Throwable throwable, final HttpServletRequest req, + final HttpServletResponse resp, final IRequest protocolRequest) throws EAAFException, IOException { + try { + + final Class<?> clazz = Class.forName(protocolRequest.requestedModule()); + + if (clazz == null || + !IModulInfo.class.isAssignableFrom(clazz)) { + log.error("Requested protocol module Class is NULL or does not implement the IModulInfo interface."); + throw new Exception("Requested protocol module Class is NULL or does not implement the IModulInfo interface."); + + } + + final IModulInfo handlingModule = (IModulInfo) applicationContext.getBean(clazz); + + if (handlingModule.generateErrorMessage( + throwable, req, resp, protocolRequest)) { + + //log Error to technical log + logExceptionToTechnicalLog(throwable); + + //log Error Message + statisticLogger.logErrorOperation(throwable, protocolRequest); + + //write revision log entries + revisionsLogger.logEvent(protocolRequest, EventConstants.TRANSACTION_ERROR, protocolRequest.getUniqueTransactionIdentifier()); + + return; + + } else { + handleErrorNoRedirect(throwable, req, resp, true); + + } + + } catch (final Throwable e) { + handleErrorNoRedirect(throwable, req, resp, true); + + } + + } + + @Override + public void handleErrorNoRedirect(final Throwable throwable, final HttpServletRequest req, + final HttpServletResponse resp, final boolean writeExceptionToStatisticLog) throws IOException, EAAFException { + + //log Exception into statistic database + if (writeExceptionToStatisticLog) + statisticLogger.logErrorOperation(throwable); + + //write errror to console + logExceptionToTechnicalLog(throwable); + + //return error to Web browser + if (throwable instanceof EAAFException || throwable instanceof ProcessExecutionException) + internalMOAIDExceptionHandler(req, resp, (Exception)throwable, false); + + else { + //write generic message for general exceptions + final String msg = statusMessager.getMessage(IStatusMessenger.CODES_INTERNAL_ERROR_GENERIC, null); + writeHTMLErrorResponse(req, resp, msg, "9199", (Exception) throwable); + + } + + } + + /** + * Finalize the requested protocol operation + * + * @param httpReq HttpServletRequest + * @param httpResp HttpServletResponse + * @param protocolRequest Authentication request which is actually in process + * @param moaSession MOASession object, which is used to generate the protocol specific authentication information + * @throws Exception + */ + protected void internalFinalizeAuthenticationProcess(final HttpServletRequest req, final HttpServletResponse resp, + final IRequest pendingReq) throws Exception { + + String newSSOSessionId = null; + + //if Single Sign-On functionality is enabled for this request + if (pendingReq.needSingleSignOnFunctionality()) { + if (ssoManager != null) { + newSSOSessionId = ssoManager.createNewSSOSessionCookie(req, resp, pendingReq); + if (StringUtils.isEmpty(pendingReq.getInternalSSOSessionIdentifier())) + ssoManager.createNewSSOSession(pendingReq, newSSOSessionId); + + } else + log.warn("SSO is requested but there is not SSO Session-Manager available"); + + } + + //build authenticationdata from session information and OA configuration + final IAuthData authData = authDataBuilder.buildAuthenticationData(pendingReq); + + //execute the protocol-specific action + final SLOInformationInterface sloInformation = executeProtocolSpecificAction(req, resp, pendingReq, authData); + + //Store OA specific SSO session information if an SSO cookie is set + if (StringUtils.isNotEmpty(newSSOSessionId)) { + try { + ssoManager.updateSSOSession(pendingReq, newSSOSessionId, sloInformation); + + } catch (final EAAFSSOException e) { + log.warn("SSO Session information can not be stored -> SSO is not enabled!"); + authmanager.performOnlyIDPLogOut(req, resp, pendingReq); + + } + + } else { + //remove MOASession from database + authmanager.performOnlyIDPLogOut(req, resp, pendingReq); + + } + + //Advanced statistic logging + statisticLogger.logSuccessOperation(pendingReq, authData, StringUtils.isNotEmpty(newSSOSessionId)); + + } + + /** + * Executes the requested protocol action + * + * @param httpReq HttpServletRequest + * @param httpResp HttpServletResponse + * @param protocolRequest Authentication request which is actually in process + * @param authData Service-provider specific authentication data + * + * @return Return Single LogOut information or null if protocol supports no SSO + * + * @throws Exception + */ + private SLOInformationInterface executeProtocolSpecificAction(final HttpServletRequest httpReq, final HttpServletResponse httpResp, + final IRequest pendingReq, final IAuthData authData) throws Exception { + try { + // request needs no authentication --> start request processing + final Class<?> clazz = Class.forName(pendingReq.requestedAction()); + if (clazz == null || + !IAction.class.isAssignableFrom(clazz)) { + log.error("Requested protocol-action processing Class is NULL or does not implement the IAction interface."); + throw new Exception("Requested protocol-action processing Class is NULL or does not implement the IAction interface."); + + } + + final IAction protocolAction = (IAction) applicationContext.getBean(clazz); + return protocolAction.processRequest(pendingReq, httpReq, httpResp, authData); + + } catch (final ClassNotFoundException e) { + log.error("Requested Auth. protocol processing Class is NULL or does not implement the IAction interface."); + throw new Exception("Requested Auth. protocol processing Class is NULL or does not implement the IAction interface."); + } + + } + + /** + * Write a Exception to the MOA-ID-Auth internal technical log + * + * @param loggedException Exception to log + */ + protected void logExceptionToTechnicalLog(final Throwable loggedException) { + if (!( loggedException instanceof EAAFException + || loggedException instanceof ProcessExecutionException )) { + log.error("Receive an internal error: Message=" + loggedException.getMessage(), loggedException); + + } else { + if (log.isDebugEnabled() || log.isTraceEnabled()) { + log.warn(loggedException.getMessage(), loggedException); + + } else { + log.warn(loggedException.getMessage()); + + } + } + } + + private void writeBadRequestErrorResponse(final HttpServletRequest req, final HttpServletResponse resp, final EAAFException e) throws IOException { + final String code = statusMessager.mapInternalErrorToExternalError(((InvalidProtocolRequestException)e).getErrorId()); + final String descr = StringEscapeUtils.escapeHtml4(StringEscapeUtils.escapeEcmaScript(e.getMessage())); + resp.setContentType(EAAFConstants.CONTENTTYPE_HTML_UTF8); + resp.sendError(HttpServletResponse.SC_BAD_REQUEST, "Protocol validation FAILED!" + + "(Errorcode=" + code + + " | Description=" + descr + ")"); + + } + + private void writeHTMLErrorResponse(final HttpServletRequest req, final HttpServletResponse httpResp, final String msg, final String errorCode, final Exception error) throws IOException, EAAFException { + + try { + final IGUIBuilderConfiguration config + = guiConfigFactory.getDefaultErrorGUI(HTTPUtils.extractAuthURLFromRequest(req)); + +// HTTPUtils.extractAuthURLFromRequest(req), +// DefaultGUIFormBuilderConfiguration.VIEW_ERRORMESSAGE, +// null); + + //add errorcode and errormessage + if (config instanceof ModifyableGuiBuilderConfiguration) { + ((ModifyableGuiBuilderConfiguration)config).putCustomParameter("errorMsg", msg); + ((ModifyableGuiBuilderConfiguration)config).putCustomParameter("errorCode", errorCode); + + //add stacktrace if debug is enabled + if (log.isTraceEnabled()) { + ((ModifyableGuiBuilderConfiguration)config).putCustomParameter("stacktrace", getStacktraceFromException(error)); + + } + + } else + log.info("Can not ADD error message, because 'GUIBuilderConfiguration' is not modifieable "); + + + + guiBuilder.build(httpResp, config, "Error-Message"); + + } catch (final GUIBuildException e) { + log.warn("Can not build error-message GUI.", e); + throw new EAAFException("9199", null, e); + + + } + + } + + private void writeHTMLErrorResponse(final HttpServletRequest req, final HttpServletResponse httpResp, final Exception error) throws IOException, EAAFException { + writeHTMLErrorResponse(req, httpResp, + error.getMessage(), + statusMessager.getResponseErrorCode(error), + error); + } + + + private String getStacktraceFromException(final Exception ex) { + final StringWriter errors = new StringWriter(); + ex.printStackTrace(new PrintWriter(errors)); + return errors.toString(); + + } + + private void internalMOAIDExceptionHandler(final HttpServletRequest req, final HttpServletResponse resp, final Exception e, final boolean writeExceptionToStatisicLog) throws IOException, EAAFException { + if (e instanceof ProtocolNotActiveException) { + resp.getWriter().write(e.getMessage()); + resp.setContentType(EAAFConstants.CONTENTTYPE_HTML_UTF8); + resp.sendError(HttpServletResponse.SC_FORBIDDEN, + StringEscapeUtils.escapeHtml4(StringEscapeUtils.escapeEcmaScript(e.getMessage()))); + + } else if (e instanceof AuthnRequestValidatorException) { + final AuthnRequestValidatorException ex = (AuthnRequestValidatorException)e; + //log Error Message + if (writeExceptionToStatisicLog) + statisticLogger.logErrorOperation(ex, ex.getErrorRequest()); + + //write error message + writeBadRequestErrorResponse(req, resp, (EAAFException) e); + + } else if (e instanceof InvalidProtocolRequestException) { + //send error response + writeBadRequestErrorResponse(req, resp, (EAAFException) e); + + } else if (e instanceof ConfigurationException) { + //send HTML formated error message + writeHTMLErrorResponse(req, resp, e); + + } else if (e instanceof EAAFException) { + //send HTML formated error message + writeHTMLErrorResponse(req, resp, e); + + } else if (e instanceof ProcessExecutionException) { + //send HTML formated error message + writeHTMLErrorResponse(req, resp, e); + + } + + } + + +} |