/***********************************************************************
 *  File: NetCredential.cs
 *  Author: Juan Carlos Luciani (jluciani@novell.com)
 * 
 *  Namespace: Novell.Security.ClientPasswordManager
 * 
 *  Classes implemented: NetCredential.
 * 
 *  Copyright (C) 2004 Novell, Inc.
 *
 *  This library is free software; you can redistribute it and/or
 *  modify it under the terms of the GNU General Public
 *  License as published by the Free Software Foundation; either
 *  version 2 of the License, or (at your option) any later version.
 *
 *  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 General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public
 *  License along with this library; if not, write to the Free
 *  Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 ***********************************************************************/

using System;
using System.Net;
using System.IO;
using System.Diagnostics;
using Novell.Security;
using System.Reflection;

namespace Novell.Security.ClientPasswordManager
{
	/// <summary>
	/// Provides an implementation of ICredentials that leverages the
	/// services of the SecretStore Client Service Wallet.
	/// </summary>
	public class NetCredential: ICredentials
	{
		#region Class Members and Defines

		// This is temporary until we add code to interact with the SecretStore
		private class UserCache
		{
			public string m_userName;
			public CredentialCache m_credentialCache;

			public UserCache()
			{
				m_userName = null;
				m_credentialCache = null;
			}
		}

		// This is temporary until we add code to interact with the SecretStore
		private static UserCache[] m_userCaches = new UserCache[10];

		private string m_svcName = null;
		private string m_svcGroupName = null;
		private string m_userName = null;
		private string m_password = null;
		private string m_credScope = null;

		private bool m_haveCredentials;

		#endregion


		private static bool bMiCasaIsAvailable = false;
		private static Assembly assembly = null; //
		private static string MI_CASA_CLASS_NAME = "Novell.CASA.miCASA";
	   
	    
		static NetCredential()
		{

			try
			{		
				assembly = Assembly.LoadWithPartialName("Novell.CASA.miCASAWrapper");
				if (assembly != null)
					bMiCasaIsAvailable = true;				
			}
			catch (Exception e)
			{
				Dbg.trcError(e.ToString());
			}
		}
		


      /// <summary>
      /// Constructs a NetCredential object for Windows Applications.
      /// 
      /// There is a high probability that services under the same group share the
      /// same user name and passwords. By specifying a service group name, the caller wants to
      /// take advantage of this to try to improve the user experience. If the service group
      /// name is not specified then no effort is made to share usernames and passwords with
      /// other services.
      /// 
      /// If the svcGroupNameIsRealmName parameter is set to true then it is assumed that
      /// the services in the group validate username and passwords against the same
      /// database or against synchronized databases.
      /// 
      /// If the svcGroupNameIsRealmName parameter is set to false then an attempt will be
      /// made to share passwords between the services in the group but services will be
      /// allowed to use different passwords.  Specifying a service group does not mean
      /// that the system will not be able to obtain a username and password that is
      /// unique to a particular service.
      /// </summary>
      /// <param name="svcName">Name of service requesting credentials.</param>
      /// <param name="svcGroupName">Name of group to which the service belongs, can be null or zero length.</param>
      /// <param name="svcGroupNameIsRealmName">The group name is the name of the backend authentication realm.</param>
      /// <param name="userName">Name of the user.</param>
      /// <param name="password">User password if known, otherwise null or empty string.</param>
      public NetCredential(string svcName,
                           string svcGroupName,
                           bool svcGroupNameIsRealmName,
                           string userName,
                           string password)
   {
         // Check input parameters
         if (svcName == null
            || svcName.Length == 0
            || (svcGroupNameIsRealmName
                && (svcGroupName == null
                    || svcGroupName.Length == 0))
            || userName == null
            || userName.Length == 0)
         {
            Dbg.trcError("NetCredential constructor- Invalid input parameter");
            throw new Exception("NetCredential constructor- Invalid input parameter");
         }

         m_svcName = svcName;
         m_svcGroupName = svcGroupName;
         m_userName = userName;
         m_password = password;

         // Determine the name under which the credentials are scoped
         if (svcGroupNameIsRealmName)
            m_credScope = m_svcGroupName;
         else
            m_credScope = m_svcName;

         // Check if we need to update the credential cache
         if (password != null
             && password.Length != 0)
         {
			 if (bMiCasaIsAvailable)
			 {
				 UpdateMiCasaCredential(userName, password, m_svcName, svcGroupName);				 
			 }
			 else
			 {
				 UpdateCredentialCache(userName, password, m_credScope);
			 }

            // Remember that there object has credentials associated with it to avoid
            // looking them up in the cache at a later time.
            m_haveCredentials = true;
         }
         else
         {
            // Indicate that we need to look up the credentials in the cache
            m_haveCredentials = false;
         }
      }

      /// <summary>
      /// Constructs a NetCredential object.
      /// 
      /// No effort is made to share usernames and passwords with other services.
      /// </summary>
      /// <param name="svcName">Name of service requesting credentials.</param>
      /// <param name="userName">Name of the user.</param>
      /// <param name="password">User password if known, otherwise null or empty string. Not meaningfull if username is not specified.</param>
      public NetCredential(string svcName,
                           string userName,
                           string password)
      {
         // Check input parameters
         if (svcName == null
            || svcName.Length == 0
            || userName == null
            || userName.Length == 0)
         {
            Dbg.trcError("NetCredential constructor- Invalid input parameter");
            throw new Exception("NetCredential constructor- Invalid input parameter");
         }

         m_svcName = svcName;
         m_userName = userName;
         m_password = password;

         // The credentials are scoped to the service name
         m_credScope = m_svcName;

         // Check if we need to update the credential cache
         if (password != null
             && password.Length != 0)
         {

			 if (bMiCasaIsAvailable)
			 {
				 UpdateMiCasaCredential(userName, password, m_svcName, null);
			 }
			 else
			 {
				 UpdateCredentialCache(userName, password, m_credScope);
			 }

            // Remember that there object has credentials associated with it to avoid
            // looking them up in the cache at a later time.
            m_haveCredentials = true;
         }
         else
         {
            // Indicate that we need to look up the credentials in the cache
            m_haveCredentials = false;
         }
      }

      /// <summary>
      /// Removes a NetworkCredential instance from the cache.
      /// </summary>
      /// <param name="uriPrefix">A Uri that specifies the URI prefix of the resources that the credential is used for.</param>
      /// <param name="authType">The authentication scheme used by the host named in <i>uriPrefix</i>. </param>
      public void Remove(Uri uriPrefix,
                         String authType)
      {
         // Only process requests for the "Basic" authentication scheme
         if (authType.ToUpper() == "BASIC")
         {

			 if (bMiCasaIsAvailable)
			 {
				RemoveMiCasaCredential(uriPrefix.ToString());
				return;
			 }

            // Check if we already have credentials
            if (m_haveCredentials)
            {
               // Clear the credentials.
               m_password = null;
			   m_haveCredentials = false;
            }
            
            // 
			// Lookup the credentials in the cache
			//
            // Synchronize access into the cache
			lock (m_userCaches)
			{
			   // Look for the user's cache
			   int i;
			   for (i = 0; i < m_userCaches.Length; i++)
			   {
				  // Check if the user cache has been instantiated
				  if (m_userCaches[i] != null)
				  {
					 if (m_userCaches[i].m_userName == m_userName)
					 {
						// We found a cache for this user, remove the credentials.
						m_userCaches[i].m_credentialCache.Remove(new Uri("http://" + m_credScope, true), "Basic");
						break;
					 }
				  }
			   }
            }
         }
         else
         {
            Dbg.trcError("NetCredential.GetCredential- Unsupported authentication scheme");
         }
      }

      /// <summary>
      /// Obtains the network credentials necessary to gain access to the resources associated
      /// with the specified uri prefix.
      /// </summary>
      /// <param name="uriPrefix">Uri that specifies the URI prefix of resources that the credential grant access to</param>
      /// <param name="authType">The authentication scheme used by the resource</param>
      /// <returns>NetworkCredential object or null if not successful</returns>
      public NetworkCredential GetCredential(Uri uriPrefix,
                                             String authType)
      {
         NetworkCredential cred = null;

         // Only process requests for the "Basic" authentication scheme
         if (authType.ToUpper() == "BASIC")
         {

			 if (bMiCasaIsAvailable)
			 {
				return GetMiCasaCredential(m_svcName, m_svcGroupName);
			 }

            // Check if we already have credentials
            if (m_haveCredentials)
            {
               // Create NetworkCredential object with the credentials that we have.
               cred = new NetworkCredential(m_userName, m_password);
            }
            else
            {
               // 
               // Lookup the credentials in the cache
               //
               // Synchronize access into the cache
               lock (m_userCaches)
               {
                  // Look for the user's cache
                  int i;
                  for (i = 0; i < m_userCaches.Length; i++)
                  {
                     // Check if the user cache has been instantiated
                     if (m_userCaches[i] != null)
                     {
                        if (m_userCaches[i].m_userName == m_userName)
                        {
                           // We found a cache for this user, check if we already have the
                           // necessary credentials.
                           cred = m_userCaches[i].m_credentialCache.GetCredential(new Uri("http://" + m_credScope, true), "Basic");
                           break;
                        }
                     }
                  }
               }
            }
         }
         else
         {
            Dbg.trcError("NetCredential.GetCredential- Unsuported authentication scheme");
         }

         return cred;
      }

      /// <summary>
      /// Updates the credential cache with the supplied credentials.
      /// </summary>
      /// <param name="userName">Name of the user.</param>
      /// <param name="password">User password.</param>
      /// <param name="credScope">Credential scope.</param>
		private void UpdateCredentialCache(string userName, string password, string credScope)
		{
			// Check if we already have a cache for the user
			lock (m_userCaches)
			{
				int i;
				for (i = 0; i < m_userCaches.Length; i++)
				{
					// Check if the user cache has been instantiated
					if (m_userCaches[i] != null)
					{
						if (m_userCaches[i].m_userName == userName)
						{
							// A user cache has already been instantiated, check if
							// we already have a cache entry.
							if (m_userCaches[i].m_credentialCache.GetCredential(new Uri("http://" + credScope, true), "Basic") != null)
							{
								// We have a cache entry, remove it.
								m_userCaches[i].m_credentialCache.Remove(new Uri("http://" + credScope, true), "Basic");
							}
							break;
						}
					}
					else
					{
						// Instantiate user cache and use it
						m_userCaches[i] = new UserCache();

						// Setup user cache
						m_userCaches[i].m_userName = userName;
						m_userCaches[i].m_credentialCache = new CredentialCache();
						break;
					}
				}

				// Exit if we failed to obtain a user cache
				if (i == m_userCaches.Length)
					return;

				// Create NetworkCredential object and add it to the credential cache
				NetworkCredential cred = new NetworkCredential(userName, password);
				m_userCaches[i].m_credentialCache.Add(new Uri("http://" + credScope, true), "Basic", cred);
			}
		}		
		

		// the following is micasa/secretstore code.
		private void UpdateMiCasaCredential(string sUsername, string sPassword, string sSecretID, string sSharedID)
		{
			
			// call SetCredential through reflection
			Type type = assembly.GetType(MI_CASA_CLASS_NAME);
			object Instance = Activator.CreateInstance (type);				

			object[] arguments = new object[4];			
			arguments[0] = sSecretID;
			arguments[1] = sSharedID;
			arguments[2] = sUsername;
			arguments[3] = sPassword;
				
			object result; 
			try
			{					
				result = type.InvokeMember("SetBasicCredential", BindingFlags.InvokeMethod, null, Instance, arguments);
			}
			catch (Exception e)
			{
				Dbg.trcError(e.ToString());								
			}
		}

		private NetworkCredential GetMiCasaCredential(string sSecretID, string sSharedID)
		{
			object oUsername;
			object oPassword;

			Type type = assembly.GetType(MI_CASA_CLASS_NAME);
			object Instance = Activator.CreateInstance (type);				
			
			try
			{
				object[] arguments = new object[2];
				arguments[0] = sSecretID;
				arguments[1] = sSharedID;				

				// reflection call
				oUsername = type.InvokeMember("GetCredentialUsername", BindingFlags.InvokeMethod, null, Instance, arguments);
				oPassword = type.InvokeMember("GetCredentialPassword", BindingFlags.InvokeMethod, null, Instance, arguments);
				
				if ((oUsername != null) & (oPassword != null))
					return new NetworkCredential(oUsername.ToString().Trim(), oPassword.ToString().Trim());

				// invoke the GetUsername and GetPassword methods
				//object username = basicCred.InvokeMember("GetUsername", BindingFlags.InvokeMethod, null, result, null);
				//object password = basicCred.InvokeMember("GetPassword", BindingFlags.InvokeMethod, null, result, null);
			}
			catch (Exception e)
			{
				Dbg.trcError(e.ToString());
			}

			return null;
		}

		private void RemoveMiCasaCredential(string sSecretID)
		{
			Type type = assembly.GetType(MI_CASA_CLASS_NAME);
			object Instance = Activator.CreateInstance (type);				
			
			try
			{
				object[] arguments = new object[2];
				arguments[0] = sSecretID;
				arguments[1] = null;

				// reflection call
				type.InvokeMember("RemoveBasicCredential", BindingFlags.InvokeMethod, null, Instance, arguments);				
			}
			catch (Exception e)
			{
				Dbg.trcError(e.ToString());
			}
		}
   }
}