]> git.immae.eu Git - github/wallabag/wallabag.git/blob - inc/3rdparty/Session.class.php
Merge pull request #567 from mariroz/fix-session-livetime
[github/wallabag/wallabag.git] / inc / 3rdparty / Session.class.php
1 <?php
2 /**
3 * Session management class
4 *
5 * http://www.developpez.net/forums/d51943/php/langage/sessions/
6 * http://sebsauvage.net/wiki/doku.php?id=php:session
7 * http://sebsauvage.net/wiki/doku.php?id=php:shaarli
8 *
9 * Features:
10 * - Everything is stored on server-side (we do not trust client-side data,
11 * such as cookie expiration)
12 * - IP addresses are checked on each access to prevent session cookie hijacking
13 * (such as Firesheep)
14 * - Session expires on user inactivity (Session expiration date is
15 * automatically updated everytime the user accesses a page.)
16 * - A unique secret key is generated on server-side for this session
17 * (and never sent over the wire) which can be used to sign forms (HMAC)
18 * (See $_SESSION['uid'])
19 * - Token management to prevent XSRF attacks
20 * - Brute force protection with ban management
21 *
22 * TODOs
23 * - Replace globals with variables in Session class
24 *
25 * How to use:
26 * - http://tontof.net/kriss/php5/session
27 */
28 class Session
29 {
30 // Personnalize PHP session name
31 public static $sessionName = '';
32 // If the user does not access any page within this time,
33 // his/her session is considered expired (3600 sec. = 1 hour)
34 public static $inactivityTimeout = 3600;
35 // Extra timeout for long sessions (if enabled) (82800 sec. = 23 hours)
36 public static $longSessionTimeout = 7776000; // 7776000 = 90 days
37 // If you get disconnected often or if your IP address changes often.
38 // Let you disable session cookie hijacking protection
39 public static $disableSessionProtection = false;
40 // Ban IP after this many failures.
41 public static $banAfter = 4;
42 // Ban duration for IP address after login failures (in seconds).
43 // (1800 sec. = 30 minutes)
44 public static $banDuration = 1800;
45 // File storage for failures and bans. If empty, no ban management.
46 public static $banFile = '';
47
48 /**
49 * Initialize session
50 */
51 public static function init($longlastingsession = false)
52 {
53 //check if session name is correct
54 if ( session_id() && session_id()!=self::$sessionName ) {
55 session_destroy();
56 }
57
58 // Force cookie path (but do not change lifetime)
59 $cookie = session_get_cookie_params();
60 // Default cookie expiration and path.
61 $cookiedir = '';
62 if (dirname($_SERVER['SCRIPT_NAME'])!='/') {
63 $cookiedir = dirname($_SERVER["SCRIPT_NAME"]).'/';
64 }
65 $ssl = false;
66 if (isset($_SERVER["HTTPS"]) && $_SERVER["HTTPS"] == "on") {
67 $ssl = true;
68 }
69
70 if ( $longlastingsession ) {
71 session_set_cookie_params(self::$longSessionTimeout, $cookiedir, $_SERVER['HTTP_HOST'], $ssl, true);
72 }
73 else {
74 session_set_cookie_params('', $cookiedir, $_SERVER['HTTP_HOST'], $ssl, true);
75 }
76
77 // Use cookies to store session.
78 ini_set('session.use_cookies', 1);
79 // Force cookies for session (phpsessionID forbidden in URL)
80 ini_set('session.use_only_cookies', 1);
81 if ( !session_id() ) {
82 // Prevent php to use sessionID in URL if cookies are disabled.
83 ini_set('session.use_trans_sid', false);
84 if (!empty(self::$sessionName)) {
85 session_name(self::$sessionName);
86 }
87 session_start();
88 }
89 }
90
91 /**
92 * Returns the IP address
93 * (Used to prevent session cookie hijacking.)
94 *
95 * @return string IP addresses
96 */
97 private static function _allIPs()
98 {
99 $ip = $_SERVER["REMOTE_ADDR"];
100 $ip.= isset($_SERVER['HTTP_X_FORWARDED_FOR']) ? '_'.$_SERVER['HTTP_X_FORWARDED_FOR'] : '';
101 $ip.= isset($_SERVER['HTTP_CLIENT_IP']) ? '_'.$_SERVER['HTTP_CLIENT_IP'] : '';
102
103 return $ip;
104 }
105
106 /**
107 * Check that user/password is correct and then init some SESSION variables.
108 *
109 * @param string $login Login reference
110 * @param string $password Password reference
111 * @param string $loginTest Login to compare with login reference
112 * @param string $passwordTest Password to compare with password reference
113 * @param array $pValues Array of variables to store in SESSION
114 *
115 * @return true|false True if login and password are correct, false
116 * otherwise
117 */
118 public static function login (
119 $login,
120 $password,
121 $loginTest,
122 $passwordTest,
123 $longlastingsession,
124 $pValues = array())
125 {
126 self::banInit();
127 if (self::banCanLogin()) {
128 if ($login === $loginTest && $password === $passwordTest) {
129 self::banLoginOk();
130
131 self::init($longlastingsession);
132
133 // Generate unique random number to sign forms (HMAC)
134 $_SESSION['uid'] = sha1(uniqid('', true).'_'.mt_rand());
135 $_SESSION['ip'] = self::_allIPs();
136 $_SESSION['username'] = $login;
137 // Set session expiration.
138 $_SESSION['expires_on'] = time() + self::$inactivityTimeout;
139 if ($longlastingsession) {
140 $_SESSION['longlastingsession'] = self::$longSessionTimeout;
141 $_SESSION['expires_on'] += $_SESSION['longlastingsession'];
142 }
143
144 foreach ($pValues as $key => $value) {
145 $_SESSION[$key] = $value;
146 }
147
148 return true;
149 }
150 self::banLoginFailed();
151 }
152
153 self::init();
154 return false;
155 }
156
157 /**
158 * Unset SESSION variable to force logout
159 */
160 public static function logout()
161 {
162 // unset($_SESSION['uid'],$_SESSION['ip'],$_SESSION['expires_on'],$_SESSION['tokens'], $_SESSION['login'], $_SESSION['pass'], $_SESSION['longlastingsession'], $_SESSION['poche_user']);
163
164 // Destruction du cookie (le code peut paraître complexe mais c'est pour être certain de reprendre les mêmes paramètres)
165 $args = array_merge(array(session_name(), ''), array_values(session_get_cookie_params()));
166 $args[2] = time() - 3600;
167 call_user_func_array('setcookie', $args);
168 // Suppression physique de la session
169 session_destroy();
170 }
171
172 /**
173 * Make sure user is logged in.
174 *
175 * @return true|false True if user is logged in, false otherwise
176 */
177 public static function isLogged()
178 {
179 if (!isset ($_SESSION['uid'])
180 || (self::$disableSessionProtection === false
181 && $_SESSION['ip'] !== self::_allIPs())
182 || time() >= $_SESSION['expires_on']) {
183 self::logout();
184
185 return false;
186 }
187 // User accessed a page : Update his/her session expiration date.
188 $_SESSION['expires_on'] = time() + self::$inactivityTimeout;
189 if (!empty($_SESSION['longlastingsession'])) {
190 $_SESSION['expires_on'] += $_SESSION['longlastingsession'];
191 }
192
193 return true;
194 }
195
196 /**
197 * Create a token, store it in SESSION and return it
198 *
199 * @param string $salt to prevent birthday attack
200 *
201 * @return string Token created
202 */
203 public static function getToken($salt = '')
204 {
205 if (!isset($_SESSION['tokens'])) {
206 $_SESSION['tokens']=array();
207 }
208 // We generate a random string and store it on the server side.
209 $rnd = sha1(uniqid('', true).'_'.mt_rand().$salt);
210 $_SESSION['tokens'][$rnd]=1;
211
212 return $rnd;
213 }
214
215 /**
216 * Tells if a token is ok. Using this function will destroy the token.
217 *
218 * @param string $token Token to test
219 *
220 * @return true|false True if token is correct, false otherwise
221 */
222 public static function isToken($token)
223 {
224 if (isset($_SESSION['tokens'][$token])) {
225 unset($_SESSION['tokens'][$token]); // Token is used: destroy it.
226
227 return true; // Token is ok.
228 }
229
230 return false; // Wrong token, or already used.
231 }
232
233 /**
234 * Signal a failed login. Will ban the IP if too many failures:
235 */
236 public static function banLoginFailed()
237 {
238 if (self::$banFile !== '') {
239 $ip = $_SERVER["REMOTE_ADDR"];
240 $gb = $GLOBALS['IPBANS'];
241
242 if (!isset($gb['FAILURES'][$ip])) {
243 $gb['FAILURES'][$ip] = 0;
244 }
245 $gb['FAILURES'][$ip]++;
246 if ($gb['FAILURES'][$ip] > (self::$banAfter - 1)) {
247 $gb['BANS'][$ip]= time() + self::$banDuration;
248 }
249
250 $GLOBALS['IPBANS'] = $gb;
251 file_put_contents(self::$banFile, "<?php\n\$GLOBALS['IPBANS']=".var_export($gb, true).";\n?>");
252 }
253 }
254
255 /**
256 * Signals a successful login. Resets failed login counter.
257 */
258 public static function banLoginOk()
259 {
260 if (self::$banFile !== '') {
261 $ip = $_SERVER["REMOTE_ADDR"];
262 $gb = $GLOBALS['IPBANS'];
263 unset($gb['FAILURES'][$ip]); unset($gb['BANS'][$ip]);
264 $GLOBALS['IPBANS'] = $gb;
265 file_put_contents(self::$banFile, "<?php\n\$GLOBALS['IPBANS']=".var_export($gb, true).";\n?>");
266 }
267 }
268
269 /**
270 * Ban init
271 */
272 public static function banInit()
273 {
274 if (self::$banFile !== '') {
275 if (!is_file(self::$banFile)) {
276 file_put_contents(self::$banFile, "<?php\n\$GLOBALS['IPBANS']=".var_export(array('FAILURES'=>array(), 'BANS'=>array()), true).";\n?>");
277 }
278 include self::$banFile;
279 }
280 }
281
282 /**
283 * Checks if the user CAN login. If 'true', the user can try to login.
284 *
285 * @return boolean true if user is banned, false otherwise
286 */
287 public static function banCanLogin()
288 {
289 if (self::$banFile !== '') {
290 $ip = $_SERVER["REMOTE_ADDR"];
291 $gb = $GLOBALS['IPBANS'];
292 if (isset($gb['BANS'][$ip])) {
293 // User is banned. Check if the ban has expired:
294 if ($gb['BANS'][$ip] <= time()) {
295 // Ban expired, user can try to login again.
296 unset($gb['FAILURES'][$ip]);
297 unset($gb['BANS'][$ip]);
298 file_put_contents(self::$banFile, "<?php\n\$GLOBALS['IPBANS']=".var_export($gb, true).";\n?>");
299
300 return true; // Ban has expired, user can login.
301 }
302
303 return false; // User is banned.
304 }
305 }
306
307 return true; // User is not banned.
308 }
309 }