/******************************************************************************* * Copyright 2014 Federal Chancellery Austria * MOA-ID has been developed in a cooperation between BRZ, the Federal * Chancellery Austria - ICT staff unit, and Graz University of Technology. * * Licensed under the EUPL, Version 1.1 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: * http://www.osor.eu/eupl/ * * 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.egovernment.moa.id.moduls; import java.util.Date; import java.util.Map; import java.util.Map.Entry; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.apache.commons.lang3.StringUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import at.gv.egiz.eaaf.core.api.IRequest; import at.gv.egiz.eaaf.core.api.idp.auth.ISSOManager; 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.exceptions.EAAFSSOException; import at.gv.egiz.eaaf.core.exceptions.EAAFStorageException; import at.gv.egiz.eaaf.core.impl.idp.controller.protocols.RequestImpl; import at.gv.egiz.eaaf.core.impl.utils.Random; import at.gv.egovernment.moa.id.advancedlogging.MOAIDEventConstants; import at.gv.egovernment.moa.id.auth.data.AuthenticationSession; import at.gv.egovernment.moa.id.auth.data.AuthenticationSessionExtensions; import at.gv.egovernment.moa.id.auth.exception.AuthenticationException; import at.gv.egovernment.moa.id.auth.exception.BuildException; import at.gv.egovernment.moa.id.auth.exception.WrongParametersException; import at.gv.egovernment.moa.id.commons.MOAIDAuthConstants; import at.gv.egovernment.moa.id.commons.api.AuthConfiguration; import at.gv.egovernment.moa.id.commons.api.IOAAuthParameters; import at.gv.egovernment.moa.id.commons.api.exceptions.ConfigurationException; import at.gv.egovernment.moa.id.commons.api.exceptions.SessionDataStorageException; import at.gv.egovernment.moa.id.commons.db.dao.session.AuthenticatedSessionStore; import at.gv.egovernment.moa.id.commons.db.dao.session.InterfederationSessionStore; import at.gv.egovernment.moa.id.commons.db.dao.session.OldSSOSessionIDStore; import at.gv.egovernment.moa.id.commons.db.ex.MOADatabaseException; import at.gv.egovernment.moa.id.config.auth.OAAuthParameterDecorator; import at.gv.egovernment.moa.id.storage.IAuthenticationSessionStoreage; import at.gv.egovernment.moa.id.util.CookieUtils; import at.gv.egovernment.moa.id.util.legacy.LegacyHelper; import at.gv.egovernment.moa.logging.Logger; import at.gv.egovernment.moa.util.MiscUtil; @Service("MOAID_SSOManager") public class SSOManager implements ISSOManager { private static final String HTMLTEMPLATESDIR = "htmlTemplates/"; private static final String HTMLTEMPLATEFULL = "slo_template.html"; public static String CONTEXTPATH = "contextPath"; private static final String SSOCOOKIE = "MOA_ID_SSO"; private static final String SSOINTERFEDERATION = "MOA_INTERFEDERATION_SSO"; private static final int INTERFEDERATIONCOOKIEMAXAGE = 5 * 60;// sec public static final String DATAID_INTERFEDERATIOIDP_URL = "interIDPURL"; public static final String DATAID_INTERFEDERATIOIDP_RESPONSE = "interIDPResponse"; public static final String DATAID_INTERFEDERATIOIDP_ENTITYID = "interIDPEntityID"; @Autowired private IAuthenticationSessionStoreage authenticatedSessionStore; @Autowired private AuthConfiguration authConfig; @Autowired private IRevisionLogger revisionsLogger; //@Autowired private MOASessionDBUtils moaSessionDBUtils; @Override public boolean checkAndValidateSSOSession(IRequest pendingReq, HttpServletRequest httpReq, HttpServletResponse httpResp) throws EAAFSSOException { try { //get SSO cookie from http request String ssoId = getSSOSessionID(httpReq); //check if interfederation IDP is requested checkInterfederationIsRequested(httpReq, httpResp, pendingReq); //check if SSO session cookie is already used if (ssoId != null) { String correspondingMOASession = existsOldSSOSession(ssoId); if (correspondingMOASession != null) { Logger.warn("Request sends an old SSO Session ID("+ssoId+")! " + "Invalidate the corresponding MOASession with ID="+ correspondingMOASession); revisionsLogger.logEvent(pendingReq, EVENT_SSO_SESSION_INVALID); //destroy internal SSO-session object and SSO-session cooky authenticatedSessionStore.destroyInternalSSOSession(correspondingMOASession); deleteSSOSessionID(httpReq, httpResp); } } //check if SSO Session is valid boolean isSSOValid = isValidSSOSession(ssoId, pendingReq); return isSSOValid; } catch (SessionDataStorageException | ConfigurationException | EAAFStorageException e) { Logger.warn("Cann not process SSO session. Reason: " + e.getMessage(), e); Logger.info("All SSO session will be ignored."); } return false; } @Override public void isSSOAllowedForSP(IRequest pendingReq, HttpServletRequest httpReq) { // check if Service-Provider allows SSO sessions IOAAuthParameters oaConfig = pendingReq.getServiceProviderConfiguration(OAAuthParameterDecorator.class); boolean useSSOOA = oaConfig.useSSO() || oaConfig.isInderfederationIDP(); //if a legacy request is used SSO should not be allowed in case of mandate authentication boolean isUseMandateRequested = false; try { isUseMandateRequested = LegacyHelper.isUseMandateRequested(httpReq); //check if SSO is allowed for the actually executed request //INFO: Actually, useMandate disables SSO functionality!!!!! pendingReq.setNeedSingleSignOnFunctionality((useSSOOA && !isUseMandateRequested)); //check if current service provider needs user consent for SSO pendingReq.setNeedUserConsent(oaConfig.useSSOQuestion()); } catch (WrongParametersException e) { Logger.warn("Find suspect http parameter for mandates! Reason: " + e.getMessage()); } } @Override public void populatePendingRequestWithSSOInformation(IRequest pendingReq) throws EAAFSSOException { //populate pending request with eID data from SSO session if no userConsent is required try { AuthenticationSession ssoMOASession = authenticatedSessionStore.getInternalSSOSession(pendingReq.getInternalSSOSessionIdentifier()); if (ssoMOASession == null) Logger.info("No MOASession FOUND with provided SSO-Cookie."); else { Logger.debug("Found authenticated MOASession with provided SSO-Cookie."); revisionsLogger.logEvent(pendingReq, EVENT_SSO_SESSION_VALID); Logger.trace("Populatint pending request with SSO session information .... "); Map fullSSOData = ssoMOASession.getKeyValueRepresentationFromAuthSession(); if (Logger.isTraceEnabled()) { Logger.trace("Full SSO DataSet: "); for (Entry el : fullSSOData.entrySet()) { Logger.trace(" Key: " + el.getKey() + " Value: " + el.getValue()); } } pendingReq.setRawDataToTransaction(fullSSOData); pendingReq.setAuthenticated(true); } } catch (EAAFStorageException e) { Logger.warn("Can NOT populate pending request from SSO session.", e); throw new EAAFSSOException("", new Object[] {}, e); } } @Override public boolean destroySSOSessionOnIDPOnly(HttpServletRequest httpReq, HttpServletResponse httpResp, IRequest pendingReq) throws EAAFSSOException { //get SSO token from request String internalSSOSessionId = null; try { if (pendingReq != null && MiscUtil.isNotEmpty(pendingReq.getInternalSSOSessionIdentifier())) { internalSSOSessionId = pendingReq.getInternalSSOSessionIdentifier(); } else { String ssoid = getSSOSessionID(httpReq); if (isValidSSOSession(ssoid, null)) { internalSSOSessionId = authenticatedSessionStore.getInternalSSOSessionWithSSOID(ssoid); } } //destroy SSO session if it was found if (StringUtils.isNotEmpty(internalSSOSessionId)) { //delete SSO session and MOA session AuthenticationSession ssoMOASession = authenticatedSessionStore.getInternalSSOSession(internalSSOSessionId); if (ssoMOASession == null) { Logger.info("No internal MOA SSO-Session found. Nothing to destroy"); return false; } ssoMOASession.setAuthenticated(false); //log Session_Destroy to reversionslog AuthenticationSessionExtensions sessionExtensions = authenticatedSessionStore.getAuthenticationSessionExtensions(ssoMOASession.getSSOSessionID()); revisionsLogger.logEvent(MOAIDEventConstants.SESSION_DESTROYED, sessionExtensions.getUniqueSessionId()); authenticatedSessionStore.destroyInternalSSOSession(ssoMOASession.getSSOSessionID()); } } catch (ConfigurationException | SessionDataStorageException | EAAFStorageException e) { Logger.info("NO MOA Authentication data for ID " + internalSSOSessionId); return false; } //Remove SSO token deleteSSOSessionID(httpReq, httpResp); return true; } @Override public String createNewSSOSessionCookie(HttpServletRequest httpReq, HttpServletResponse httpResp, IRequest pendingReq) throws EAAFSSOException { Logger.debug("Creating new SSO session-cookie for http response ... "); //Store SSO information into database String newSSOSessionId = Random.nextHexRandom32(); //set SSO cookie to response if (StringUtils.isNotEmpty(newSSOSessionId)) setSSOSessionID(httpReq, httpResp, newSSOSessionId); else deleteSSOSessionID(httpReq, httpResp); return newSSOSessionId; } @Override public void createNewSSOSession(IRequest pendingReq, String newSSOSessionId) throws EAAFSSOException { AuthenticationSession internalDBSSOSession; try { internalDBSSOSession = authenticatedSessionStore.createInternalSSOSession(pendingReq); pendingReq.setInternalSSOSessionIdentifier(internalDBSSOSession.getSSOSessionID()); } catch (MOADatabaseException | BuildException e) { Logger.warn("Can NOT create SSO session.", e); throw new EAAFSSOException("builder.10", null, e); } } @Override public void updateSSOSession(IRequest pendingReq, String newSSOSessionId, SLOInformationInterface sloInformation) throws EAAFSSOException { try { authenticatedSessionStore.addSSOInformation( pendingReq.getInternalSSOSessionIdentifier(), newSSOSessionId, sloInformation, pendingReq); } catch (AuthenticationException e) { Logger.warn("Can NOT update SSO session.", e); throw new EAAFSSOException("builder.10", null, e); } } //*********************************** old ************************************** /** * Check if interfederation IDP is requested via HTTP GET parameter or if interfederation cookie exists. * Set the requested interfederation IDP as attribte of the {protocolRequest} * * @param httpReq HttpServletRequest * @param httpResp HttpServletResponse * @param protocolRequest Authentication request which is actually in process * @throws SessionDataStorageException * @throws EAAFStorageException * **/ public void checkInterfederationIsRequested(HttpServletRequest httpReq, HttpServletResponse httpResp, IRequest protocolRequest) throws SessionDataStorageException, EAAFStorageException { String interIDP = httpReq.getParameter(MOAIDAuthConstants.INTERFEDERATION_IDP); String interfederationIDP = protocolRequest.getRawData(DATAID_INTERFEDERATIOIDP_URL, String.class); if (MiscUtil.isNotEmpty(interfederationIDP)) { Logger.debug("Protocolspecific preprocessing already set interfederation IDP " + interfederationIDP); return; } if (protocolRequest instanceof RequestImpl) { //check if IDP is requested RequestImpl moaReq = (RequestImpl) protocolRequest; if (MiscUtil.isNotEmpty(interIDP)) { Logger.info("Receive SSO request for interfederation IDP " + interIDP); moaReq.setRawDataToTransaction(DATAID_INTERFEDERATIOIDP_URL, interIDP); } else { //check if IDP cookie is set String cookie = CookieUtils.getValueFromCookie(httpReq, SSOINTERFEDERATION); if (MiscUtil.isNotEmpty(cookie)) { Logger.info("Receive SSO request for interfederated IDP from Cookie " + cookie); moaReq.setRawDataToTransaction(DATAID_INTERFEDERATIOIDP_URL, cookie); CookieUtils.deleteCookie(httpReq, httpResp, SSOINTERFEDERATION); } } } else { Logger.warn("Request is not of type RequestImpl"); } } public void setInterfederationIDPCookie(HttpServletRequest httpReq, HttpServletResponse httpResp, String value) { CookieUtils.setCookie(httpReq, httpResp, SSOINTERFEDERATION, value, INTERFEDERATIONCOOKIEMAXAGE); } public boolean isValidSSOSession(String ssoSessionID, IRequest protocolRequest) throws ConfigurationException, SessionDataStorageException, EAAFStorageException { // search SSO Session if (ssoSessionID == null) { Logger.info("No SSO Session cookie found."); return false; } AuthenticatedSessionStore storedSession = authenticatedSessionStore.isValidSessionWithSSOID(ssoSessionID); if (storedSession == null) return false; else { //check if session is out of lifetime Date now = new Date(); long maxSSOSessionTime = authConfig.getSSOCreatedTimeOut() * 1000; Date ssoSessionValidTo = new Date(storedSession.getCreated().getTime() + maxSSOSessionTime); if (now.after(ssoSessionValidTo)) { Logger.info("Found outdated SSO session information. Start reauthentication process ... "); return false; } //check if stored SSO session is a federated SSO session if (protocolRequest != null && storedSession.isInterfederatedSSOSession()) { //in case of federated SSO session, jump to federated IDP for authentication String interfederationIDP = protocolRequest.getRawData(DATAID_INTERFEDERATIOIDP_URL, String.class); if (MiscUtil.isEmpty(interfederationIDP)) { InterfederationSessionStore selectedIDP = authenticatedSessionStore.searchInterfederatedIDPFORSSOWithMOASession(storedSession.getSessionid()); if (selectedIDP != null) { //no local SSO session exist -> request interfederated IDP Logger.info("SSO Session refer to federated IDP: " + selectedIDP.getIdpurlprefix()); protocolRequest.setRawDataToTransaction( DATAID_INTERFEDERATIOIDP_URL, selectedIDP.getIdpurlprefix()); } else { Logger.warn("MOASession is marked as interfederated SSO session but no interfederated IDP is found. Switch to local authentication ..."); try { authenticatedSessionStore.destroyInternalSSOSession(storedSession.getSessionid()); } catch (MOADatabaseException e) { Logger.error("Delete MOASession with ID:" + storedSession.getSessionid() + " FAILED!" , e); } } } return false; } //set internal SSO SessionID if (protocolRequest != null) protocolRequest.setInternalSSOSessionIdentifier(storedSession.getSessionid()); return true; } } // public String getInternalSSOSession(String ssoSessionID) throws MOADatabaseException { // return authenticatedSessionStore.getInternalSSOSessionWithSSOID(ssoSessionID); // // } //TODO: refactor for faster DB access public String getUniqueSessionIdentifier(String ssoSessionID) { try { if (MiscUtil.isNotEmpty(ssoSessionID)) { String ssoSessionId = authenticatedSessionStore.getInternalSSOSessionWithSSOID(ssoSessionID); if (MiscUtil.isNotEmpty(ssoSessionId)) { AuthenticationSessionExtensions extSessionInformation = authenticatedSessionStore.getAuthenticationSessionExtensions(ssoSessionId); if (extSessionInformation != null) return extSessionInformation.getUniqueSessionId(); else Logger.warn("Extended SSO-Session Information ARE NULL. Something looks wrong!"); } } } catch (MOADatabaseException e) { Logger.debug("No SSO Session with SSO sessionID: " + ssoSessionID); } return null; } public String getSSOSessionID(HttpServletRequest httpReq) { return CookieUtils.getValueFromCookie(httpReq, SSOCOOKIE); } /** * @param entityID * @param request */ public boolean removeInterfederatedSSOIDP(String entityID, HttpServletRequest request) { String ssoSessionID = getSSOSessionID(request); if (MiscUtil.isNotEmpty(ssoSessionID)) { AuthenticatedSessionStore storedSession = authenticatedSessionStore.isValidSessionWithSSOID(ssoSessionID); if (storedSession == null) return false; InterfederationSessionStore selectedIDP = authenticatedSessionStore.searchInterfederatedIDPFORSSOWithMOASessionIDPID(storedSession.getSessionid(), entityID); if (selectedIDP != null) { //no local SSO session exist -> request interfederated IDP Logger.info("Delete interfederated IDP " + selectedIDP.getIdpurlprefix() + " from MOASession " + storedSession.getSessionid()); authenticatedSessionStore.deleteIdpInformation(selectedIDP); } else { Logger.warn("MOASession is marked as interfederated SSO session but no interfederated IDP is found. Switch to local authentication ..."); } return true; } else return false; } private String existsOldSSOSession(String ssoId) { Logger.trace("Check that the SSOID has already been used"); OldSSOSessionIDStore oldSSOSession = authenticatedSessionStore.checkSSOTokenAlreadyUsed(ssoId); if (oldSSOSession == null) { Logger.debug("SSO session-cookie was not used in parst"); return null; } AuthenticatedSessionStore correspondingMoaSession = oldSSOSession.getMoasession(); if (correspondingMoaSession == null) { Logger.info("Get request with old SSO SessionID but no corresponding SSO Session is found."); return null; } return correspondingMoaSession.getSessionid(); } private void setSSOSessionID(HttpServletRequest httpReq, HttpServletResponse httpResp, String ssoId) { CookieUtils.setCookie(httpReq, httpResp, SSOCOOKIE, ssoId, -1); } private void deleteSSOSessionID(HttpServletRequest httpReq, HttpServletResponse httpResp) { CookieUtils.deleteCookie(httpReq, httpResp, SSOCOOKIE); } // private String getValueFromCookie(HttpServletRequest httpReq, String cookieName) { // Cookie[] cookies = httpReq.getCookies(); // // if (cookies != null) { // for (Cookie cookie : cookies) { // if (cookie.getName().equals(cookieName)) { // return cookie.getValue(); // } // } // } // return null; // } // // private void setCookie(HttpServletRequest httpReq, HttpServletResponse httpResp, // String cookieName, String cookieValue, int maxAge) { // // Cookie cookie = new Cookie(cookieName, cookieValue); // cookie.setMaxAge(maxAge); // cookie.setSecure(true); // cookie.setHttpOnly(true); // cookie.setPath(httpReq.getContextPath()); // // httpResp.addCookie(cookie); // } // // private void deleteCookie(HttpServletRequest httpReq, HttpServletResponse httpResp, String cookieName) { // setCookie(httpReq, httpResp, cookieName, "", 0); // // } }