using System;
using System.Collections;
using System.ComponentModel;
using System.Configuration;
using System.Data;
using System.Data.SqlClient;
using System.Diagnostics;
using System.Text.RegularExpressions;
using System.Web;
using System.Web.Services;
using System.Web.Mail;
using edu.jhu.Cas.Util;
using edu.jhu.Cas.Servers;
namespace edu.jhu.Cas.Users {
	[WebService (Namespace="http://Users.Cas.jhu.edu", Name="CasUsers", Description="An interface for user administrative operations on a jobsDB $Name: v3_5_18 $ $Revision: 1.51 $")]
	/// <summary>
	/// This encapsulates the user manipulation methods as a set of webservices. 
	/// THe seperation of this from CasJobs was done mainly to reduce the size of the
	/// interface and as it seemed a logical seperation. Mostly one wans the Jobs
	/// interface and not this which mainly concerns itself with user administration
	/// the one exception is Login i.e. getWebServivesId - but for a WebService user
	/// they probbaly never call this rather storing that number in a config file
	/// as is done in the CasJobsCL (Command line tool)..
	///$Id: CasUsers.asmx.cs,v 1.51 2008/06/12 22:03:22 nli Exp $
	///$Revision: 1.51 $
	///$Date: 2008/06/12 22:03:22 $
	/// </summary>
	public class CasUsers : System.Web.Services.WebService {
		
		[WebMethod(Description="Create a new user account")]
		public long CreateAccount(long wsid,string userid,string name,string email,
			string password,string privileges,int emaillevel, short gvisible) {
			HasPrivilege(wsid,PRIVS.ADMIN);
			long newwsid = userid.GetHashCode() + (email.GetHashCode() << 32);
			if(newwsid < 0)newwsid = newwsid * -1;
			JobUsers job = new JobUsers();
			job.SetValue(JobUsers.WEB_SERVICES_ID, newwsid);
			job.SetValue(JobUsers.USERID,userid);
			job.SetValue(JobUsers.NAME, name);
			job.SetValue(JobUsers.EMAIL, email);
            job.SetValue(JobUsers.PASSWORD, Encryptor.MD52Base64Encrypt(password));
            if (privileges != null)
                job.SetValue(JobUsers.PRIVILEGES, privileges);
            else
                job.SetValue(JobUsers.PRIVILEGES, DBNull.Value);
			job.SetValue(JobUsers.EMAIL_LEVEL, emaillevel);
			job.SetValue(JobUsers.CREATED_BY, wsid);
            job.SetValue(JobUsers.GVISIBLE, gvisible);
            try {
                SqlFactory.insert(job, job.CreateAccountStatement);
            }catch(SqlException se){
                string upper = se.Message.ToUpper();
                if (upper.IndexOf("UNIQUE KEY") > -1 && upper.IndexOf("EMAIL") > -1)
                    throw new CasJobsException("Cannot create account; this email already exists.");
                if (upper.IndexOf("UNIQUE KEY") > -1 && upper.IndexOf("USERID") > -1)
                    throw new CasJobsException("Cannot create account; this userid already exists.");
                throw;
            }
			return newwsid;
		}
		public void DeleteAccount(long wsid,long deleteme){
			HasPrivilege(wsid,PRIVS.ADMIN);
			string delGTables = " delete from grouptables where webservicesid = "+deleteme;
			string delGMembers = " delete from groupmembers where webservicesid = "+deleteme;
			string delJobs = " delete from jobs where webservicesid = "+deleteme;
			User u = FindUser(wsid," webservicesid = "+deleteme)[0];          
			string kickAllUsersHack = @"
						alter database "+u.MyDBName+@" set read_only with rollback immediate
						
						alter database "+u.MyDBName+@" set read_write with rollback immediate
						
			";
			string dropMyDB = " drop database "+u.MyDBName;
			SqlFactory.insert(delGTables+delGMembers+delJobs);
			try{
                Server mydbS = GetMydbAdminServerObject(deleteme);
                SqlFactory.runQuery(kickAllUsersHack+dropMyDB,mydbS.ConnectionString);
            }catch{}//this can fail if mydb doesn't exist
			SqlFactory.insert("delete from users where webservicesid = "+deleteme);
		}
		/// <summary>
		/// not yet in full use the notion here is to replicate user informaiton 
		/// between cjobs instalations.
		/// </summary>
		/// <param name="service_wsid"></param>
		/// <param name="u"></param>
		/// <param name="deleteOld"></param>
		/// <returns></returns>
		public bool ReplicateAccount(long service_wsid,User u,bool deleteOld){
			HasPrivilege(service_wsid,PRIVS.ADMIN);
			u.PWEncrypted= u.PWEncrypted== null?null:u.PWEncrypted;
			u.Email= u.Email== null?"":u.Email;
			u.Name = u.Name == null?"":u.Name;
			if(u.WebServicesID == ID.SERVICE_ID)
				return false;//never update these accounts
			if(deleteOld){
				string qry = "delete from users where webservicesid = "+u.WebServicesID
					+" or userid = '"+u.UserID.Replace("'","''")+"' or email = '"+u.Email.Replace("'","''")+"'";
				SqlFactory.insert(qry);
			}
			JobUsers job = new JobUsers();
			
			job.SetValue(JobUsers.WEB_SERVICES_ID, u.WebServicesID);
			job.SetValue(JobUsers.USERID,u.UserID);
			job.SetValue(JobUsers.NAME, u.Name);
			job.SetValue(JobUsers.PASSWORD, u.PWEncrypted);
			job.SetValue(JobUsers.PRIVILEGES, u.Privileges);
			job.SetValue(JobUsers.EMAIL, u.Email);
			job.SetValue(JobUsers.EMAIL_LEVEL, u.EmailLevel);
			job.SetValue(JobUsers.CREATED_BY, u.WebServicesID);
			SqlFactory.insert(job, job.ReplicateAccountStatement);
			return true;
		}
		/// <summary>
		///  even a logging in user is given the anonymous wsid for this action
		/// this is called by the WebServive below
		/// </summary>
		/// <param name="wsid"></param>
		/// <param name="userid"></param>
		/// <returns></returns>
		public long GetWebServiceId(long wsid, string userid) 
		{
            try {
                HasPrivilege(wsid, PRIVS.SIGHT);
            }
            catch {
                HasPrivilege(wsid, PRIVS.ADMIN);
            }
			SqlCommand c = new SqlCommand("select webservicesid from users where userid = @userid ");
			return long.Parse(
				SqlFactory.scalar("select webservicesid from users where userid = '"+userid+"'").ToString());
		}
		[WebMethod(Description="Finds the web service id of a user, based on userid and password.   The parameter userid can be either a userid or an email. This is the equivolent of Login")]
        public long GetWebServiceId(string userid, string password) {
            if (userid == null || password == null)
                throw new Exception("Please set a userid and/or password.");

            JobUsers job = new JobUsers();
            job.SetValue(JobUsers.USERID, userid);//intentional double-map
            job.SetValue(JobUsers.EMAIL, userid);

            string qry = "select webservicesid from users where userid = " + JobUsers.USERID + " and pwencrypted ";
            if (password != null && password.Length > 0)
                qry += "='" + Encryptor.MD52Base64Encrypt(password) + "'";
            else
                qry += " is null";
            Object o = SqlFactory.scalar(job, qry);
            if (o == null) return -1;
            return long.Parse(o.ToString());
		}
		[WebMethod(Description="Not available")]
		public string ForgotPassword(long wsid, string id) {
			throw new Exception("This method has been deprecated.  Please send a mail if you'd like to reset your password.");
		}
		public bool UpdateAccount(long wsid, User user,bool forceupdate) {
            if (wsid != user.WebServicesID)
                HasPrivilege(wsid, PRIVS.ADMIN);
			User[] us = FindUser(ID.SERVICE_ID, "webservicesid = "+wsid);
			if(us.Length > 0) {
				JobUsers job = new JobUsers();
				if(forceupdate)user.LastModified = DateTime.Now;
				job.SetValue(JobUsers.WEB_SERVICES_ID,wsid);//for searching, not updating
				job.SetValue(JobUsers.USERID,user.UserID);
				job.SetValue(JobUsers.NAME,user.Name);
				job.SetValue(JobUsers.EMAIL,user.Email);
				// this gets called from more than one place - sometimes the password passed in
				// is the old one (especially from the admin page) in that case we o not want to
				// encrypt it again cause thats not nice - so best is to pass null
				// if you wan tthe password left as it was
				if (null != user.PWEncrypted && user.PWEncrypted != us[0].PWEncrypted) 
				{
					job.SetValue(JobUsers.PASSWORD,Encryptor.MD52Base64Encrypt(user.PWEncrypted));
				} 
				else 
				{ // put in old pass
					if (us[0].PWEncrypted == null || us[0].PWEncrypted.Length ==0 ) 
					{
						job.SetValue(JobUsers.PASSWORD,DBNull.Value);
					} 
					else 
					{
						job.SetValue(JobUsers.PASSWORD,us[0].PWEncrypted);
					}
				}
                if (user.Privileges != null && user.Privileges.Trim() != "")
                    job.SetValue(JobUsers.PRIVILEGES, user.Privileges);
                else
                    job.SetValue(JobUsers.PRIVILEGES, DBNull.Value);
				//you cannot change web services id
				//you cannot change CreatedByl
				job.SetValue(JobUsers.EMAIL_LEVEL,user.EmailLevel);
                job.SetValue(JobUsers.GVISIBLE, user.GVisible ? 1 : 0);
				//you cannot change time created
				SqlFactory.insert(job, JobUsers.UpdateAccountStatement);
				return true;
			}
			return false;
		}
		[WebMethod(Description="updates the wsid account with new user info.  Fields left null in the User object will also be updated as null in the db.")]
		public bool UpdateAccount(long wsid, User user) {
			return UpdateAccount(wsid,user,false);
		}
		[WebMethod(Description="Finds a user given a where clause predicate.  Must be admin")]
		public DataSet FindUserDataSet(long wsid, string predicate) {
			HasPrivilege(wsid,PRIVS.ADMIN);
			JobUsers job = new JobUsers();
			job.SetValue(JobUsers.WEB_SERVICES_ID, wsid);
            string qry = JobUsers.FindUserStatement;
			if(predicate.Length > 1)
			qry+=" where ";
			qry+=predicate;
			DataSet dataSet = SqlFactory.select(job, qry);
			return dataSet;
		}
		[WebMethod(Description="Finds a user given a where clause predicate.")]
		public User[] FindUser(long wsid, string predicate) {
			DataSet ds = FindUserDataSet(wsid, predicate);
			User[] users = DataConverter.dataSet2User(ds);
			return users;
		}
		[WebMethod(Description="Returns a unique version of a users id.")]
		public string GetUniqueUserId(string userid) {
			JobUsers job = new JobUsers();
			string tmp = userid;
			long x = 0;
			for(;;) {
				job.SetValue(JobUsers.USERID, tmp);
                if (SqlFactory.scalar(job, JobUsers.GetUniqueUserIdStatement) == null)
					return tmp;
				tmp = userid+(x++).ToString();
			}
		}
	    
		[WebMethod(Description="Returns a unique version of a users id.")]
		public void UploadDataSet(long wsid, string tableName, DataSet dataSet) {
			CreateMyDB(wsid);
			string tmpTableName = "happyTree";
			string startQry = "create table "+tmpTableName+"(x int primary key)";
            using (SqlConnection con = new SqlConnection(MyDBServerObject(wsid).ConnectionString)) {
                con.Open();
                SqlCommand command = new SqlCommand(startQry);
                command.Connection = con;
                try {
                    command.ExecuteNonQuery();
                }
                catch (Exception e) { Console.Out.WriteLine(e.StackTrace); }
                string extract = "select * from " + tmpTableName;
                SqlDataAdapter adapter = new SqlDataAdapter(extract, con);
                DataSet currentSet = new DataSet();
                adapter.Fill(currentSet);
                DataTable tmpTable = currentSet.Tables[0];
                foreach (DataRow newRow in dataSet.Tables[0].Rows)
                    tmpTable.ImportRow(newRow);
                adapter.Update(currentSet);

                string finishQry = "drop table " + tmpTableName;
                command.CommandText = finishQry;
                command.ExecuteNonQuery();
                con.Close();
            }
		}
		/**
		 * throws an exception if user does not have spec priv
		 */
		public void HasPrivilege(long wsid, string privilege) {
            privilege = privilege.Trim();
			JobUsers job = new JobUsers();
			job.SetValue(JobUsers.WEB_SERVICES_ID, wsid);
            string qry = JobUsers.HasPrivilegeStatement.Replace(JobUsers.PRIVILEGES, privilege);
			if(SqlFactory.scalar(job, qry) == null)
				throw new CasJobsException("User "+wsid+" does not have "+privilege+" permission");
		}
		[WebMethod(Description="Returns true if a user has a MyDB")]
		public bool HasMyDB(long wsid) {
			JobUsers job = new JobUsers();
			job.SetValue(JobUsers.WEB_SERVICES_ID, wsid);
            return SqlFactory.scalar(job, JobUsers.HasMyDBStatement) != null;
		}
		/// <summary>
		/// finds the database server of the mydb of the given user
		/// </summary>
		/// <param name="wsid"></param>
		/// <returns></returns>
		public Server MyDBServerObject(long wsid){
			if(!HasMyDB(wsid)) return null;
            /* query will proably return multiple objects without the 'top', but they should be identical */
            string qry = @"
select top 1 m.mydbhost as name, s.queue,s.target,m.usr,m.pass as password,m.server as host,u.mydbname as cat,s.execlimit,s.privs
from mydbhosts m, users u,servers s
where u.mydbhost = m.mydbhost
and s.target like '%mydb%'
and u.webservicesid= "+wsid;

            DataSet ds = SqlFactory.select(qry);
            return DataConverter.dataSet2Server(ds)[0];
        }
		/**
		 * returns connectionString of a mydbname
		 */
		public string TheirDBConnection(string mydbname) {
			User[] them = FindUser(ID.SERVICE_ID," mydbname = '"+mydbname+"'");
			return MyDBServerObject(them[0].WebServicesID).ConnectionString;
		}
		//0 user 1 pass
		public string[] MyDBConnectionLogin(long adminID,long wsid){
			HasPrivilege(adminID,PRIVS.ADMIN);
			JobUsers job = new JobUsers();
			job.SetValue(JobUsers.WEB_SERVICES_ID,wsid);
            DataSet ds = SqlFactory.select(job, JobUsers.MyDBConnectionLoginStatement);
			string[] rst = new string[2];
			rst[0] = ds.Tables[0].Rows[0][0].ToString();
			rst[1] = ds.Tables[0].Rows[0][1].ToString();
			return rst;
		}
		public void CreateMyDB(long wsid) {
			if(HasMyDB(wsid))return;
			JobUsers job = new JobUsers();
			job.SetValue(JobUsers.WEB_SERVICES_ID, wsid);
			User user = FindUser(ID.SERVICE_ID,"webservicesid = "+wsid)[0];
			string dbName = "MYDB_"+wsid;
			DataSet ds = SqlFactory.select(job, @"		
select top 1 a.mydbhost,path, admin_usr as usr, admin_pw as password, server as host, 'master' as cat
from mydbhosts a, (
	select max(cnt) as cnt, mydbhost from 
	((select u.mydbhost, count(u.mydbhost) as cnt
	from users u 
	where mydbhost is not null
	group by mydbhost)
	union(select distinct mydbhost,0 as cnt from mydbhosts)) a
	group by mydbhost
) b
where a.mydbhost = b.mydbhost and b.cnt < a.limit
			");
            Server s = DataConverter.dataSet2Server(ds)[0];
			if (ds.Tables[0].Rows.Count ==0) 
				throw new Exception ("No more MyDB's allowed");
			DataRow dr = ds.Tables[0].Rows[0];
			string cHost = dr["mydbhost"].ToString();
			string path = dr["path"].ToString();
			string createQuery = "create database "+dbName+" on (name = "+dbName+
				", filename='"+path+dbName+"', size = "+user.MyDBMinSize+", maxsize = "+
				user.MyDBMaxSize+
				", filegrowth = 5)";
			try{
				SqlFactory.runQuery(createQuery, s.ConnectionString);
			}catch (Exception e) {//mydb may already exist
				if(e.Message.IndexOf("already exists") == -1)
                    throw;
			}
			setDBAccess(wsid,(new FilterFactory()).SwitchMyDb(s.ConnectionString,dbName));
			SqlFactory.runQuery("update users set mydbname='"+dbName+"',mydbhost='"+cHost+
				"' where webservicesid = "+wsid,SqlFactory.JOBS_STRING);
		}
		private void setDBAccess(long wsid,string cString) {
			Server[] servers = (new CasServers()).FindDistinctOwnerServers(ID.SERVICE_ID);
            
            // 'errors' should only contain non-fatal exceptions.  these are stored temporaily for debug purposes
            string errors = "";

			foreach(Server server in servers)
                try {
                    errors += maybeAddDBUser(server.User, cString);
                } catch { continue; }
            DataSet ds = SqlFactory.select("select distinct usr from mydbhosts");
            foreach (DataRow dr in ds.Tables[0].Rows)
                try {
                    errors += maybeAddDBUser(dr[0].ToString(), cString);
                } catch { continue; }
		}
        private string maybeAddDBUser(string user, string cString) {
            string errors = "";
            string dboUser = SqlFactory.scalar("select SYSTEM_USER").ToString();
            errors += "(ADDED [" + dboUser + ", " + user + "])";
            
            //'fake' is not a real user, skip it
            if (user.ToLower().IndexOf("fake") > -1) return errors;
            
            //assuming the dbo has all appropriate permissions already
            if(user == dboUser)return errors;

            string addUser = "EXEC sp_adduser '" + user + "'";
            string main = "exec dbo.sp_grantdbaccess '" + user + "'";
            string permissions = "grant create table to " + user + "\n" +
                " grant create function to " + user + "\n" +
                " grant create procedure to " + user + "\n" +
                " grant create view to " + user + "\n";
            /* 
             * specifically trying to avoid this exception...
             * User, group, or role 'cjuser' already exists in the current database.
             */
            try {
                SqlFactory.runQuery(addUser, cString);
            }
            catch (SqlException se) {
                if (se.Message.IndexOf("already exists") > -1) {
                    errors += se.Message + "\n";
                }
                else throw;
            }
            try {
                SqlFactory.runQuery(main, cString);
            }
            catch (SqlException se) {
                if (se.Message.IndexOf("already exists") > -1) {
                    errors += se.Message + "\n";
                }
                else throw;
            }
            try {
                SqlFactory.runQuery(permissions, cString);
            }
            catch (SqlException se) {
                if (se.Message.IndexOf("already exists") > -1) {
                    errors += se.Message + "\n";
                }
                else throw;
            }
            return errors;
        }
        private Server GetMydbAdminServerObject(long wsid) {
            string qry = @"
select admin_usr as usr, admin_pw as password, server as host, 'master' as cat, cstringextra 
from mydbhosts m, users u 
where u.mydbhost = m.mydbhost
and u.webservicesid = "+wsid;
            return DataConverter.dataSet2Server(SqlFactory.select(qry))[0];
        }
		public void ChangeDBMaxSize(long serviceid, long targetid,int maxsize){
			HasPrivilege(serviceid,PRIVS.ADMIN);
			string fileName = "MYDB_"+targetid.ToString();

            edu.jhu.Cas.Util.Server s = GetMydbAdminServerObject(targetid);
			string qry = @"
alter database "+fileName+@"
MODIFY FILE 
	(NAME='"+fileName+@"',
	MAXSIZE="+maxsize.ToString()+@")";
			SqlFactory.runQuery(qry,s.ConnectionString);
			SqlFactory.insert("update users set mydbmaxsize="+maxsize+" where webservicesid = "+targetid);
		}
		public static string Revision = "$Revision: 1.51 $";
		
		public void Sync(){
			string target = SYNC.WITH;
			int added=0, changed = 0;
			bool didupdate = false;
            throw new Exception("Sync disabled");
            /*
			if(SYNC.WITH != null){
				local.far.CasUsers farCUsers = new local.far.CasUsers(target);
				User[] farUsers = farCUsers.FindUser(ID.SERVICE_ID,"lastmodified is not null and lastmodified > "+LastSync().ToOADate());
				foreach(User u in farUsers){
					didupdate= UpdateAccount(u.WebServicesID,u);
					changed+=didupdate?1:0;
					if(!didupdate){
						try{
							Console.WriteLine("REPLICATING");
							added+=ReplicateAccount(ID.SERVICE_ID,u,true)?1:0;
						}catch{Console.WriteLine("Account creation failed for "+u.UserID);continue;}
					}
				}
				SqlFactory.insert("insert into synclog(lastrun,recordsChanged,recordsAdded) values(getdate(),"+changed+","+added+")");
			}
             * */
		}
		public DateTime LastSync(){
			object o = SqlFactory.select("select max(lastrun) from synclog").Tables[0].Rows[0][0];
			return o is System.DBNull?DateTime.MinValue:(DateTime)o;
		}
	}
}
/* Revision History
   $Log: CasUsers.asmx.cs,v $
   Revision 1.51  2008/06/12 22:03:22  nli
   authenticate fix
   updateuser fix

   Revision 1.50  2008/04/09 23:33:40  nli
   updateaccount is actually fixed this time
   renamed webservicesid to wsid in getusers

   Revision 1.49  2008/04/08 04:52:54  nli
   --services.jobs.getjobs now returns all jobs, if owner has admin priv
   --removed extra space from name field of createaccount.aspx
   --setup app

   Revision 1.48  2008/03/11 04:06:53  nli
   new web services added

   Revision 1.47  2008/02/12 23:32:01  nli
   --invalid logins should now show appropriate error message

   Revision 1.46  2007/10/25 18:24:32  nli
   sqlconnection disposal audit

   Revision 1.45  2007/06/24 20:23:38  nli
   --updateuser should no longer break on null privs
   --jobs will no longer get stuck if set to mail and mail fails

   Revision 1.44  2007/05/10 00:52:56  nli
   web interface now reflects multi-group system better
   fixed errors regarding grouptables remaining published after group destruction, member desubscription
   new users should now have gvisible set to true by default

   Revision 1.43  2007/05/03 19:32:10  nli
   users that are not members of groups should now be able to see null privileged contexts

   Revision 1.42  2007/04/26 22:18:40  nli
   --server access is now tied to a users group[s]
   --'sight' and 'admin' are the only remaining user-level privs with any effect

   Revision 1.41  2007/04/25 18:51:05  nli
   removed redundant information from servers and mydbhosts table
   code modified to support this
   schema changes:
   servers ->
     --add CStringExtra column, varchar(200)
     --add Password column, varchar(200)
     --rename owner column to usr
     --fill in appropriate values
     --remove ConnectionString column

   mydbhosts ->
     -- rename 'user' to usr
     -- add CStringExtraColumn
     -- add admin_usr varchar column
     -- add admin_pw varchar column

   Revision 1.40  2007/04/25 17:03:27  nli
   optimized sql generation in JobXXX classes

   Revision 1.39  2007/04/24 19:50:50  nli
   gvisible is now a column in the user table, not a priv
   added public group

   Revision 1.38  2007/03/13 22:27:03  nli
   anon access removed.
   cleaned up for release

   Revision 1.37  2007/02/26 16:56:55  nli
   submitjob.aspx should be a bit zippier

   Revision 1.36  2007/02/15 22:26:44  nli
   --server privs system updated, more robust
   --ztest page functionality restored, link added to admin page

   Revision 1.35  2007/02/13 21:21:47  nli
   --new mydbs  have all users in mydbhosts table added by default
   --user search @admin.aspx

   Revision 1.34  2007/01/02 19:29:39  nli
   failed user account creation should now display an appropriate error message.

   Revision 1.33  2006/12/12 20:22:21  nli
   TableImport.aspx now propogates a user-entered table name correctly.

   Revision 1.32  2006/09/14 16:34:32  nli
   minor cosmetic changes

   Revision 1.31  2006/09/13 15:47:40  nli
   back from summer
   Revision 1.30  2006/06/08 21:41:38  nli
   --select into now functions correctly
   --server is refered to as [server]
   --query to check mydb usage should work correctly when there are no allocated mydbs
   Revision 1.29  2006/01/24 20:16:51  nli
   --new filtering system based on plans
   --new test suite
   --nunit removed
   Revision 1.28  2005/10/31 18:44:20  nli
   --HasIntoClause Regexes bit more robust
   Revision 1.27  2005/06/08 17:21:58  nli
   --fixed mydb creation bug.
   --new req. 'fake' users in the servers table must contain the substring 'fake'
   Revision 1.26  2005/05/31 17:57:21  womullan
    Added some general comments. Fixed The Admin page resetting users password SPR6843. Added New Password box to admin page to allow admin to change users password.
   Revision 1.25  2005/03/08 16:36:24  nli
   following fixes are somewhat untested...
   --queue display bugs in queues/admin page should
      --not display short jobs
      --not say nothing waiting when running queries > 0
   --select into dropout more selective
   --mydbcontent preview scrolls separate from page
   Revision 1.24  2005/02/24 04:32:38  nli
   don't use me!
   Revision 1.23  2005/01/19 22:02:24  womullan
    Mulitple bugs fixed - old failed jobs in output removed, better subjetc for email, eail for output nolonger says failed when it worked.
   Revision 1.22  2005/01/18 22:01:00  womullan
    Fixed password problem on profile screen - does not reset pass now when no password inerted
   Revision 1.21  2004/11/01 21:10:03  womullan
    Absolute Namespace :
   Revision 1.20  2004/10/22 16:29:47  nli
   *** empty log message ***
   Revision 1.19  2004/10/08 15:57:29  nli
   --fixed account creation error
   Revision 1.18  2004/10/08 15:13:39  nli
   --queue position available in job details
   --queues page shows all queues, and all waiting jobs
   --it also shows waiting output jobs (separately)
   --session timeout set to one hour
   -- password encryption
   Revision 1.17  2004/09/30 15:11:43  nli
   --fixed mydb time limit in service
   --fixed cookie error on submitpage
   Revision 1.16  2004/09/24 16:24:58  nli
   reviewed create/modify/delete account
   modify account was broken; fixed now
   new test included
   Revision 1.15  2004/09/23 23:51:53  nli
   --fixed create account max_size bug.  need test for that...
   Revision 1.14  2004/09/10 15:12:36  womullan
   spexecutesql2
   Revision 1.13  2004/09/02 20:49:45  nli
   --can modify mydbmaxsize and privs from admin page
   Revision 1.12  2004/09/02 18:48:40  nli
   --unit tests
   --massive debugging, many changes to execution
   --new help page
   Revision 1.11  2004/08/04 14:29:55  nli
   asdf
   Revision 1.9  2004/07/30 13:48:46  nli
   fixed bug in permission granting
   also added create view permission
   delete user added
   fixed output service bug for types other than csv
   Revision 1.8  2004/07/23 17:58:03  nli
   --added user sync
   --fixed bug that caused mydbindex to crash in certain cases
   --changed text in browser when no mydb tables are found.
   --upped session time to 30 minutes
   Revision 1.7  2004/07/20 16:26:10  nli
   preliminary user sync
   Revision 1.6  2004/07/09 01:04:51  nli
   -added omni browser
   -misc small stuff
   Revision 1.5  2004/06/22 22:13:50  nli
   advanced priv specifically disabled
   Revision 1.4  2004/06/16 15:16:09  nli
   mydb for new account creatino bug fixed
   Revision 1.3  2004/06/15 19:03:26  nli
   -cleaned up some query editing.
   -sps and functions might work
   -can do into mydb.table as just into table
   -new visibility setting for extracts
   -implemented intant extracts with row limiting
   -implented extract type availability based on table size
   Revision 1.2  2004/06/09 20:47:13  womullan
    DDL allowed in special cases
   Revision 1.1.1.1  2004/04/27 16:38:31  cvsuser
   import CasJobs to FERMI
   Revision 1.3  2004/03/16 22:52:53  nli
   sessioned viewjobs, added spoof user to admin
   Revision 1.2  2004/02/18 17:16:19  nli
   0 mydbhost bug fixed *maybe*
   Revision 1.1.1.1  2004/02/18 16:38:28  nli
   new version
   Revision 1.2  2004/01/27 19:28:05  nli
   yup
   Revision 1.1.1.1  2004/01/25 18:16:58  nli
   first
   Revision 1.1.1.1  2004/01/20 20:59:57  nli
   new
   Revision 1.39  2003/11/26 22:06:41  womullan
    added name some places
   Revision 1.38  2003/11/21 15:30:57  nli
   *** empty log message ***
   Revision 1.37  2003/10/21 17:49:18  nli
   parse bug
   Revision 1.36  2003/10/21 17:20:38  nli
   mydb creation bug
   Revision 1.35  2003/10/21 17:13:51  nli
   virtualhost implemented, all targets are virtual
   Revision 1.34  2003/09/17 18:05:09  nli
   output stuff
   Revision 1.33  2003/09/10 17:12:30  nli
   exception in procras
   Revision 1.32  2003/08/26 18:41:34  nli
   linked mydbs
   Revision 1.31  2003/08/19 19:19:16  nli
   test
   Revision 1.30  2003/08/19 19:10:56  nli
   test
   Revision 1.29  2003/08/13 19:51:19  nli
   cpl group pages in, no top part
   Revision 1.28  2003/08/04 21:11:53  womullan
   name spaces
   Revision 1.27  2003/07/31 21:33:10  nli
   it broke
   Revision 1.26  2003/07/30 20:42:30  nli
   short job timeouts should actually be working correctly now.  empty timeouts are in there but untested
   Revision 1.25  2003/07/30 15:53:10  nli
   -test
   Revision 1.24  2003/07/29 21:05:59  nli
   cleaned up some mydb stuff
   Revision 1.23  2003/07/28 20:17:55  nli
   Privileges simplified.  Not bool, just throws exception
   -added priv to casjobs
   Revision 1.22  2003/07/28 20:01:26  nli
   -authentication in CasUsers
   -build.build compiles extras from reference
   Revision 1.21  2003/07/24 17:15:43  nli
   various fields should be filled out correctly by outputcore
   Revision 1.20  2003/07/17 21:12:59  womullan
   extract table list works
   Revision 1.19  2003/07/17 19:57:47  nli
   mydb target support
   Revision 1.18  2003/07/02 20:29:53  nli
   --full lazy creation mydb support
   --changed some *.aspx so non ms cronies can run it
   --failed submitJob should set failed status
   Revision 1.17  2003/07/02 16:19:
     womullan
   mail working now
   Revision 1.16  2003/07/02 15:25:40  womullan
   maill added for forgot pass/ cancel job eorks
   Revision 1.15  2003/07/01 20:57:47  nli
   mydb lazy creation should work
   Revision 1.14  2003/07/01 19:31:40  nli
   --though incomplete, mydb might work now
   Revision 1.13  2003/06/27 17:38:05  nli
   -fixed bug in HasMyDB(wsid) method
   Revision 1.12  2003/06/26 21:36:45  nli
   added mydb creation support.   Core methods are in casusers
   Revision 1.11  2003/06/26 20:08:40  nli
   formatting stuff in casjobs
   Revision 1.10  2003/06/26 19:13:06  nli
   new webmethod in CasUsers called HasMyDB(wsid)
   Revision 1.9  2003/06/25 16:09:08  womullan
   jobs started
   Revision 1.8  2003/06/23 16:35:25  nli
   generated wsids restricted to positive, added procrastinate base
   Revision 1.7  2003/06/23 16:04:08  nli
   informative exceptions in users.GetWebServiceId
   Revision 1.6  2003/06/20 19:10:49  nli
   short qrys registered
   Revision 1.5  2003/06/20 18:33:40  nli
   added submitShortJob method in CasJobs
   Revision 1.4  2003/06/20 16:02:25  nli
   users done and tested (somewhat)
   Revision 1.3  2003/06/19 21:58:33  nli
   casusers complete, but utterly untested
   Revision 1.2  2003/06/19 19:37:20  womullan
   users added
   */
