<?php
//
// +---------------------------------------------------------------------------+
// | Nitro :: Session |
// +---------------------------------------------------------------------------+
// | Copyright (c) 2003 June Systems BV |
// +---------------------------------------------------------------------------+
// | 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; either version 2.1 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 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, write to the Free Software Foundation, |
// | Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA |
// +---------------------------------------------------------------------------+
// | Authors: Siggi Oskarsson <hide@address.com> |
// +---------------------------------------------------------------------------+
//
// $Id: Session.inc.php 229 2008-04-17 09:20:31Z oli $
//
// This file contains the session handlers for Nitro
//
/**
* This file contains the Session class of Nitro
*
* @package Nitro
* @subpackage Base
* @author Siggi Oskarsson
* @version $Revision: 1.31 $
* @copyright 2004 June Systems BV
*/
define ('_DEF_SESSIONTIMEOUT', 20); // minutes
define ('_DEF_LOGINTEMPLATE', 'Defaults/Templates/Login.tpl');
/**
* NitroSession class
*
* @package Nitro
* @subpackage Base
*/
class NitroSession {
var $DB;
var $Conf;
var $UserLoginModule;
var $UserID;
var $User = Array();
var $LoggedOn = FALSE;
var $LoginSalt;
var $SecurityGroups;
var $SecurityGroupsLogout;
var $SessionUserAgent;
var $SessionStart;
var $SessionLastView;
var $LoginFailed;
var $SessionExpired;
var $Language;
var $CacheConfig;
var $PageStack = Array();
function NitroSession() {
global $DB, $__NitroConf;
// If SessionExpired is posted than session jues expired (needed for wrong passwd re-attempt)
if ($_POST['SessionExpired']) $this->SessionExpired = 1;
$this->DB = &$DB;
$this->Conf = &$__NitroConf;
}
function Login($username, $password, $salt = FALSE) {
// Check whether we need to restore GET and POST variables when session expired
if ($_POST["SessionGET"]) {
$GETVars = unserialize(base64_decode($_POST["SessionGET"]));
if (is_array($GETVars)) {
global $_GET;
foreach($GETVars AS $Key => $Values) {
$_GET[$Key] = $Values;
}
}
}
if ($_POST["SessionPOST"]) {
$POSTVars = unserialize(base64_decode($_POST["SessionPOST"]));
if (is_array($POSTVars)) {
global $_POST;
foreach($POSTVars AS $Key => $Values) {
if ($Key != "_Nitro_Login_username" && $Key != "_Nitro_Login_password")
$_POST[$Key] = $Values;
}
}
}
if ($username && $password) {
DebugGroup("Session", "Login", "Login attempted", __FILE__, __LINE__, DEBUG_SESS_OK);
if ($this->Conf->CONF["Settings"]["UserLoginModule"]) {
#######################################################################
# User login module used
#
# This uses an external module in the Nitro/Session/ Directory to
# handle the login and checksession functions.
#######################################################################
Debug("Session", "Login", "Logging in with custom user module:".$this->Conf->CONF["Settings"]["UserLoginModule"], __FILE__, __LINE__, DEBUG_SESS_OK);
if (is_object($this->UserLoginModule)) {
$User = $this->UserLoginModule->Login($username, $password);
} else {
$this->Error = "Login class (NitroSession_".$this->Conf->CONF["Settings"]["UserLoginModule"].") defined in settings is not initalized!";
$this->RaiseFatalError();
}
} else {
if ($this->Conf->CONF["Settings"]["UserTable"]) {
#######################################################################
# Custom user table used
#
# This uses a custom database table, but the same principle login
# method as the default Nitro login.
#######################################################################
Debug("Session", "Login", "Using custom user table:".$this->Conf->CONF["Settings"]["UserTable"], __FILE__, __LINE__, DEBUG_SESS_OK);
if ($ParsedUserTable = $this->ParseUserTableString($this->Conf->CONF["Settings"]["UserTable"])) {
$DBAlias = $ParsedUserTable["DBAlias"];
$Query = "SELECT ".($ParsedUserTable["Columns"] ? implode(",", $ParsedUserTable["Columns"]) : $ParsedUserTable["Username"]).",
".$ParsedUserTable["Password"]." AS Password
FROM ".$ParsedUserTable["Table"]."
WHERE ".$ParsedUserTable["Username"]." = ".NitroPrepareDB($username)."
AND ".$ParsedUserTable["Password"]." = ".NitroPrepareDB(sha1($password))."
".($ParsedUserTable["WHERE"] ? " AND ".implode(" AND ", $ParsedUserTable["WHERE"]) : "")."
";
//echo $Query;print_r($ParsedUserTable["WHERE"]);
} else {
Debug("Session", "Login", "User table definition is incorrect", __FILE__, __LINE__, DEBUG_SESS_ERR);
$this->Error = "User table definition is incorrect";
return FALSE;
}
} else {
#######################################################################
# Default Nitro login code
#######################################################################
Debug("Session", "Login", "Using default user table", __FILE__, __LINE__, DEBUG_SESS_OK);
$DBAlias = "Nitro";
// password = md5(real_password + salt)
// $this->LoginSalt
$Query = "SELECT UserID, Name, Password
FROM User
WHERE Username = ".NitroPrepareDB($username)."
AND Password = ".NitroPrepareDB(sha1($password))."
";
}
//echo $this->DB[$DBAlias]->PrepareQuery($Query);exit;
$Result = $this->DB[$DBAlias]->query($Query);
if ($Result->numRows()) {
Debug("Session", "Login", "Login successful, setting session vars", __FILE__, __LINE__, DEBUG_SESS_OK);
$User = $Result->fetchArray();
} else {
Debug("Session", "Login", "Login failed", __FILE__, __LINE__, DEBUG_SESS_ERR);
$this->LoginFailed = TRUE;
}
$Result->free();
}
#######################################################################
# Complete login if successfull
#######################################################################
if (is_array($User) && $User['UserID']) {
// Log on successful, call startSession
$this->startSession($User);
} else {
// login attempted, but failed, sleep 2 seconds to slow down hackers
sleep(2);
}
DebugCloseGroup(DEBUG_SESS_OK);
} else {
Debug("Session", "Login", "Login attempted with empty username or password", __FILE__, __LINE__, DEBUG_SESS_ERR);
$this->LoginFailed = TRUE;
}
$this->LoginSalt = FALSE;
return ((is_array($User) && $User['UserID']) ? TRUE : FALSE);
}
/**
* Start the Nitro session after successfull logon
*
* This function sets the necessary session variables,
* regenerates the session id, loads the user info, retreives
* the user security groups and fills the login stack if exists.
*
* @param array $User Array of user variables ($User['UserID'] mandatory!)
*/
function startSession($User)
{
DebugGroup(__CLASS__, __FUNCTION__, "Starting session", __FILE__, __LINE__, DEBUG_SESS_OK);
$this->LoggedOn = TRUE;
$this->LoginFailed = FALSE;
$this->SessionStart = time();
$this->SessionUserAgent = md5($_SERVER["HTTP_USER_AGENT"]);
$this->UserID = $User['UserID'];
$this->User = Array();
$this->LoadUserSettings();
foreach($User AS $ID => $Value) {
$this->User[$ID] = $Value;
}
Debug(__CLASS__, __FUNCTION__, "Setting security groups", __FILE__, __LINE__, DEBUG_SESS_OK);
$this->SecurityGroups = NitroGetSecurityGroups($this->UserID);
if (count($this->SecurityGroups)) {
// check whether session expired
if ($_POST['SessionExpired'] OR $this->SessionExpired) {
// continue session & no login stack
Debug(__CLASS__, __FUNCTION__, "Session expired, skipping login stack", __FILE__, __LINE__, DEBUG_SESS_OK);
$this->SessionExpired = 0;
} else {
Debug(__CLASS__, __FUNCTION__, "Setting login page stack for user to walk through", __FILE__, __LINE__, DEBUG_SESS_OK);
$Query = "SELECT
P.PageID, P.IDString, LSP.Condition, LS.ForceLastStack, LS.LoginStackID
FROM SecurityGroup AS SG
INNER JOIN LoginStack AS LS
ON LS.LoginStackID = SG.LoginStackID
INNER JOIN LoginStackPage AS LSP
ON LSP.LoginStackID = LS.LoginStackID
INNER JOIN Page_SecurityGroup AS PSG
ON PSG.SecurityGroupID = SG.SecurityGroupID
AND PSG.PageID = LSP.PageID
INNER JOIN Page AS P
ON P.PageID = LSP.PageID
WHERE SG.SecurityGroupID IN (".implode(",", $this->SecurityGroups).")
GROUP BY P.PageID, P.IDString, LS.LoginStackID, LSP.Condition, LS.ForceLastStack, LS.Sortorder, LSP.Sortorder
ORDER BY LS.Sortorder, LSP.Sortorder DESC
";
//echo $Query;exit;
$Result = $this->DB["Nitro"]->query($Query);
$n = 0;
$LoginStackID = FALSE;
while($Row = $Result->fetchArray()) {
if (!$LoginStackID OR $LoginStackID == $Row["LoginStackID"]) {
// Check whether ForceLastStack is true and then add the current
// Page to the stack as the exit point
if (!$n && !$Row["ForceLastStack"]) {
array_push($this->PageStack, $_GET["P"]);
}
// Check if condition is used and if it is true
if($Row["Condition"]) {
include_once "Nitro/Condition.inc.php";
$Condition = CheckCondition($Row["Condition"]);
} else {
$Condition = TRUE;
}
// Add Page to the stack if condition is true
if ($Condition) {
array_push($this->PageStack, $Row["IDString"]);
}
$n++;
} else {
// Next login stack, break loop and continue with script
break;
}
$LoginStackID = $Row["LoginStackID"];
}
$Result->free();
}
}
// Regenerate session id and delete old session data
Debug(__CLASS__, __FUNCTION__, "Regenerating session id", __FILE__, __LINE__, DEBUG_SESS_OK);
NitroRegenerateSessionID();
Debug(__CLASS__, __FUNCTION__, "New session id: ".session_id(), __FILE__, __LINE__, DEBUG_SESS_OK);
DebugCloseGroup(DEBUG_SESS_OK);
}
/**
* Parse user table string
*
* This function parses a user table string and returns it in an
* array the login function recongnizes. Format:
*
* [DBAlias]:[Table]:[Username Column,Password Column]:[WHERE statement]:[Other columns to get]
*
* @param $UserTable User table string definition
*/
function ParseUserTableString($UserTable){
$UserTable = explode(":", $UserTable);
if (count($UserTable) == 5) {
$RV = Array();
$RV["DBAlias"] = $UserTable[0];
$RV["Table"] = $UserTable[1];
$RV["Username"] = current(explode(",", $UserTable[2]));
$RV["Password"] = end(explode(",", $UserTable[2]));
$RV["WHERE"] = ($UserTable[3] ? explode(",", $UserTable[3]) : FALSE);
if (is_array($RV["WHERE"])) {
foreach($RV["WHERE"] AS $key => $where) {
// fix for ini failure with = signs, must use something else like -->
$clause = explode('-->', $where);
if (!ereg('[<>=]', $clause[1])) $RV["WHERE"][$key] = $clause[0].' = '.$clause[1];
else $RV["WHERE"][$key] = $clause[0].' '.$clause[1];
}
}
$RV["Columns"] = explode(",", $UserTable[4]);
} else {
$RV = FALSE;
}
return $RV;
}
function CheckSession(){
Debug("Session", "CheckSession", "Checking session validity: ".session_id(), __FILE__, __LINE__, DEBUG_SESS_OK);
// If a custom session module is used, include it and set in the class
if ($this->Conf->CONF["Settings"]["UserLoginModule"]) {
Debug("Session", "NitroSession", "Using custom user module:".$this->Conf->CONF["Settings"]["UserLoginModule"], __FILE__, __LINE__, DEBUG_SESS_OK);
$includeFile = 'Nitro/Session/'.$this->Conf->CONF["Settings"]["UserLoginModule"].'.inc.php';
// Let PHP find the include file for us
if (@include_once($includeFile)) {
if (class_exists('NitroSession_'.$this->Conf->CONF["Settings"]["UserLoginModule"])) {
eval('$this->UserLoginModule = new NitroSession_'.$this->Conf->CONF["Settings"]["UserLoginModule"].'($this);');
if (!is_object($this->UserLoginModule)) {
$this->Error = "Login class (NitroSession_".$this->Conf->CONF["Settings"]["UserLoginModule"].") defined in settings does not return a valid object!";
$this->RaiseFatalError();
}
} else {
$this->Error = "Login class (NitroSession_".$this->Conf->CONF["Settings"]["UserLoginModule"].") defined in settings does not exist!";
$this->RaiseFatalError();
}
} else {
$this->Error = "Login module (".$this->Conf->CONF["Settings"]["UserLoginModule"].") defined in settings does not exist!";
$this->RaiseFatalError();
}
$this->UserLoginModule->CheckSession();
} else {
Debug("Session", "NitroSession", "Using default Nitro user module", __FILE__, __LINE__, DEBUG_SESS_OK);
// Max session duration in seconds, conf is in minutes, default = 20
$MaxSession = 60*($this->Conf->CONF["Settings"]["SessionTimeout"] ? $this->Conf->CONF["Settings"]["SessionTimeout"] : _DEF_SESSIONTIMEOUT);
if ($this->UserID && ((time() - $this->SessionLastView) > $MaxSession)) {
Debug("Session", "CheckSession", "Session expired", __FILE__, __LINE__, DEBUG_SESS_ERR);
$this->SessionExpired = TRUE;
$this->Logout();
}
//TODO: - Add token to cookie => $token = md5(uniqid(rand(), true));
if ($this->UserID && $this->SessionUserAgent != md5($_SERVER["HTTP_USER_AGENT"])) {
Debug("Session", "CheckSession", "User agent has changed, spoofing???", __FILE__, __LINE__, DEBUG_SESS_ERR);
$this->Logout();
}
$this->SessionLastView = time();
}
}
function Logout() {
Debug("Session", "Logout", "Logout called, killing session", __FILE__, __LINE__, DEBUG_SESS_OK);
$this->SaveUserSettings();
$this->LoggedOn = FALSE;
$this->UserID = FALSE;
$this->User = Array();
$this->SecurityGroups = $this->SecurityGroupsLogout;
$this->Name = FALSE;
// Regenerate session id and delete old session data
NitroRegenerateSessionID();
return TRUE;
}
function LoadUserSettings() {
global $__NSess;
DebugGroup("Session", "LoadUserSettings", "Loading User Settings", __FILE__, __LINE__, DEBUG_SESS_OK);
if ($this->UserID && $this->Conf->CONF["Settings"]["UserSaveSettings"]) {
$UserSaveSettings = explode(":", $this->Conf->CONF["Settings"]["UserSaveSettings"]);
if (count($UserSaveSettings) == 4) {
Debug("Session", "LoadUserSettings", "Settings column defined (correctly) in config file", __FILE__, __LINE__, DEBUG_SESS_OK);
$SettingsDBAlias = $UserSaveSettings[0];
$SettingsTable = $UserSaveSettings[1];
$SettingsUserID = $UserSaveSettings[2];
$SettingsColumn = $UserSaveSettings[3];
$Query = "SELECT $SettingsColumn FROM $SettingsTable
WHERE $SettingsUserID = $this->UserID
";
//echo $Query;
$Settings = unserialize($this->DB[$SettingsDBAlias]->getOne($Query));
if (is_array($Settings)) {
Debug("Session", "LoadUserSettings", "Saved settings loaded", __FILE__, __LINE__, DEBUG_SESS_OK);
$this->User = $Settings;
$RV = TRUE;
} else {
Debug("Session", "LoadUserSettings", "No previous saved settings found", __FILE__, __LINE__, DEBUG_SESS_OK);
$RV = FALSE;
}
} else {
Debug("Session", "LoadUserSettings", "Settings column not defined (correctly) in config file", __FILE__, __LINE__, DEBUG_SESS_ERR);
$RV = FALSE;
}
}
DebugCloseGroup(DEBUG_SESS_OK);
return $RV;
}
function SaveUserSettings() {
DebugGroup("Session", "SaveUserSettings", "Saveing User Settings", __FILE__, __LINE__, DEBUG_SESS_OK);
$RV = FALSE;
if ($this->UserID && array_key_exists('UserSaveSettings', $this->Conf->CONF["Settings"]) && $this->Conf->CONF["Settings"]["UserSaveSettings"]) {
$UserSaveSettings = explode(":", $this->Conf->CONF["Settings"]["UserSaveSettings"]);
if (count($UserSaveSettings) == 4) {
Debug("Session", "SaveUserSettings", "Settings column defined (correctly) in config file", __FILE__, __LINE__, DEBUG_SESS_OK);
$SettingsDBAlias = $UserSaveSettings[0];
$SettingsTable = $UserSaveSettings[1];
$SettingsUserID = $UserSaveSettings[2];
$SettingsColumn = $UserSaveSettings[3];
$Query = "UPDATE $SettingsTable SET
$SettingsColumn = ".NitroPrepareDB(serialize($this->User))."
WHERE $SettingsUserID = $this->UserID
";
if ($this->DB[$SettingsDBAlias]->query($Query)) {
Debug("Session", "SaveUserSettings", "Settings saved", __FILE__, __LINE__, DEBUG_SESS_OK);
$RV = TRUE;
} else {
Debug("Session", "SaveUserSettings", "Settings could not be saved", __FILE__, __LINE__, DEBUG_SESS_ERR);
$RV = FALSE;
}
} else {
Debug("Session", "SaveUserSettings", "Settings column not defined (correctly) in config file", __FILE__, __LINE__, DEBUG_SESS_ERR);
$RV = FALSE;
}
}
$__NSess = $this; //force write of session to $_SESSION["Nitro"]
DebugCloseGroup(DEBUG_SESS_OK);
return $RV;
}
function DrawLoginScreen() {
DebugGroup(__CLASS__, __FUNCTION__, "Drawing login screen", __FILE__, __LINE__, DEBUG_SESS_OK);
if($this->Conf->CONF["Settings"]["ForceSSL"] && $_SERVER["HTTPS"] != "on" && $_SERVER["HTTP_POUND_HTTPS"] != "on"){
if (!$Server = $this->Conf->CONF["Settings"]["ForceSSLServer"]) {
$Server = $_SERVER["HTTP_HOST"];
}
Debug(__CLASS__, __FUNCTION__, "SSL forced, but not turned on, redirecting", __FILE__, __LINE__, DEBUG_SESS_OK);
$this->SaveToDisk();
Header("Location: https://".$Server.$_SERVER["REQUEST_URI"]);
exit;
}
$DB = $this->DB; global $DB, $GlobalDBAlias; // needed for Template class
include_once NITRO_PATH.'Template.inc.php';
$this->LoginSalt = NitroCreateRandomString(32);
$POST = $_POST;
unset($POST['_Nitro_Login_username']); // remove username/password from POST data
unset($POST['_Nitro_Login_password']); // prevents repost and filling of POST on password failure
$Login = new NitroTemplate(GetNitroTemplateID("NitroLogin"));
$Login->Assign("GET", base64_encode(serialize($_GET)));
$Login->Assign("POST", base64_encode(serialize($POST)));
$Login->Assign("username", htmlspecialchars($_REQUEST["_Nitro_Login_username"]));
$Login->Assign("salt", $this->LoginSalt);
$Login->Assign("LoggedOn", $this->LoggedOn);
$Login->Assign("SessionExpired", $this->SessionExpired);
$Login->Assign("LoginFailed", ($_POST['_Nitro_Login_username'] ? $this->LoginFailed : FALSE));
//TODO: Login No rights implementeren
$RV = $Login->fetch("file:".NITRO_PATH._DEF_LOGINTEMPLATE);
DebugCloseGroup(DEBUG_SESS_OK);
return $RV;
}
/**
* Clear the page stack set during logon
*/
function ClearPageStack()
{
$this->PageStack = Array();
}
/**
* Raise fatal session error, show error page and exit
*/
function RaiseFatalError()
{
if (file_exists($_SERVER["DOCUMENT_ROOT"]."/SessionError.php")) {
/* Include error page /SessionError.php if error found and file exists */
include $_SERVER["DOCUMENT_ROOT"]."/SessionError.php";
} elseif (file_exists($_SERVER["DOCUMENT_ROOT"]."/SessionError.html")) {
/* Include error page /SessionError.html if error found and file exists */
include $_SERVER["DOCUMENT_ROOT"]."/SessionError.html";
} elseif (file_exists(NITRO_PATH."/Defaults/Texts/SessionError.html")) {
/* Include error page Defaults/Texts/SessionError.html if error found and file exists */
include NITRO_PATH."Defaults/Texts/SessionError.html";
} else {
echo "ERROR: ".$this->Error;
}
echo $this->Error;
exit;
}
function SaveToDisk()
{
global $__Nitro_ReadOnly_Session;
DebugGroup(__CLASS__, __FUNCTION__, "Saving session to disk", __FILE__, __LINE__, DEBUG_SESS_OK);
if ($__Nitro_ReadOnly_Session || Headers_sent()) {
//Headers sent out, can not restart session to save it;
// OR session doesnt need to be saved
$rv = FALSE;
} else {
NitroSession_SaveToDisk();
$rv = TRUE;
}
DebugCloseGroup(DEBUG_SESS_OK);
return $rv;
}
}
?>