/***********************************************************************
 * 
 *  Copyright (C) 2006 Novell, Inc. All Rights Reserved.
 *
 *  This library is free software; you can redistribute it and/or
 *  modify it under the terms of the GNU Lesser General Public
 *  License as published by the Free Software Foundation; version 2.1
 *  of the License.
 *
 *  This library 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
 *  Library Lesser General Public License for more details.
 *
 *  You should have received a copy of the GNU Lesser General Public
 *  License along with this library; if not, Novell, Inc.
 * 
 *  To contact Novell about this file by physical or electronic mail, 
 *  you may find current contact information at www.novell.com.
 * 
 *  Author: Juan Carlos Luciani <jluciani@novell.com>
 *  
 ***********************************************************************/

package com.novell.casa.authtoksvc;

import java.io.ByteArrayInputStream;

import org.apache.log4j.Logger;
import org.apache.xml.serialize.XMLSerializer;
import org.apache.xml.serialize.OutputFormat;
import org.apache.xml.security.utils.Constants;
import org.apache.xerces.parsers.DOMParser;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.xml.sax.InputSource;

import java.io.*;


/**
* SessionToken class.
* <p>
* This class constructs sessions tokens that clients can present to an ATS
* to prove that an entity has been authenticated to a particular realm.
* The session token consists of a SOAP message secured with WSSecurity
* with the appropriate elements signed and with a timestamp. The body of
* the SOAP message is as follows:
* <p>
*     <session_token>
*     <realm>realm value</realm>
*     <ident_id>identity id value</ident_id>
*     </session_token>
*
*/
public final class SessionToken
{
   private static final Logger m_log = Logger.getLogger(SessionToken.class);

   private String m_id = null;
   private String m_realm = null;
   private final String m_token;

   static private SecureTokenUtil m_secTokenUtil = null;


   /**
    * Constructor.
    *
    * @param id         Id of the authenticated identity.
    * @param realm      Realm where the identity id is valid.
    * @param lifetime   Token lifetime.
    * @throws Exception
    */
   public SessionToken(String id,
                       String realm,
                       String lifetime) throws Exception
   {
      // Save some of the input parameters
      m_id = id;
      m_realm = realm;

      // Get secure token util object
      SecureTokenUtil secTokenUtil = getSecureTokenUtilObj();
      if (secTokenUtil != null)
      {
         // Obtain secure token template document
         Document tokenDoc = SecureTokenUtil.getSecureTokenTemplate();
         if (tokenDoc != null)
         {
            // Secure token template obtained, now get its top most element.
            Node tokenElement = tokenDoc.getDocumentElement();

            // Add session token payload to the token
            Node soapBodyElement = SecureTokenUtil.findChildNodeNS(tokenElement, "http://www.w3.org/2003/05/soap-envelope", "Body");
            Node sessionTokenElement = tokenDoc.createElement("session_token");
            soapBodyElement.appendChild(sessionTokenElement);
            Node realmElement = tokenDoc.createElement("realm");
            realmElement.setTextContent(m_realm);
            Node idenIdElement = tokenDoc.createElement("ident_id");
            idenIdElement.setTextContent(m_id);
            sessionTokenElement.appendChild(idenIdElement);
            sessionTokenElement.insertBefore(realmElement, idenIdElement);

            // Secure the token
            secTokenUtil.secure(tokenDoc, Integer.valueOf(lifetime).intValue());

            // Now save the token as a string
            ByteArrayOutputStream outStream = null;
            try
            {
               outStream = new ByteArrayOutputStream();
               OutputFormat format = new OutputFormat(tokenDoc);
               XMLSerializer serializer = new XMLSerializer(outStream, format);
               serializer.serialize(tokenDoc.getDocumentElement());
               m_token = new String(outStream.toByteArray(), "UTF-8");
            }
            finally
            {
               if (outStream != null)
               {
                  try
                  {
                     outStream.flush();
                     outStream.close();
                  }
                  catch (Exception e)
                  {
                     // Do nothing
                  }
               }
            }
         }
         else
         {
            // Failed to obtain secure token template
            m_log.warn("Constructor()- Failed to obtain secure token template");
            throw new Exception("SessionToken.Constructor()- Failed to obtain secure token template");
         }
      }
      else
      {
         // Failed to obtain secure token util object
         m_log.warn("Constructor()- Failed to obtain secure token util object");
         throw new Exception("SessionToken.Constructor()- Failed to obtain secure token util object");
      }
   }

   /**
    * Constructor given a session token string. The constructor
    * validates the token as part of its processing.
    *
    * @param token   Session token string.
    * @throws Exception
    */
   public SessionToken(String token) throws Exception
   {
      Reader tokenReader = null;

      try
      {
         // Decode the token string
         m_token = Base64Coder.decodeUTF8(token);

         // Now instantiate token document with the token string
         tokenReader = new StringReader(m_token);
         Constants.setSignatureSpecNSprefix("");

         DOMParser parser = new DOMParser();
         parser.setFeature("http://xml.org/sax/features/namespaces", true);
         parser.parse(new InputSource(tokenReader));
         Document tokenDoc = parser.getDocument();

         // Obtain secure token util object
         SecureTokenUtil secTokenUtil = getSecureTokenUtilObj();
         if (secTokenUtil != null)
         {
            // Use the secure token util to validate the token
            if (secTokenUtil.valid(tokenDoc))
            {
               // The session token is valid, now obtain the realm and identity id
               // information from it.
               Node tokenElement = tokenDoc.getDocumentElement();
               if (tokenElement != null)
               {
                  Node soapBodyElement = SecureTokenUtil.findChildNodeNS(tokenElement,
                                                                         "http://www.w3.org/2003/05/soap-envelope",
                                                                         "Body");
                  if (soapBodyElement != null)
                  {
                     Node sessionTokenElement = SecureTokenUtil.findChildNode(soapBodyElement, "session_token");
                     if (sessionTokenElement != null)
                     {
                        Node realmElement = SecureTokenUtil.findChildNode(sessionTokenElement, "realm");
                        Node identIdElement = SecureTokenUtil.findChildNode(sessionTokenElement, "ident_id");
                        if (realmElement != null
                            && identIdElement != null)
                        {
                           m_realm = realmElement.getTextContent();
                           m_id = identIdElement.getTextContent();
                        }
                        else
                        {
                           // Missing session token element child
                           m_log.warn("Constructor(String)- Unable to obtain session token element child");
                           throw new Exception("SessionToken.Constructor(String)- Unable to obtain session token element child");
                        }
                     }
                     else
                     {
                        // Missing session token element
                        m_log.warn("Constructor(String)- Unable to obtain session token element");
                        throw new Exception("SessionToken.Constructor(String)- Unable to obtain session token element");
                     }
                  }
                  else
                  {
                     // Invalid SOAP message
                     m_log.warn("Constructor(String)- Unable to obtain soap body element");
                     throw new Exception("SessionToken.Constructor(String)- Unable to obtain soap body element");
                  }
               }
               else
               {
                  // Invalid XML document
                  m_log.warn("Constructor(String)- Unable to obtain document element");
                  throw new Exception("SessionToken.Constructor(String)- Unable to obtain document element");
               }
            }
            else
            {
               // Message verification failed
               m_log.warn("Constructor(String)- Invalid Session Token");
               throw new Exception("SessionToken.Constructor(String)- Invalid Session Token");
            }
         }
         else
         {
            // Failed to obtain secure token util object
            m_log.warn("Constructor(String)- Failed to obtain secure token util object");
            throw new Exception("SessionToken.Constructor(String)- Failed to obtain secure token util object");
         }
      }
      finally
      {
         if (tokenReader != null)
         {
            try
            {
               tokenReader.close();
            }
            catch (IOException e)
            {
               // Do nothing
            }
         }
      }
   }

   /**
    * Gets object to be utilized for creating and verifying secure tokens.
    *
    * @return SecuteTokenUtil object or null if the method fails.
    */
   private static synchronized SecureTokenUtil getSecureTokenUtilObj()
   {
      // Instantiate the object if it has not been instantiated already
      if (m_secTokenUtil == null)
      {
         try
         {
            // Instantiate secure token object to be utilized in server type operations
            m_secTokenUtil = new SecureTokenUtil(true);
         }
         catch (Exception e)
         {
            // Exception caught
            m_log.warn("getSecureTokenUtilObj()- Exception caught, message = " + e.getMessage());
         }
      }

      // Return the object
      return m_secTokenUtil;
   }

   /**
    * Returns a string representing the session token.
    *
    * @return Session token string.
    */
   public String serialize() throws Exception
   {
      return Base64Coder.encodeUTF8(m_token);
   }

   /**
    * Method to get the Identity Id.
    *
    * @return Identity id.
    */
   public String getIdentId()
   {
      return m_id;
   }

   /**
    * Method to get the Identity Repository Reference (Realm).
    *
    * @return realm
    */
   public String getRealm()
   {
      return m_realm;
   }
}
