/**********************************************************************
 *
 * authenaction.cpp -- authenticating users
 * Copyright (C) 1999  DigiLib Systems Limited, New Zealand
 *
 * A component of the Greenstone digital library software
 * from the New Zealand Digital Library Project at the
 * University of Waikato, New Zealand.
 *
 * This program 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 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 General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 *********************************************************************/

#include "gsdl_modules_cfg.h"
#ifdef GSDL_USE_AUTHEN_ACTION

#include "authenaction.h"
#include "fileutil.h"
#include "cfgread.h"
#include "cgiutils.h"
#include "gsdltimes.h"


///////////////
// authenaction
///////////////

authenaction::authenaction () {
  keydecay = 1800; // 30 minutes
  recpt = NULL;

  // this action uses cgi variable "a"
  cgiarginfo arg_ainfo;
  arg_ainfo.shortname = "a";
  arg_ainfo.longname = "action";
  arg_ainfo.multiplechar = true;
  arg_ainfo.multiplevalue = false;
  arg_ainfo.defaultstatus = cgiarginfo::weak;
  arg_ainfo.argdefault = "a";
  arg_ainfo.savedarginfo = cgiarginfo::must;
  argsinfo.addarginfo (NULL, arg_ainfo);

  // "us"
  arg_ainfo.shortname = "us";
  arg_ainfo.longname = "user account status";
  arg_ainfo.multiplechar = true;
  arg_ainfo.multiplevalue = false;
  arg_ainfo.defaultstatus = cgiarginfo::weak;
  arg_ainfo.argdefault = "invalid";
  arg_ainfo.savedarginfo = cgiarginfo::mustnot;
  argsinfo.addarginfo (NULL, arg_ainfo);

  // "ug"
  arg_ainfo.shortname = "ug";
  arg_ainfo.longname = "user groups"; // comma seperated list
  arg_ainfo.multiplechar = true;
  arg_ainfo.multiplevalue = false;
  arg_ainfo.defaultstatus = cgiarginfo::weak;
  arg_ainfo.argdefault = g_EmptyText;
  arg_ainfo.savedarginfo = cgiarginfo::mustnot;
  argsinfo.addarginfo (NULL, arg_ainfo);

  // "un"
  arg_ainfo.shortname = "un";
  arg_ainfo.longname = "user name";
  arg_ainfo.multiplechar = true;
  arg_ainfo.multiplevalue = false;
  arg_ainfo.defaultstatus = cgiarginfo::weak;
  arg_ainfo.argdefault = g_EmptyText;
  arg_ainfo.savedarginfo = cgiarginfo::must;
  argsinfo.addarginfo (NULL, arg_ainfo);

  // "pw"
  arg_ainfo.shortname = "pw";
  arg_ainfo.longname = "password";
  arg_ainfo.multiplechar = true;
  arg_ainfo.multiplevalue = false;
  arg_ainfo.defaultstatus = cgiarginfo::weak;
  arg_ainfo.argdefault = g_EmptyText;
  arg_ainfo.savedarginfo = cgiarginfo::mustnot;
  argsinfo.addarginfo (NULL, arg_ainfo);

  // "ky" - gives a specific user authentication for a
  // limited amount of time
  arg_ainfo.shortname = "ky";
  arg_ainfo.longname = "user time key";
  arg_ainfo.multiplechar = true;
  arg_ainfo.multiplevalue = false;
  arg_ainfo.defaultstatus = cgiarginfo::weak;
  arg_ainfo.argdefault = g_EmptyText;
  arg_ainfo.savedarginfo = cgiarginfo::must;
  argsinfo.addarginfo (NULL, arg_ainfo);

  // "ua" - ""=no, "1"=yes 
  arg_ainfo.shortname = "ua";
  arg_ainfo.longname = "whether a user has been authenticated";
  arg_ainfo.multiplechar = true;
  arg_ainfo.multiplevalue = false;
  arg_ainfo.defaultstatus = cgiarginfo::weak;
  arg_ainfo.argdefault = g_EmptyText;
  arg_ainfo.savedarginfo = cgiarginfo::mustnot;
  argsinfo.addarginfo (NULL, arg_ainfo);

  // "er" - compressed arguments for the referer page
  arg_ainfo.shortname = "er";
  arg_ainfo.longname = "the compressed args of the refer page";
  arg_ainfo.multiplechar = true;
  arg_ainfo.multiplevalue = false;
  arg_ainfo.defaultstatus = cgiarginfo::weak;
  arg_ainfo.argdefault = g_EmptyText;
  arg_ainfo.savedarginfo = cgiarginfo::mustnot;
  argsinfo.addarginfo (NULL, arg_ainfo);

  // "uan" - whether user authentication is needed
  arg_ainfo.shortname = "uan";
  arg_ainfo.longname = "whether user authentication is needed";
  arg_ainfo.multiplechar = true;
  arg_ainfo.multiplevalue = false;
  arg_ainfo.defaultstatus = cgiarginfo::weak;
  arg_ainfo.argdefault = g_EmptyText;
  arg_ainfo.savedarginfo = cgiarginfo::mustnot;
  argsinfo.addarginfo (NULL, arg_ainfo);
}

void authenaction::configure (const text_t &key, const text_tarray &cfgline) {
  action::configure (key, cfgline);
}

bool authenaction::init (ostream &logout) {
  if (dbhome.empty()) {
    logout << "ERROR (authenaction::init) dbhome is not set\n";
    return false;
  }

  return action::init (logout);
}

bool authenaction::check_cgiargs (cgiargsinfoclass &/*argsinfo*/, cgiargsclass &/*args*/, 
				  recptprotolistclass * /*protos*/, ostream &/*logout*/) {
  return true;
}

// returns false if there is a major problem with the cgi arguments -- not
// if authentication fails. If the authentication fails "un" will be empty
bool authenaction::check_external_cgiargs (cgiargsinfoclass &argsinfo,
					   cgiargsclass &args,
					   outconvertclass &outconvert,
					   const text_t &saveconf,
					   ostream &logout) {

  // no need to go further unless authentication is
  // required by this page
  if (args["uan"].empty()) {
    return true;
  }

  // failure means we have to redirect to this action to get authentication
  // (if we are not already doing this)

  userinfo_t thisuser;

  text_t &args_uan = args["uan"];  text_t &args_un = args["un"];
  text_t &args_pw = args["pw"];    text_t &args_us = args["us"];
  text_t &args_ug = args["ug"];    text_t &args_ky = args["ky"];
  text_t &args_ua = args["ua"];    text_t &args_a = args["a"];

  // we must have a username and a password or a key to
  // do any authentication
  args_ua.clear(); // default = false;
  if (args_un.empty() || args_pw.empty()) {
    args_us = "invalid";
  }
  else {
    args_us = "failed";
  }

  // make sure we have a username                                                                                                 
  int status = user_database->get_user_info (args_un, thisuser);
  if (!args_un.empty() && (status == ERRNO_SUCCEED)) {
    if (!args_pw.empty()) {
      // we are authenticating using a password
      if (user_database->check_passwd (thisuser.username, args_pw) == ERRNO_SUCCEED) {
	args_ua = "1"; // succeeded
      }
      
    } else if (!args_ky.empty()) {
      // we are authenticating using a key
      if (key_database->check_key(thisuser, args_ky, args_ug, keydecay)) {
	args_ua = "1";
      }
      else {
	args_us = "stalekey";
      }
    }
  }

  args_pw.clear(); // password goes no further
  if (!args_ua.empty()) {
    if (thisuser.enabled) {
      bool haspermission = true;

      // check to make sure the user is in the required group
      // one group is available only at the moment.
      // this is what we are changing !
      
      if (!args_ug.empty()) {

	 // Since we receive a comma separated list
	 // of groups like mygroup,yourgroup,ourgroup
	 // we want to split them into individual groups
	 // and examine them. This is what is done here.
	 
	 text_tset splitgrps;
	 text_t::const_iterator split_here = args_ug.begin();
	 text_t::const_iterator split_end = args_ug.end();

	 splitchar(split_here,split_end,',',splitgrps);

	 haspermission = false;

	 // This examines the current user to be authenticated and
	 // tries to see if he or she is in the group that we have in
	 // thisuser structure. We compare args_ua contents with that
	 // of the user database.
	 
	 text_t::const_iterator group_here = thisuser.groups.begin();
	 text_t::const_iterator group_end = thisuser.groups.end();
	 text_t thisgroup;
	while (group_here != group_end) {
	   group_here = getdelimitstr (group_here, group_end, ',', thisgroup);
	   if (splitgrps.find(thisgroup) != splitgrps.end() )
	      {
		 haspermission = true;
		 break;
	      }
	}
      }
      
      if (haspermission) {
	 // succeeded, get info about this user
	 // note: we don't need to set "ug" as it is already set to what it needs to be
	 args_us = "enabled";
	 args_ky = key_database->generate_key(args_un); // new key
	
	// delete old keys around every 50 accesses
	if (rand()%50 == 1) key_database->remove_old_keys(keydecay);
	
      } else {
	// succeeded, however, the user is not in the correct group
	 args_ua.clear();
	 args_us = "permissiondenied";
	 args_ky.clear();
      }
      
    } else {
       // succeeded, however, the account is disabled
      args_ua.clear();
      args_us = "disabled";
      args_ky.clear();
    }

  } else {
    // failure, reset info about the user
    args_ky.clear();
  }

  // we will have to redirect the user if authentication is needed,
  // it failed, and we weren't on our way to be authenticated anyway
  if ((!args_uan.empty()) && (args_ua.empty()) && (args_a != "a")) {
    // need to save the current arguments in "er"
    text_t &arg_er = args["er"];
    if (!compress_save_args(argsinfo, saveconf, args, arg_er, outconvert, logout)) {
      arg_er.clear();
    }
    
    // needs to be decoded for use within forms
    decode_cgi_arg (arg_er);    

    // redirect to this action
    args_a = "a";
  }

  return true;
}

void authenaction::get_cgihead_info (cgiargsclass &/*args*/, recptprotolistclass * /*protos*/,
				     response_t &response, text_t &response_data, 
				     ostream &/*logout*/) {
  response = content;
  response_data = "text/html";
}

void authenaction::define_internal_macros (displayclass &disp, cgiargsclass &args, 
					   recptprotolistclass * /*protos*/, ostream &/*logout*/) {
  // sets _authen:messageextra_ based on the value of args["us"]
  //      _authen:hiddenargs_   to contain all the arguments that were
  //                            explicitly set
  disp.setmacro ("messagestatus", "authen", ("_authen:message" + encodeForHTML(args["us"])
					     + "_"));
  // change style of header and footer if page is a frame
  if ((args["p"].empty()) || (args["p"] == "frameset")) {
    disp.setmacro ("header", "authen", "_status:infoheader_(Log in)");
    disp.setmacro ("header", "authenok", "_status:infoheader_(Log in)");
    disp.setmacro ("footer", "authen", "_status:infofooter_(Log in)");
    disp.setmacro ("footer", "authenok", "_status:infofooter_(Log in)");
  }

  // get a list of saved configuration arguments (if possible)
  text_t saveconf;
  text_tset saveconfset;
  if (recpt != NULL) {
    saveconf = recpt->get_configinfo().saveconf;
    splitchar (saveconf.begin(), saveconf.end(), '-', saveconfset);
  }
  
  text_t hiddenargs;
  cgiargsclass::const_iterator args_here = args.begin();
  cgiargsclass::const_iterator args_end = args.end();
  while (args_here != args_end) {
    // set this as a hidden argument if it came from the cgi arguments,
    // its not the compressed arguments, the query string, a user name or
    // password, or collect.cfg, and if it is not in the compressed arguments
    if ((*args_here).second.source == cgiarg_t::cgi_arg &&
	(*args_here).first != "e" && (*args_here).first != "q" &&
	(*args_here).first != "un" && (*args_here).first != "pw" &&
	(*args_here).first != "cfgfile" && 
	saveconfset.find((*args_here).first) == saveconfset.end()) {
      hiddenargs += "<input type=hidden name=\"" + (*args_here).first +
	"\" value=\"_cgiarg" + (*args_here).first + "Attrsafe_\">\n";
    }
    ++args_here;
  }

  disp.setmacro ("hiddenargs", "authen", hiddenargs);
}

bool authenaction::do_action (cgiargsclass &args, recptprotolistclass * /*protos*/, 
			      browsermapclass * /*browsers*/, displayclass &disp, 
			      outconvertclass &outconvert, ostream &textout, 
			      ostream &/*logout*/) {
  if (args["us"] == "enabled") {
    // have been authenticated
    textout << outconvert << disp
	    << "_authenok:header_\n_authenok:content_\n_authenok:footer_\n";
    return true;
  }
  
  // need to be authenticated
  textout << outconvert << disp
	  << "_authen:header_\n_authen:content_\n_authen:footer_\n";

  return true;
}

#endif //GSDL_USE_AUTHEN_ACTION
