/* * 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.protocols.pvp2x; import java.io.Serializable; import java.io.UnsupportedEncodingException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.apache.commons.lang.SerializationUtils; import org.opensaml.saml2.core.LogoutRequest; import org.opensaml.saml2.core.LogoutResponse; import org.opensaml.saml2.metadata.SingleLogoutService; 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.IAction; import at.gv.egiz.eaaf.core.api.idp.IAuthData; import at.gv.egiz.eaaf.core.api.idp.auth.IAuthenticationManager; import at.gv.egiz.eaaf.core.api.idp.slo.ISLOInformationContainer; 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.storage.ITransactionStorage; import at.gv.egiz.eaaf.core.exceptions.EAAFException; import at.gv.egiz.eaaf.core.exceptions.SLOException; import at.gv.egiz.eaaf.core.impl.utils.Random; import at.gv.egiz.eaaf.modules.pvp2.idp.impl.PVPSProfilePendingRequest; import at.gv.egiz.eaaf.modules.pvp2.impl.message.PVPSProfileRequest; import at.gv.egiz.eaaf.modules.pvp2.impl.message.PVPSProfileResponse; import at.gv.egovernment.moa.id.advancedlogging.MOAIDEventConstants; import at.gv.egovernment.moa.id.auth.exception.AuthenticationException; import at.gv.egovernment.moa.id.auth.servlet.RedirectServlet; import at.gv.egovernment.moa.id.commons.MOAIDAuthConstants; import at.gv.egovernment.moa.id.commons.api.exceptions.MOAIDException; import at.gv.egovernment.moa.id.commons.db.dao.session.AssertionStore; import at.gv.egovernment.moa.id.commons.db.ex.MOADatabaseException; import at.gv.egovernment.moa.id.data.SLOInformationContainer; import at.gv.egovernment.moa.id.moduls.SSOManager; import at.gv.egovernment.moa.id.protocols.pvp2x.builder.SingleLogOutBuilder; import at.gv.egovernment.moa.id.storage.IAuthenticationSessionStoreage; import at.gv.egovernment.moa.logging.Logger; import at.gv.egovernment.moa.util.MiscUtil; import at.gv.egovernment.moa.util.URLEncoder; /** * @author tlenz * */ @Service("pvpSingleLogOutService") public class SingleLogOutAction implements IAction { @Autowired private SSOManager ssomanager; @Autowired private IAuthenticationManager authManager; @Autowired private IAuthenticationSessionStoreage authenticationSessionStorage; @Autowired private ITransactionStorage transactionStorage; @Autowired private SingleLogOutBuilder sloBuilder; @Autowired private IRevisionLogger revisionsLogger; /* (non-Javadoc) * @see at.gv.egovernment.moa.id.moduls.IAction#processRequest(at.gv.egovernment.moa.id.moduls.IRequest, javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse, at.gv.egovernment.moa.id.data.IAuthData) */ @Override public SLOInformationInterface processRequest(IRequest req, HttpServletRequest httpReq, HttpServletResponse httpResp, IAuthData authData) throws EAAFException { PVPSProfilePendingRequest pvpReq = (PVPSProfilePendingRequest) req; if (pvpReq.getRequest() instanceof PVPSProfileRequest && ((PVPSProfileRequest)pvpReq.getRequest()).getSamlRequest() instanceof LogoutRequest) { Logger.debug("Process Single LogOut request"); PVPSProfileRequest samlReq = (PVPSProfileRequest) pvpReq.getRequest(); LogoutRequest logOutReq = (LogoutRequest) samlReq.getSamlRequest(); String ssoSessionId = authenticationSessionStorage.searchSSOSessionWithNameIDandOAID( logOutReq.getIssuer().getValue(), logOutReq.getNameID().getValue()); if (MiscUtil.isEmpty(ssoSessionId)) { Logger.warn("Can not find active SSO session with nameID " + logOutReq.getNameID().getValue() + " and OA " + logOutReq.getIssuer().getValue()); Logger.info("Search active SSO session with SSO session cookie"); String ssoID = ssomanager.getSSOSessionID(httpReq); if (MiscUtil.isEmpty(ssoID)) { Logger.info("Can not find active Session. Single LogOut not possible!"); SingleLogoutService sloService = sloBuilder.getResponseSLODescriptor(pvpReq); //LogoutResponse message = sloBuilder.buildSLOErrorResponse(sloService, pvpReq, StatusCode.RESPONDER_URI); LogoutResponse message = sloBuilder.buildSLOResponseMessage(sloService, pvpReq, null); Logger.info("Sending SLO success message to requester ..."); sloBuilder.sendFrontChannelSLOMessage(sloService, message, httpReq, httpResp, samlReq.getRelayState(), pvpReq); return null; } else { try { ssoSessionId = authenticationSessionStorage.getInternalSSOSessionWithSSOID(ssoID); if (MiscUtil.isEmpty(ssoSessionId)) throw new MOADatabaseException(""); } catch (MOADatabaseException e) { Logger.info("Can not find active Session. Single LogOut not possible!"); SingleLogoutService sloService = sloBuilder.getResponseSLODescriptor(pvpReq); //LogoutResponse message = sloBuilder.buildSLOErrorResponse(sloService, pvpReq, StatusCode.RESPONDER_URI); LogoutResponse message = sloBuilder.buildSLOResponseMessage(sloService, pvpReq, null); Logger.info("Sending SLO success message to requester ..."); sloBuilder.sendFrontChannelSLOMessage(sloService, message, httpReq, httpResp, samlReq.getRelayState(), pvpReq); return null; } } } pvpReq.setInternalSSOSessionIdentifier(ssoSessionId); ISLOInformationContainer sloInformationContainer = authManager.performSingleLogOut(httpReq, httpResp, pvpReq, ssoSessionId); Logger.debug("Starting technical SLO process ... "); sloBuilder.toTechnicalLogout(sloInformationContainer, httpReq, httpResp, null); } else if (pvpReq.getRequest() instanceof PVPSProfileResponse && ((PVPSProfileResponse)pvpReq.getRequest()).getResponse() instanceof LogoutResponse) { Logger.debug("Process Single LogOut response"); LogoutResponse logOutResp = (LogoutResponse) ((PVPSProfileResponse)pvpReq.getRequest()).getResponse(); //Transaction tx = null; try { String relayState = pvpReq.getRequest().getRelayState(); if (MiscUtil.isEmpty(relayState)) { Logger.warn("SLO Response from " + logOutResp.getIssuer().getValue() + " has no SAML2 RelayState."); throw new SLOException("pvp2.19", null); } //Session session = MOASessionDBUtils.getCurrentSession(); boolean storageSuccess = false; int counter = 0; //TODO: add counter to prevent deadlock synchronized(this){ while (!storageSuccess) { Logger.debug("Current Thread: " +Thread.currentThread().getId() + " requests TransactionStore"); Object o = transactionStorage.getRaw(relayState); if(o==null){ Logger.trace("No entries found."); throw new MOADatabaseException("No sessioninformation found with this ID"); } AssertionStore element = (AssertionStore) o; Object data = SerializationUtils.deserialize(element.getAssertion()); if (data instanceof SLOInformationContainer) { ISLOInformationContainer sloContainer = (ISLOInformationContainer) data; //check status sloBuilder.checkStatusCode(sloContainer, logOutResp); if (sloContainer.hasFrontChannelOA()) { try { //some response are open byte[] serializedSLOContainer = SerializationUtils.serialize((Serializable) sloContainer); element.setAssertion(serializedSLOContainer); element.setType(sloContainer.getClass().getName()); Logger.debug("Current Thread: " + Thread.currentThread().getId() + " puts SLOInformation into TransactionStore"); transactionStorage.putRaw(element.getArtifact(), element); //sloContainer could be stored to database storageSuccess = true; } catch(EAAFException e) { counter++; Logger.debug("SLOContainter could not stored to database. Wait some time and restart storage process ... "); if (counter > 1000) { Logger.warn("Stopping SLO process with an error, because it runs in a loop.", e); throw new EAAFException("internal.01", null, e); } try { java.util.Random rand = new java.util.Random(); Thread.sleep(rand.nextInt(20)*10); } catch (InterruptedException e1) { Logger.warn("Thread could not stopped. ReStart storage process immediately", e1); } } } else { Logger.debug("Current Thread: " + Thread.currentThread().getId() + " remove SLOInformation from TransactionStore"); transactionStorage.remove(element.getArtifact()); storageSuccess = true; String redirectURL = null; IRequest sloReq = sloContainer.getSloRequest(); if (sloReq != null && sloReq instanceof PVPSProfilePendingRequest) { //send SLO response to SLO request issuer SingleLogoutService sloService = sloBuilder.getResponseSLODescriptor((PVPSProfilePendingRequest)sloReq); LogoutResponse message = sloBuilder.buildSLOResponseMessage(sloService, (PVPSProfilePendingRequest)sloReq, sloContainer.getSloFailedOAs()); redirectURL = sloBuilder.getFrontChannelSLOMessageURL(sloService, message, httpReq, httpResp, ((PVPSProfilePendingRequest)sloReq).getRequest().getRelayState()); } else { //print SLO information directly redirectURL = req.getAuthURL() + "/idpSingleLogout"; String artifact = Random.nextRandom(); String statusCode = null; if (sloContainer.getSloFailedOAs() == null || sloContainer.getSloFailedOAs().size() == 0) { statusCode = MOAIDAuthConstants.SLOSTATUS_SUCCESS; revisionsLogger.logEvent(sloContainer.getSessionID(), sloContainer.getTransactionID(), MOAIDEventConstants.AUTHPROCESS_SLO_ALL_VALID); } else { revisionsLogger.logEvent(sloContainer.getSessionID(), sloContainer.getTransactionID(), MOAIDEventConstants.AUTHPROCESS_SLO_NOT_ALL_VALID); statusCode = MOAIDAuthConstants.SLOSTATUS_ERROR; } transactionStorage.put(artifact, statusCode, -1); redirectURL = addURLParameter(redirectURL, MOAIDAuthConstants.PARAM_SLOSTATUS, artifact); } //redirect to Redirect Servlet String url = req.getAuthURL() + "/RedirectServlet"; url = addURLParameter(url, RedirectServlet.REDIRCT_PARAM_URL, URLEncoder.encode(redirectURL, "UTF-8")); url = httpResp.encodeRedirectURL(url); httpResp.setContentType("text/html"); httpResp.setStatus(302); httpResp.addHeader("Location", url); } } else { Logger.warn("Sessioninformation Cast-Exception by using Artifact=" + relayState); throw new MOADatabaseException("Sessioninformation Cast-Exception"); } } } } catch (EAAFException e) { Logger.error("MOA AssertionDatabase ERROR", e); throw new SLOException("pvp2.19", null); } catch (UnsupportedEncodingException e) { Logger.error("Finale SLO redirct not possible.", e); throw new AuthenticationException("pvp2.13", new Object[]{}); } } else { Logger.error("Process SingleLogOutAction but request is NOT of type LogoutRequest or LogoutResponse."); throw new MOAIDException("pvp2.13", null); } return null; } /* (non-Javadoc) * @see at.gv.egovernment.moa.id.moduls.IAction#needAuthentication(at.gv.egovernment.moa.id.moduls.IRequest, javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse) */ @Override public boolean needAuthentication(IRequest req, HttpServletRequest httpReq, HttpServletResponse httpResp) { return false; } /* (non-Javadoc) * @see at.gv.egovernment.moa.id.moduls.IAction#getDefaultActionName() */ @Override public String getDefaultActionName() { return PVPConstants.SINGLELOGOUT; } protected static String addURLParameter(String url, String paramname, String paramvalue) { String param = paramname + "=" + paramvalue; if (url.indexOf("?") < 0) return url + "?" + param; else return url + "&" + param; } }