gt:             throw new OAException(SystemErrors.ERROR_INTERNAL);
context: /*
   * Asimba Server
   * 
   * Copyright (C) 2012 Asimba
   * Copyright (C) 2007-2010 Alfa & Ariss B.V.
   * 
   * This program is free software: you can redistribute it and/or modify
   * it under the terms of the GNU Affero General Public License as published by
   * the Free Software Foundation, either version 3 of the License, or
   * (at your option) any later version.
   * 
   * This program is distributed in the hope that it will be useful,
   * but WITHOUT ANY WARRANTY; without even the implied warranty of
   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   * GNU Affero General Public License for more details.
   * 
   * You should have received a copy of the GNU Affero General Public License
   * along with this program.  If not, see www.gnu.org/licenses
   * 
   * Asimba - Serious Open Source SSO - More information on www.asimba.org
   * 
   */
  package com.alfaariss.oa.profile.saml2.profile.sso;
  
  import java.io.IOException;
  import java.util.List;
  
  import javax.servlet.RequestDispatcher;
  import javax.servlet.http.HttpServletRequest;
  import javax.servlet.http.HttpServletResponse;
  
  import org.apache.commons.logging.Log;
  import org.apache.commons.logging.LogFactory;
  import org.opensaml.common.SAMLObject;
  import org.opensaml.common.SignableSAMLObject;
  import org.opensaml.common.binding.SAMLMessageContext;
  import org.opensaml.common.binding.decoding.SAMLMessageDecoder;
  import org.opensaml.common.binding.encoding.SAMLMessageEncoder;
  import org.opensaml.common.xml.SAMLConstants;
  import org.opensaml.saml2.core.LogoutRequest;
  import org.opensaml.saml2.core.LogoutResponse;
  import org.opensaml.saml2.core.StatusCode;
  import org.opensaml.saml2.metadata.EntityDescriptor;
  import org.opensaml.saml2.metadata.IDPSSODescriptor;
  import org.opensaml.saml2.metadata.SPSSODescriptor;
  import org.opensaml.saml2.metadata.SingleLogoutService;
  import org.opensaml.saml2.metadata.provider.MetadataProvider;
  import org.opensaml.ws.message.decoder.MessageDecodingException;
  import org.opensaml.ws.message.encoder.MessageEncodingException;
  import org.opensaml.ws.transport.http.HTTPInTransport;
  import org.opensaml.xml.XMLObject;
  import org.opensaml.xml.security.SecurityException;
  import org.opensaml.xml.security.credential.Credential;
  import org.opensaml.xml.util.DatatypeHelper;
  import org.w3c.dom.Element;
  
  import com.alfaariss.oa.OAException;
  import com.alfaariss.oa.RequestorEvent;
  import com.alfaariss.oa.SystemErrors;
  import com.alfaariss.oa.UserEvent;
  import com.alfaariss.oa.UserException;
  import com.alfaariss.oa.api.attribute.ISessionAttributes;
  import com.alfaariss.oa.api.configuration.IConfigurationManager;
  import com.alfaariss.oa.api.requestor.IRequestor;
  import com.alfaariss.oa.api.session.ISession;
  import com.alfaariss.oa.api.tgt.ITGT;
  import com.alfaariss.oa.engine.core.tgt.factory.ITGTAliasStore;
  import com.alfaariss.oa.profile.saml2.profile.sso.protocol.SingleLogoutProtocol;
  import com.alfaariss.oa.util.logging.RequestorEventLogItem;
  import com.alfaariss.oa.util.logging.UserEventLogItem;
  import com.alfaariss.oa.util.saml2.NameIDFormatter;
  import com.alfaariss.oa.util.saml2.SAML2IssueInstantWindow;
  import com.alfaariss.oa.util.saml2.SAML2Requestor;
  import com.alfaariss.oa.util.saml2.ISAML2Requestors;
  import com.alfaariss.oa.util.saml2.SAML2SecurityException;
  import com.alfaariss.oa.util.saml2.StatusException;
  import com.alfaariss.oa.util.saml2.binding.AbstractDecodingFactory;
  import com.alfaariss.oa.util.saml2.binding.AbstractEncodingFactory;
  import com.alfaariss.oa.util.saml2.binding.BindingProperties;
  import com.alfaariss.oa.util.saml2.binding.soap11.SOAP11Utils;
  import com.alfaariss.oa.util.saml2.crypto.SAML2CryptoUtils;
  import com.alfaariss.oa.util.saml2.metadata.role.sso.IDPSSODescriptorBuilder;
  import com.alfaariss.oa.util.saml2.profile.AbstractSAML2Profile;
  import com.alfaariss.oa.util.saml2.profile.ISAML2Profile;
  import com.alfaariss.oa.util.validation.SessionValidator;
  
  /**
   * Single Logout Profile implementation.
   *
   * @author MHO 
   * @author EVB
   * @author Alfa & Ariss
   * @see <a 
   *  href="http://docs.oasis-open.org/security/saml/v2.0/saml-profiles-2.0-os.pdf" 
   *  target="_new">
   *  Profiles for the OASIS Security Assertion Markup Language (SAML) V2.0
   *  </a>
   */
  public class SingleLogout extends AbstractSAML2Profile 
  {    
      /** Sesstion attribute: ProtocolBinding */
      public final static String SESSION_REQUEST_PROTOCOLBINDING = "ProtocolBinding";
      /** Sesstion attribute: ID */
      public final static String SESSION_REQUEST_ID = "ID";
      /** Sesstion attribute: RelayState */
      public final static String SESSION_REQUEST_RELAYSTATE = "RelayState";
      
      private final static String SSO_LOGOUT_URI = "logout";
      
      private static Log _logger = LogFactory.getLog(SingleLogout.class);
      
      private BindingProperties _bindingProperties;
     
      /** The protocol handler */
      private SingleLogoutProtocol _protocol;
      
      private IDPSSODescriptor _idpSSODescriptor;
       
      
      /**
       * @see AbstractSAML2Profile#init(
       * com.alfaariss.oa.api.configuration.IConfigurationManager, 
       * org.w3c.dom.Element, org.opensaml.saml2.metadata.EntityDescriptor, 
       * java.lang.String, java.lang.String, 
       * com.alfaariss.oa.util.saml2.SAML2Requestors, 
       * com.alfaariss.oa.util.saml2.SAML2IssueInstantWindow, java.lang.String)
       */
      public void init(IConfigurationManager configurationManager,
          Element config, EntityDescriptor entityDescriptor, String sBaseUrl, 
          String sWebSSOPath, ISAML2Requestors requestors, 
          SAML2IssueInstantWindow issueInstantWindow, String sProfileID) 
          throws OAException
      {
          super.init(configurationManager, config, entityDescriptor, sBaseUrl, 
              sWebSSOPath, requestors, issueInstantWindow, sProfileID);
          
          //read bindings config 
          Element eBindings = configurationManager.getSection(config, 
              "bindings");
          if (eBindings == null)
          {
              _logger.error(
                  "No 'bindings' section found in 'profile' section in configuration with profile id: " 
                  + _sID);
              throw new OAException(SystemErrors.ERROR_CONFIG_READ);
          }
          
          _bindingProperties = new BindingProperties(configurationManager, eBindings);
                  
          //read NameID config
          Element eNameID = configurationManager.getSection(config, "nameid");
          if (eNameID == null)
          {
              _logger.error(
                  "No 'nameid' section found in 'profile' section in configuration with profile id: " 
                  + _sID);
              throw new OAException(SystemErrors.ERROR_CONFIG_READ);
          }
          
          ITGTAliasStore spAliasStore = _tgtFactory.getAliasStoreSP();
          if (spAliasStore == null)
          {
              _logger.error("TGT Factory has no SP Role alias support");
              throw new OAException(SystemErrors.ERROR_INIT);
          }
          
          NameIDFormatter nameIDFormatter = new NameIDFormatter(
              configurationManager, eNameID, _cryptoManager, spAliasStore);
          
          //Create protocol
          _protocol = new SingleLogoutProtocol(_cryptoManager.getSecureRandom(),
              _sProfileURL, _tgtFactory, nameIDFormatter, 
              _issueInstantWindow, spAliasStore);
          
          //Update metadata
          updateEntityDescriptor(configurationManager, config);
      }
      
      /**
       * @see ISAML2Profile#process(javax.servlet.http.HttpServletRequest, 
       *  javax.servlet.http.HttpServletResponse)
       */
      public void process(HttpServletRequest servletRequest,
          HttpServletResponse servletResponse) throws OAException
      {
          ISession session = null;
          try
          {
              String sSessionId = servletRequest.getParameter(ISession.ID_NAME);
              if (sSessionId != null)
              {
                  if(!SessionValidator.validateDefaultSessionId(sSessionId))
                  {
                      _logger.warn("Invalid session id in request: " + sSessionId);
                      throw new UserException(UserEvent.REQUEST_INVALID);
                  }
                  session = _sessionFactory.retrieve(sSessionId);
              
                  processLogoutResponse(servletRequest, servletResponse, 
                      session);
              }
              else
                  processSAMLRequest(servletRequest, servletResponse);
          }
          catch(UserException e) //User error
          {            
              UserEventLogItem logItem = null;
              if(session != null)
                  logItem = new UserEventLogItem(session, 
                      servletRequest.getRemoteAddr(), e.getEvent(), this, null);    
              else
                  logItem = new UserEventLogItem(null, null, null, e.getEvent(), 
                      null, servletRequest.getRemoteAddr(), null, this, null);   
              _eventLogger.info(logItem);
              
              if(!servletResponse.isCommitted()) 
              {
                  try
                  {
                      servletResponse.sendError(HttpServletResponse.SC_BAD_REQUEST);
                  }
                  catch (IOException e1)
                  {
                      _logger.warn("Could not send response",e1);
                  }             
              }  
          }
      }
      
      private void processSAMLRequest(HttpServletRequest servletRequest,
          HttpServletResponse servletResponse) throws OAException
      {
          SAMLMessageContext<SignableSAMLObject, 
          SignableSAMLObject,SAMLObject>  context = null;
          String sBinding = null;
          try
          {
              //Decode message
              AbstractDecodingFactory decFactory = 
                  AbstractDecodingFactory.resolveInstance(servletRequest, 
                      servletResponse, _bindingProperties);
              if(decFactory == null)
              {
                  _logger.debug("Decoding factory not created: Invalid request");
                  throw new MessageDecodingException("Could not determine binding");
              }
      
              SAMLMessageDecoder decoder = decFactory.getDecoder();
              sBinding = decoder.getBindingURI();
              
              _logger.debug("Binding URI: " + sBinding);
              
              context = decFactory.getContext();
              context.setLocalEntityId(_sEntityID);
              context.setLocalEntityMetadata(_entityDescriptor);
                                     
              //Decode request
              try
              {
                  decoder.decode(context);
              }
              catch (SecurityException e)
              {
                  _logger.debug(
                      "Could not decode inbound message due to security exception", e);
                 throw new SAML2SecurityException(RequestorEvent.REQUEST_INVALID);
              }
                       
              //verify saml message in request
              SignableSAMLObject requestMessage = context.getInboundSAMLMessage();
              
              if (_logger.isDebugEnabled())
              {
                  if (requestMessage != null)
                      logXML(requestMessage);
              }
              
              if (requestMessage instanceof LogoutRequest)
              {       
                  //DD <LogoutRequest> signing is forced by code for HTTP POST or Redirect binding [saml-profiles-2.0-os r1223].
                  boolean bMandatorySinging = 
                      sBinding.equals(SAMLConstants.SAML2_POST_BINDING_URI) ||
                      sBinding.equals(SAMLConstants.SAML2_REDIRECT_BINDING_URI);
                  
                  HTTPInTransport inTransport = (HTTPInTransport) context.getInboundMessageTransport();
                  String sigParam = inTransport.getParameterValue("Signature");
                  boolean bSigned = !DatatypeHelper.isEmpty(sigParam) || requestMessage.isSigned();                                     
                  if(bMandatorySinging && !bSigned)
                  {
                      _logger.debug(
                          "LogoutRequest MUST be signed if the HTTP POST or Redirect binding is used");
                      throw new SAML2SecurityException(
                          RequestorEvent.REQUEST_INVALID);
                  }
                  
                  //synchronous bindings: The requester MUST authenticate itself 
                  //to the identity provider, either by signing the 
                  //<LogoutRequest> or using any other binding-supported 
                  //mechanism.
                  //DD <LogoutRequest> signing is not forced by code for synchronous bindings, but should be enabled by configuration in a production environment
                  
                  LogoutRequest lr = (LogoutRequest)requestMessage;
                  String sReason = lr.getReason();
                  
                  processLogoutRequest(
                     servletRequest, servletResponse, context, sBinding, sReason);
              }
              else
              {
                  _logger.debug("Unsupported SAML message in request");  
                  throw new MessageDecodingException(
                      "Unsupported SAML message");
              }
          }
          catch(StatusException e) //SAML processing error
          {
              _eventLogger.info(new RequestorEventLogItem(null, null, null, 
                  e.getEvent(), null, servletRequest.getRemoteAddr(), 
                  e.getRequestorID(), this, e.getMessage()));
              
              sendResponse(context, servletRequest, servletResponse, sBinding);
          }
          catch (MessageDecodingException e) //Binding processing error  
          {    
             _logger.debug(
                 "Decoding error", e);
             _eventLogger.info(new RequestorEventLogItem(null, null, null, 
                 RequestorEvent.REQUEST_INVALID, null, 
                 servletRequest.getRemoteAddr(), null, this, null));
             if(sBinding != null && sBinding.equals(
                 SAMLConstants.SAML2_SOAP11_BINDING_URI))
             {
                 SOAP11Utils.sendSOAPFault(context, 
                     RequestorEvent.REQUEST_INVALID);
             }
             else
             {            
                 try
                 {
                     if (!servletResponse.isCommitted())
                         servletResponse.sendError(
                             HttpServletResponse.SC_BAD_REQUEST);
                 }
                 catch (IOException e1)
                 {
                     _logger.warn("Could not send response", e1);
                 }
             }
          }
          catch (SAML2SecurityException e)
              //The message does not meet the required security constraints
          {  
              _logger.debug(
                  "Security error", e);
              _eventLogger.info(new RequestorEventLogItem(null, null, null, 
                  e.getEvent(), null, servletRequest.getRemoteAddr(), 
                  null, this, "Security Fault"));
                         
              //DD Security error -> Return a "403 Forbidden" response
              try
              {
                  if (!servletResponse.isCommitted())
                      servletResponse.sendError(HttpServletResponse.SC_FORBIDDEN);
              }
              catch (IOException e1)
              {
                  _logger.warn("Could not send response", e1);
              }                  
          }        
          catch (OAException e) //Internal error
          {
              throw e;
          }         
          catch (Exception e)
          {
              _logger.fatal("Could not process SAML request message", e);
  