2013-04-18 15:39:34 +02:00
< ? php
/**
* Session management class
2013-09-17 14:48:16 +02:00
*
2013-04-18 15:39:34 +02:00
* http :// www . developpez . net / forums / d51943 / php / langage / sessions /
* http :// sebsauvage . net / wiki / doku . php ? id = php : session
* http :// sebsauvage . net / wiki / doku . php ? id = php : shaarli
2013-09-17 14:48:16 +02:00
*
2013-04-18 15:39:34 +02:00
* Features :
* - Everything is stored on server - side ( we do not trust client - side data ,
* such as cookie expiration )
2013-09-17 14:48:16 +02:00
* - IP addresses are checked on each access to prevent session cookie hijacking
* ( such as Firesheep )
2013-04-18 15:39:34 +02:00
* - Session expires on user inactivity ( Session expiration date is
* automatically updated everytime the user accesses a page . )
* - A unique secret key is generated on server - side for this session
2013-09-17 14:48:16 +02:00
* ( and never sent over the wire ) which can be used to sign forms ( HMAC )
* ( See $_SESSION [ 'uid' ])
* - Token management to prevent XSRF attacks
* - Brute force protection with ban management
2013-04-18 15:39:34 +02:00
*
2013-09-17 14:48:16 +02:00
* TODOs
* - Replace globals with variables in Session class
2013-04-18 15:39:34 +02:00
*
2013-09-17 14:48:16 +02:00
* How to use :
* - http :// tontof . net / kriss / php5 / session
2013-04-18 15:39:34 +02:00
*/
class Session
{
2013-09-17 14:48:16 +02:00
// Personnalize PHP session name
public static $sessionName = '' ;
2013-04-18 15:39:34 +02:00
// If the user does not access any page within this time,
2013-09-17 14:48:16 +02:00
// his/her session is considered expired (3600 sec. = 1 hour)
2014-03-17 21:22:05 +01:00
public static $inactivityTimeout = 3600 ;
2013-12-21 20:39:45 +01:00
// Extra timeout for long sessions (if enabled) (82800 sec. = 23 hours)
2014-03-17 21:22:05 +01:00
public static $longSessionTimeout = 7776000 ; // 7776000 = 90 days
2013-09-17 14:48:16 +02:00
// If you get disconnected often or if your IP address changes often.
// Let you disable session cookie hijacking protection
public static $disableSessionProtection = false ;
// Ban IP after this many failures.
public static $banAfter = 4 ;
// Ban duration for IP address after login failures (in seconds).
// (1800 sec. = 30 minutes)
public static $banDuration = 1800 ;
// File storage for failures and bans. If empty, no ban management.
public static $banFile = '' ;
2013-04-18 15:39:34 +02:00
2013-09-17 14:48:16 +02:00
/**
* Initialize session
*/
2014-03-17 21:22:05 +01:00
public static function init ( $longlastingsession = false )
2013-04-18 15:39:34 +02:00
{
2014-03-17 21:22:05 +01:00
//check if session name is correct
2014-03-21 13:59:30 +01:00
if ( ( session_id () && ! empty ( self :: $sessionName ) && session_name () != self :: $sessionName ) || $longlastingsession ) {
2014-03-17 21:22:05 +01:00
session_destroy ();
}
2013-09-17 14:48:16 +02:00
// Force cookie path (but do not change lifetime)
$cookie = session_get_cookie_params ();
// Default cookie expiration and path.
$cookiedir = '' ;
if ( dirname ( $_SERVER [ 'SCRIPT_NAME' ]) != '/' ) {
$cookiedir = dirname ( $_SERVER [ " SCRIPT_NAME " ]) . '/' ;
}
$ssl = false ;
if ( isset ( $_SERVER [ " HTTPS " ]) && $_SERVER [ " HTTPS " ] == " on " ) {
$ssl = true ;
}
2014-03-17 21:22:05 +01:00
if ( $longlastingsession ) {
2014-03-21 20:56:40 +01:00
session_set_cookie_params ( self :: $longSessionTimeout , $cookiedir , null , $ssl , true );
2014-03-17 21:22:05 +01:00
}
else {
2014-03-21 20:56:40 +01:00
session_set_cookie_params ( 0 , $cookiedir , null , $ssl , true );
2014-03-17 21:22:05 +01:00
}
2014-03-18 16:39:19 +01:00
//set server side valid session timeout
//WARNING! this may not work in shared session environment. See http://www.php.net/manual/en/session.configuration.php#ini.session.gc-maxlifetime about min value: it can be set in any application
ini_set ( 'session.gc_maxlifetime' , self :: $longSessionTimeout );
2014-03-17 21:22:05 +01:00
2013-04-18 15:39:34 +02:00
// Use cookies to store session.
ini_set ( 'session.use_cookies' , 1 );
// Force cookies for session (phpsessionID forbidden in URL)
ini_set ( 'session.use_only_cookies' , 1 );
2014-03-17 21:22:05 +01:00
if ( ! session_id () ) {
2013-04-18 15:39:34 +02:00
// Prevent php to use sessionID in URL if cookies are disabled.
ini_set ( 'session.use_trans_sid' , false );
2013-09-17 14:48:16 +02:00
if ( ! empty ( self :: $sessionName )) {
session_name ( self :: $sessionName );
}
session_start ();
2013-04-18 15:39:34 +02:00
}
}
2013-09-17 14:48:16 +02:00
/**
* Returns the IP address
* ( Used to prevent session cookie hijacking . )
*
* @ return string IP addresses
*/
private static function _allIPs ()
2013-04-18 15:39:34 +02:00
{
2013-09-17 14:48:16 +02:00
$ip = $_SERVER [ " REMOTE_ADDR " ];
$ip .= isset ( $_SERVER [ 'HTTP_X_FORWARDED_FOR' ]) ? '_' . $_SERVER [ 'HTTP_X_FORWARDED_FOR' ] : '' ;
$ip .= isset ( $_SERVER [ 'HTTP_CLIENT_IP' ]) ? '_' . $_SERVER [ 'HTTP_CLIENT_IP' ] : '' ;
2013-04-18 15:39:34 +02:00
2013-09-17 14:48:16 +02:00
return $ip ;
2013-04-18 15:39:34 +02:00
}
2013-09-17 14:48:16 +02:00
/**
* Check that user / password is correct and then init some SESSION variables .
*
* @ param string $login Login reference
* @ param string $password Password reference
* @ param string $loginTest Login to compare with login reference
* @ param string $passwordTest Password to compare with password reference
* @ param array $pValues Array of variables to store in SESSION
*
* @ return true | false True if login and password are correct , false
* otherwise
*/
public static function login (
$login ,
$password ,
$loginTest ,
$passwordTest ,
2013-12-21 20:39:45 +01:00
$longlastingsession ,
2013-09-17 14:48:16 +02:00
$pValues = array ())
2013-04-18 15:39:34 +02:00
{
2013-09-17 14:48:16 +02:00
self :: banInit ();
if ( self :: banCanLogin ()) {
if ( $login === $loginTest && $password === $passwordTest ) {
self :: banLoginOk ();
2014-03-17 21:22:05 +01:00
self :: init ( $longlastingsession );
2013-09-17 14:48:16 +02:00
// Generate unique random number to sign forms (HMAC)
$_SESSION [ 'uid' ] = sha1 ( uniqid ( '' , true ) . '_' . mt_rand ());
$_SESSION [ 'ip' ] = self :: _allIPs ();
$_SESSION [ 'username' ] = $login ;
// Set session expiration.
$_SESSION [ 'expires_on' ] = time () + self :: $inactivityTimeout ;
2013-12-21 20:39:45 +01:00
if ( $longlastingsession ) {
$_SESSION [ 'longlastingsession' ] = self :: $longSessionTimeout ;
$_SESSION [ 'expires_on' ] += $_SESSION [ 'longlastingsession' ];
}
2013-09-17 14:48:16 +02:00
foreach ( $pValues as $key => $value ) {
$_SESSION [ $key ] = $value ;
}
return true ;
}
self :: banLoginFailed ();
2013-04-18 15:39:34 +02:00
}
2013-09-17 14:48:16 +02:00
2014-03-17 21:22:05 +01:00
self :: init ();
2013-04-18 15:39:34 +02:00
return false ;
}
2013-09-17 14:48:16 +02:00
/**
* Unset SESSION variable to force logout
*/
2013-04-18 15:39:34 +02:00
public static function logout ()
{
2014-03-02 08:38:26 +01:00
// unset($_SESSION['uid'],$_SESSION['ip'],$_SESSION['expires_on'],$_SESSION['tokens'], $_SESSION['login'], $_SESSION['pass'], $_SESSION['longlastingsession'], $_SESSION['poche_user']);
// Destruction du cookie (le code peut paraître complexe mais c'est pour être certain de reprendre les mêmes paramètres)
$args = array_merge ( array ( session_name (), '' ), array_values ( session_get_cookie_params ()));
$args [ 2 ] = time () - 3600 ;
call_user_func_array ( 'setcookie' , $args );
// Suppression physique de la session
session_destroy ();
2013-04-18 15:39:34 +02:00
}
2013-09-17 14:48:16 +02:00
/**
* Make sure user is logged in .
*
* @ return true | false True if user is logged in , false otherwise
*/
2013-04-18 15:39:34 +02:00
public static function isLogged ()
{
if ( ! isset ( $_SESSION [ 'uid' ])
2013-09-17 14:48:16 +02:00
|| ( self :: $disableSessionProtection === false
&& $_SESSION [ 'ip' ] !== self :: _allIPs ())
|| time () >= $_SESSION [ 'expires_on' ]) {
2014-03-21 13:59:30 +01:00
//self::logout();
2013-09-17 14:48:16 +02:00
2013-04-18 15:39:34 +02:00
return false ;
}
// User accessed a page : Update his/her session expiration date.
2013-09-17 14:48:16 +02:00
$_SESSION [ 'expires_on' ] = time () + self :: $inactivityTimeout ;
if ( ! empty ( $_SESSION [ 'longlastingsession' ])) {
$_SESSION [ 'expires_on' ] += $_SESSION [ 'longlastingsession' ];
}
2013-04-18 15:39:34 +02:00
return true ;
}
2013-09-17 14:48:16 +02:00
/**
* Create a token , store it in SESSION and return it
*
* @ param string $salt to prevent birthday attack
*
* @ return string Token created
*/
public static function getToken ( $salt = '' )
2013-04-18 15:39:34 +02:00
{
2013-09-17 14:48:16 +02:00
if ( ! isset ( $_SESSION [ 'tokens' ])) {
2013-04-18 15:39:34 +02:00
$_SESSION [ 'tokens' ] = array ();
}
// We generate a random string and store it on the server side.
2013-09-17 14:48:16 +02:00
$rnd = sha1 ( uniqid ( '' , true ) . '_' . mt_rand () . $salt );
2013-04-18 15:39:34 +02:00
$_SESSION [ 'tokens' ][ $rnd ] = 1 ;
2013-09-17 14:48:16 +02:00
2013-04-18 15:39:34 +02:00
return $rnd ;
}
2013-09-17 14:48:16 +02:00
/**
* Tells if a token is ok . Using this function will destroy the token .
*
* @ param string $token Token to test
*
* @ return true | false True if token is correct , false otherwise
*/
2013-04-18 15:39:34 +02:00
public static function isToken ( $token )
{
2013-09-17 14:48:16 +02:00
if ( isset ( $_SESSION [ 'tokens' ][ $token ])) {
2013-04-18 15:39:34 +02:00
unset ( $_SESSION [ 'tokens' ][ $token ]); // Token is used: destroy it.
2013-09-17 14:48:16 +02:00
2013-04-18 15:39:34 +02:00
return true ; // Token is ok.
}
2013-09-17 14:48:16 +02:00
2013-04-18 15:39:34 +02:00
return false ; // Wrong token, or already used.
}
2013-09-17 14:48:16 +02:00
/**
* Signal a failed login . Will ban the IP if too many failures :
*/
public static function banLoginFailed ()
{
if ( self :: $banFile !== '' ) {
$ip = $_SERVER [ " REMOTE_ADDR " ];
$gb = $GLOBALS [ 'IPBANS' ];
if ( ! isset ( $gb [ 'FAILURES' ][ $ip ])) {
$gb [ 'FAILURES' ][ $ip ] = 0 ;
}
$gb [ 'FAILURES' ][ $ip ] ++ ;
if ( $gb [ 'FAILURES' ][ $ip ] > ( self :: $banAfter - 1 )) {
$gb [ 'BANS' ][ $ip ] = time () + self :: $banDuration ;
}
$GLOBALS [ 'IPBANS' ] = $gb ;
file_put_contents ( self :: $banFile , " <?php \n \$ GLOBALS['IPBANS']= " . var_export ( $gb , true ) . " ; \n ?> " );
}
}
/**
* Signals a successful login . Resets failed login counter .
*/
public static function banLoginOk ()
{
if ( self :: $banFile !== '' ) {
$ip = $_SERVER [ " REMOTE_ADDR " ];
$gb = $GLOBALS [ 'IPBANS' ];
unset ( $gb [ 'FAILURES' ][ $ip ]); unset ( $gb [ 'BANS' ][ $ip ]);
$GLOBALS [ 'IPBANS' ] = $gb ;
file_put_contents ( self :: $banFile , " <?php \n \$ GLOBALS['IPBANS']= " . var_export ( $gb , true ) . " ; \n ?> " );
}
}
/**
* Ban init
*/
public static function banInit ()
{
if ( self :: $banFile !== '' ) {
if ( ! is_file ( self :: $banFile )) {
file_put_contents ( self :: $banFile , " <?php \n \$ GLOBALS['IPBANS']= " . var_export ( array ( 'FAILURES' => array (), 'BANS' => array ()), true ) . " ; \n ?> " );
}
include self :: $banFile ;
}
}
/**
* Checks if the user CAN login . If 'true' , the user can try to login .
*
* @ return boolean true if user is banned , false otherwise
*/
public static function banCanLogin ()
{
if ( self :: $banFile !== '' ) {
$ip = $_SERVER [ " REMOTE_ADDR " ];
$gb = $GLOBALS [ 'IPBANS' ];
if ( isset ( $gb [ 'BANS' ][ $ip ])) {
// User is banned. Check if the ban has expired:
if ( $gb [ 'BANS' ][ $ip ] <= time ()) {
// Ban expired, user can try to login again.
unset ( $gb [ 'FAILURES' ][ $ip ]);
unset ( $gb [ 'BANS' ][ $ip ]);
file_put_contents ( self :: $banFile , " <?php \n \$ GLOBALS['IPBANS']= " . var_export ( $gb , true ) . " ; \n ?> " );
return true ; // Ban has expired, user can login.
}
return false ; // User is banned.
}
}
return true ; // User is not banned.
}
}