/******************************************************************************* * 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.io.IOException; import java.io.PrintWriter; import java.security.NoSuchAlgorithmException; import java.util.ArrayList; import java.util.Collection; import java.util.Iterator; import java.util.List; import java.util.Map.Entry; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.apache.velocity.VelocityContext; import org.joda.time.DateTime; import org.opensaml.common.impl.SecureRandomIdentifierGenerator; import org.opensaml.common.xml.SAMLConstants; import org.opensaml.saml2.core.AuthnContextClassRef; import org.opensaml.saml2.core.AuthnContextComparisonTypeEnumeration; import org.opensaml.saml2.core.AuthnRequest; import org.opensaml.saml2.core.Issuer; import org.opensaml.saml2.core.LogoutRequest; import org.opensaml.saml2.core.LogoutResponse; import org.opensaml.saml2.core.NameID; import org.opensaml.saml2.core.NameIDPolicy; import org.opensaml.saml2.core.NameIDType; import org.opensaml.saml2.core.RequestedAuthnContext; import org.opensaml.saml2.core.StatusCode; import org.opensaml.saml2.core.Subject; import org.opensaml.saml2.metadata.EntityDescriptor; import org.opensaml.saml2.metadata.SingleLogoutService; import org.opensaml.saml2.metadata.SingleSignOnService; import org.opensaml.saml2.metadata.provider.MetadataProviderException; import org.opensaml.ws.message.encoder.MessageEncodingException; import org.opensaml.ws.soap.common.SOAPException; import org.opensaml.xml.XMLObject; import org.opensaml.xml.security.SecurityException; import at.gv.egovernment.moa.id.auth.MOAIDAuthConstants; import at.gv.egovernment.moa.id.auth.builder.LoginFormBuilder; import at.gv.egovernment.moa.id.auth.builder.SendAssertionFormBuilder; import at.gv.egovernment.moa.id.auth.builder.StartAuthenticationBuilder; import at.gv.egovernment.moa.id.auth.data.AuthenticationSession; 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.MOAIDException; import at.gv.egovernment.moa.id.auth.parser.StartAuthentificationParameterParser; import at.gv.egovernment.moa.id.auth.servlet.AuthServlet; import at.gv.egovernment.moa.id.commons.db.dao.session.InterfederationSessionStore; import at.gv.egovernment.moa.id.commons.db.dao.session.OASessionStore; import at.gv.egovernment.moa.id.commons.db.ex.MOADatabaseException; import at.gv.egovernment.moa.id.config.auth.AuthConfigurationProvider; import at.gv.egovernment.moa.id.config.auth.OAAuthParameter; import at.gv.egovernment.moa.id.data.SLOInformationContainer; import at.gv.egovernment.moa.id.data.SLOInformationImpl; import at.gv.egovernment.moa.id.protocols.pvp2x.PVPTargetConfiguration; import at.gv.egovernment.moa.id.protocols.pvp2x.binding.IEncoder; import at.gv.egovernment.moa.id.protocols.pvp2x.binding.PostBinding; import at.gv.egovernment.moa.id.protocols.pvp2x.binding.RedirectBinding; import at.gv.egovernment.moa.id.protocols.pvp2x.builder.SingleLogOutBuilder; import at.gv.egovernment.moa.id.protocols.pvp2x.config.PVPConfiguration; import at.gv.egovernment.moa.id.protocols.pvp2x.messages.MOARequest; import at.gv.egovernment.moa.id.protocols.pvp2x.metadata.MOAMetadataProvider; import at.gv.egovernment.moa.id.protocols.pvp2x.utils.MOASAMLSOAPClient; import at.gv.egovernment.moa.id.protocols.pvp2x.utils.SAML2Utils; import at.gv.egovernment.moa.id.storage.AssertionStorage; import at.gv.egovernment.moa.id.storage.AuthenticationSessionStoreage; import at.gv.egovernment.moa.id.util.MOAIDMessageProvider; import at.gv.egovernment.moa.id.util.ParamValidatorUtils; import at.gv.egovernment.moa.id.util.Random; import at.gv.egovernment.moa.logging.Logger; import at.gv.egovernment.moa.util.MiscUtil; import at.gv.egovernment.moa.util.StringUtils; public class AuthenticationManager extends AuthServlet { private static AuthenticationManager instance = null; private static final long serialVersionUID = 1L; public static final String MOA_SESSION = "MoaAuthenticationSession"; public static final String MOA_AUTHENTICATED = "MoaAuthenticated"; public static final int SLOTIMEOUT = 30 * 1000; //30 sec public static AuthenticationManager getInstance() { if (instance == null) { instance = new AuthenticationManager(); } return instance; } /** * Checks if this request can authenticate a MOA Session * * @param request * @param response * @return */ public boolean tryPerformAuthentication(HttpServletRequest request, HttpServletResponse response) { String sessionID = (String) request.getParameter(PARAM_SESSIONID); if (sessionID != null) { Logger.info("got MOASession: " + sessionID); AuthenticationSession authSession; try { authSession = AuthenticationSessionStoreage.getSession(sessionID); if (authSession != null) { Logger.info("MOASession found! A: " + authSession.isAuthenticated() + ", AU " + authSession.isAuthenticatedUsed()); if (authSession.isAuthenticated() && !authSession.isAuthenticatedUsed()) { authSession.setAuthenticatedUsed(true); AuthenticationSessionStoreage.storeSession(authSession); return true; // got authenticated } } } catch (MOADatabaseException e) { return false; } catch (BuildException e) { return false; } } return false; } public void performSingleLogOut(HttpServletRequest httpReq, HttpServletResponse httpResp, AuthenticationSession session, PVPTargetConfiguration pvpReq) throws MOAIDException { String pvpSLOIssuer = null; String inboundRelayState = null; if (pvpReq != null) { MOARequest samlReq = (MOARequest) pvpReq.getRequest(); LogoutRequest logOutReq = (LogoutRequest) samlReq.getSamlRequest(); pvpSLOIssuer = logOutReq.getIssuer().getValue(); inboundRelayState = samlReq.getRelayState(); } SSOManager ssomanager = SSOManager.getInstance(); //store active OAs to SLOContaine List dbOAs = AuthenticationSessionStoreage.getAllActiveOAFromMOASession(session); List dbIDPs = AuthenticationSessionStoreage.getAllActiveIDPsFromMOASession(session); SLOInformationContainer sloContainer = new SLOInformationContainer(); sloContainer.setSloRequest(pvpReq); sloContainer.parseActiveIDPs(dbIDPs, pvpSLOIssuer); sloContainer.parseActiveOAs(dbOAs, pvpSLOIssuer); //terminate MOASession try { AuthenticationSessionStoreage.destroySession(session.getSessionID()); ssomanager.deleteSSOSessionID(httpReq, httpResp); } catch (MOADatabaseException e) { Logger.warn("Delete MOASession FAILED."); sloContainer.putFailedOA(AuthConfigurationProvider.getInstance().getPublicURLPrefix()); } //start service provider back channel logout process Iterator nextOAInterator = sloContainer.getNextBackChannelOA(); while (nextOAInterator.hasNext()) { SLOInformationImpl sloDescr = sloContainer.getBackChannelOASessionDescripten(nextOAInterator.next()); LogoutRequest sloReq = SingleLogOutBuilder.buildSLORequestMessage(sloDescr); try { List soapResp = MOASAMLSOAPClient.send(sloDescr.getServiceURL(), sloReq); LogoutResponse sloResp = null; for (XMLObject el : soapResp) { if (el instanceof LogoutResponse) sloResp = (LogoutResponse) el; } if (sloResp == null) { Logger.warn("Single LogOut for OA " + sloReq.getIssuer().getValue() + " FAILED. NO LogOut response received."); sloContainer.putFailedOA(sloReq.getIssuer().getValue()); } SingleLogOutBuilder.checkStatusCode(sloContainer, sloResp); } catch (SOAPException e) { Logger.warn("Single LogOut for OA " + sloReq.getIssuer().getValue() + " FAILED.", e); sloContainer.putFailedOA(sloReq.getIssuer().getValue()); } catch (SecurityException e) { Logger.warn("Single LogOut for OA " + sloReq.getIssuer().getValue() + " FAILED.", e); sloContainer.putFailedOA(sloReq.getIssuer().getValue()); } } //start service provider front channel logout process try { if (sloContainer.hasFrontChannelOA()) { String relayState = Random.nextRandom(); Collection> sloDescr = sloContainer.getFrontChannelOASessionDescriptions(); List sloReqList = new ArrayList(); for (Entry el : sloDescr) { LogoutRequest sloReq = SingleLogOutBuilder.buildSLORequestMessage(el.getValue()); try { sloReqList.add(SingleLogOutBuilder.getFrontChannelSLOMessageURL(el.getValue().getServiceURL(), el.getValue().getBinding(), sloReq, httpReq, httpResp, relayState)); } catch (Exception e) { Logger.warn("Failed to build SLO request for OA:" + el.getKey()); sloContainer.putFailedOA(el.getKey()); } } AssertionStorage.getInstance().put(relayState, sloContainer); String timeOutURL = AuthConfigurationProvider.getInstance().getPublicURLPrefix() + "/idpSingleLogout" + "?restart=" + relayState; VelocityContext context = new VelocityContext(); context.put("redirectURLs", sloReqList); context.put("timeoutURL", timeOutURL); context.put("timeout", SLOTIMEOUT); ssomanager.printSingleLogOutInfo(context, httpResp); } else { if (pvpReq != null) { //send SLO response to SLO request issuer SingleLogoutService sloService = SingleLogOutBuilder.getResponseSLODescriptor(pvpReq); LogoutResponse message = SingleLogOutBuilder.buildSLOResponseMessage(sloService, pvpReq, sloContainer.getSloFailedOAs()); SingleLogOutBuilder.sendFrontChannelSLOMessage(sloService, message, httpReq, httpResp, inboundRelayState); } else { //print SLO information directly VelocityContext context = new VelocityContext(); if (sloContainer.getSloFailedOAs() == null || sloContainer.getSloFailedOAs().size() == 0) context.put("successMsg", MOAIDMessageProvider.getInstance().getMessage("slo.00", null)); else context.put("errorMsg", MOAIDMessageProvider.getInstance().getMessage("slo.01", null)); ssomanager.printSingleLogOutInfo(context, httpResp); } } } catch (MOADatabaseException e) { Logger.error("MOA AssertionDatabase ERROR", e); if (pvpReq != null) { SingleLogoutService sloService = SingleLogOutBuilder.getResponseSLODescriptor(pvpReq); LogoutResponse message = SingleLogOutBuilder.buildSLOErrorResponse(sloService, pvpReq, StatusCode.RESPONDER_URI); SingleLogOutBuilder.sendFrontChannelSLOMessage(sloService, message, httpReq, httpResp, inboundRelayState); }else { //print SLO information directly VelocityContext context = new VelocityContext(); context.put("errorMsg", MOAIDMessageProvider.getInstance().getMessage("slo.01", null)); ssomanager.printSingleLogOutInfo(context, httpResp); } } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } } public void performOnlyIDPLogOut(HttpServletRequest request, HttpServletResponse response, String moaSessionID) { Logger.info("Logout"); if(moaSessionID == null) { moaSessionID = (String) request.getParameter(PARAM_SESSIONID); } if(moaSessionID == null) { Logger.info("NO MOA Session to logout"); return; } AuthenticationSession authSession; try { authSession = AuthenticationSessionStoreage .getSession(moaSessionID); if(authSession == null) { Logger.info("NO MOA Authentication data for ID " + moaSessionID); return; } authSession.setAuthenticated(false); //HTTPSessionUtils.setHTTPSessionString(session, MOA_SESSION, null); // remove moa session from HTTP Session AuthenticationSessionStoreage.destroySession(moaSessionID); //session.invalidate(); } catch (MOADatabaseException e) { Logger.info("NO MOA Authentication data for ID " + moaSessionID); return; } } public void doAuthentication(HttpServletRequest request, HttpServletResponse response, IRequest target) throws ServletException, IOException, MOAIDException { Logger.info("Starting authentication ..."); if (MiscUtil.isEmpty(target.getRequestedIDP())) { perfomLocalAuthentication(request, response, target); } else { Logger.info("Use IDP " + target.getRequestedIDP() + " for authentication ..."); buildPVP21AuthenticationRequest(request, response, target); } } public void sendTransmitAssertionQuestion(HttpServletRequest request, HttpServletResponse response, IRequest target, OAAuthParameter oaParam) throws ServletException, IOException, MOAIDException { String form = SendAssertionFormBuilder.buildForm(target.requestedModule(), target.requestedAction(), target.getRequestID(), oaParam, AuthConfigurationProvider.getInstance().getPublicURLPrefix()); response.setContentType("text/html;charset=UTF-8"); PrintWriter out = new PrintWriter(response.getOutputStream()); out.print(form); out.flush(); } private void buildPVP21AuthenticationRequest(HttpServletRequest request, HttpServletResponse response, IRequest target) throws ServletException, IOException, MOAIDException { boolean requiredLocalAuthentication = true; Logger.debug("Build PVP 2.1 authentication request"); //get IDP metadata try { OAAuthParameter idp = AuthConfigurationProvider.getInstance().getOnlineApplicationParameter(target.getRequestedIDP()); if (!idp.isInderfederationIDP() || !idp.isInboundSSOInterfederationAllowed()) { Logger.info("Requested interfederation IDP " + target.getRequestedIDP() + " is not valid for interfederation."); Logger.info("Switch to local authentication on this IDP ... "); perfomLocalAuthentication(request, response, target); return; } EntityDescriptor idpEntity = MOAMetadataProvider.getInstance(). getEntityDescriptor(target.getRequestedIDP()); if (idpEntity != null ) { //fetch endpoint from IDP metadata SingleSignOnService redirectEndpoint = null; for (SingleSignOnService sss : idpEntity.getIDPSSODescriptor(SAMLConstants.SAML20P_NS).getSingleSignOnServices()) { // use POST binding as default if it exists //TODO: maybe use RedirectBinding as default if (sss.getBinding().equals(SAMLConstants.SAML2_POST_BINDING_URI)) { redirectEndpoint = sss; } else if ( sss.getBinding().equals(SAMLConstants.SAML2_REDIRECT_BINDING_URI) && redirectEndpoint == null ) redirectEndpoint = sss; } if (redirectEndpoint != null) { AuthnRequest authReq = SAML2Utils .createSAMLObject(AuthnRequest.class); SecureRandomIdentifierGenerator gen = new SecureRandomIdentifierGenerator(); authReq.setID(gen.generateIdentifier()); //send passive AuthnRequest authReq.setIsPassive(true); authReq.setAssertionConsumerServiceIndex(0); authReq.setIssueInstant(new DateTime()); Subject subject = SAML2Utils.createSAMLObject(Subject.class); NameID name = SAML2Utils.createSAMLObject(NameID.class); Issuer issuer = SAML2Utils.createSAMLObject(Issuer.class); String serviceURL = PVPConfiguration.getInstance().getIDPPublicPath(); name.setValue(serviceURL); issuer.setValue(serviceURL); subject.setNameID(name); authReq.setSubject(subject); issuer.setFormat(NameIDType.ENTITY); authReq.setIssuer(issuer); NameIDPolicy policy = SAML2Utils .createSAMLObject(NameIDPolicy.class); policy.setAllowCreate(true); policy.setFormat(NameID.TRANSIENT); authReq.setNameIDPolicy(policy); authReq.setDestination(redirectEndpoint.getLocation()); RequestedAuthnContext reqAuthContext = SAML2Utils.createSAMLObject(RequestedAuthnContext.class); AuthnContextClassRef authnClassRef = SAML2Utils.createSAMLObject(AuthnContextClassRef.class); authnClassRef.setAuthnContextClassRef("http://www.stork.gov.eu/1.0/citizenQAALevel/4"); reqAuthContext.setComparison(AuthnContextComparisonTypeEnumeration.MINIMUM); reqAuthContext.getAuthnContextClassRefs().add(authnClassRef); authReq.setRequestedAuthnContext(reqAuthContext); IEncoder binding = null; if (redirectEndpoint.getBinding().equals( SAMLConstants.SAML2_REDIRECT_BINDING_URI)) { binding = new RedirectBinding(); } else if (redirectEndpoint.getBinding().equals( SAMLConstants.SAML2_POST_BINDING_URI)) { binding = new PostBinding(); } binding.encodeRequest(request, response, authReq, redirectEndpoint.getLocation(), target.getRequestID()); //build and send request without an error requiredLocalAuthentication = false; } else { Logger.warn("Requested IDP " + target.getRequestedIDP() + " does not support POST or Redirect Binding."); } } else { Logger.warn("Requested IDP " + target.getRequestedIDP() + " is not found in InterFederation configuration"); } } catch (MetadataProviderException e) { Logger.error("IDP metadata error." , e); } catch (NoSuchAlgorithmException e) { Logger.error("Build IDP authentication request FAILED.", e); } catch (MessageEncodingException e) { Logger.error("Build IDP authentication request FAILED.", e); } catch (SecurityException e) { Logger.error("Build IDP authentication request FAILED.", e); } if (requiredLocalAuthentication) { Logger.info("Switch to local authentication on this IDP ... "); perfomLocalAuthentication(request, response, target); } } private void perfomLocalAuthentication(HttpServletRequest request, HttpServletResponse response, IRequest target) throws ServletException, IOException, MOAIDException { Logger.debug("Starting authentication on this IDP ..."); setNoCachingHeadersInHttpRespone(request, response); List legacyallowed_prot = AuthConfigurationProvider.getInstance().getLegacyAllowedProtocols(); //is legacy allowed boolean legacyallowed = legacyallowed_prot.contains(target.requestedModule()); //check legacy request parameter boolean legacyparamavail = ParamValidatorUtils.areAllLegacyParametersAvailable(request); AuthenticationSession moasession; try { //check if an MOASession exists and if not create an new MOASession //moasession = getORCreateMOASession(request); moasession = AuthenticationSessionStoreage.createSession(target.getRequestID()); } catch (MOADatabaseException e1) { Logger.error("Database Error! MOASession can not be created!"); throw new MOAIDException("init.04", new Object[] {}); } if (legacyallowed && legacyparamavail) { //parse request parameter into MOASession StartAuthentificationParameterParser.parse(request, response, moasession, target); Logger.info("Start Authentication Module: " + moasession.getModul() + " Action: " + moasession.getAction()); StartAuthenticationBuilder startauth = StartAuthenticationBuilder.getInstance(); String getIdentityLinkForm = startauth.build(moasession, request, response); //store MOASession try { AuthenticationSessionStoreage.storeSession(moasession, target.getRequestID()); } catch (MOADatabaseException e) { Logger.error("Database Error! MOASession is not stored!"); throw new MOAIDException("init.04", new Object[] { moasession.getSessionID()}); } if (!StringUtils.isEmpty(getIdentityLinkForm)) { response.setContentType("text/html;charset=UTF-8"); PrintWriter out = new PrintWriter(response.getOutputStream()); out.print(getIdentityLinkForm); out.flush(); Logger.debug("Finished GET StartAuthentication"); } } else { //load Parameters from OnlineApplicationConfiguration OAAuthParameter oaParam = AuthConfigurationProvider.getInstance() .getOnlineApplicationParameter(target.getOAURL()); if (oaParam == null) { throw new AuthenticationException("auth.00", new Object[] { target.getOAURL() }); } else { //check if an MOASession exists and if not create an new MOASession //moasession = getORCreateMOASession(request); //set OnlineApplication configuration in Session moasession.setOAURLRequested(target.getOAURL()); moasession.setAction(target.requestedAction()); moasession.setModul(target.requestedModule()); } //Build authentication form String publicURLPreFix = AuthConfigurationProvider.getInstance().getPublicURLPrefix(); String loginForm = LoginFormBuilder.buildLoginForm(target.requestedModule(), target.requestedAction(), oaParam, publicURLPreFix, moasession.getSessionID()); //store MOASession try { AuthenticationSessionStoreage.storeSession(moasession, target.getRequestID()); } catch (MOADatabaseException e) { Logger.error("Database Error! MOASession is not stored!"); throw new MOAIDException("init.04", new Object[] { moasession.getSessionID()}); } //set MOAIDSession //request.getSession().setAttribute(MOA_SESSION, moasession.getSessionID()); response.setContentType("text/html;charset=UTF-8"); PrintWriter out = new PrintWriter(response.getOutputStream()); out.print(loginForm); out.flush(); } } }