diff options
-rw-r--r-- | application/security/LoginManager.php | 5 | ||||
-rw-r--r-- | application/security/SessionManager.php | 48 | ||||
-rw-r--r-- | index.php | 47 | ||||
-rw-r--r-- | tests/security/SessionManagerTest.php | 181 |
4 files changed, 224 insertions, 57 deletions
diff --git a/application/security/LoginManager.php b/application/security/LoginManager.php index e7b9b21e..27247f3f 100644 --- a/application/security/LoginManager.php +++ b/application/security/LoginManager.php | |||
@@ -49,13 +49,12 @@ class LoginManager | |||
49 | * Check user session state and validity (expiration) | 49 | * Check user session state and validity (expiration) |
50 | * | 50 | * |
51 | * @param array $cookie The $_COOKIE array | 51 | * @param array $cookie The $_COOKIE array |
52 | * @param string $webPath Path on the server in which the cookie will be available on | ||
53 | * @param string $clientIpId Client IP address identifier | 52 | * @param string $clientIpId Client IP address identifier |
54 | * @param string $token Session token | 53 | * @param string $token Session token |
55 | * | 54 | * |
56 | * @return bool true if the user session is valid, false otherwise | 55 | * @return bool true if the user session is valid, false otherwise |
57 | */ | 56 | */ |
58 | public function checkLoginState($cookie, $webPath, $clientIpId, $token) | 57 | public function checkLoginState($cookie, $clientIpId, $token) |
59 | { | 58 | { |
60 | if (! $this->configManager->exists('credentials.login')) { | 59 | if (! $this->configManager->exists('credentials.login')) { |
61 | // Shaarli is not configured yet | 60 | // Shaarli is not configured yet |
@@ -73,7 +72,7 @@ class LoginManager | |||
73 | if ($this->sessionManager->hasSessionExpired() | 72 | if ($this->sessionManager->hasSessionExpired() |
74 | || $this->sessionManager->hasClientIpChanged($clientIpId) | 73 | || $this->sessionManager->hasClientIpChanged($clientIpId) |
75 | ) { | 74 | ) { |
76 | $this->sessionManager->logout($webPath); | 75 | $this->sessionManager->logout(); |
77 | $this->isLoggedIn = false; | 76 | $this->isLoggedIn = false; |
78 | return; | 77 | return; |
79 | } | 78 | } |
diff --git a/application/security/SessionManager.php b/application/security/SessionManager.php index 6f004b24..0dcd7f90 100644 --- a/application/security/SessionManager.php +++ b/application/security/SessionManager.php | |||
@@ -9,7 +9,10 @@ use Shaarli\Config\ConfigManager; | |||
9 | class SessionManager | 9 | class SessionManager |
10 | { | 10 | { |
11 | /** @var int Session expiration timeout, in seconds */ | 11 | /** @var int Session expiration timeout, in seconds */ |
12 | public static $INACTIVITY_TIMEOUT = 3600; | 12 | public static $SHORT_TIMEOUT = 3600; // 1 hour |
13 | |||
14 | /** @var int Session expiration timeout, in seconds */ | ||
15 | public static $LONG_TIMEOUT = 31536000; // 1 year | ||
13 | 16 | ||
14 | /** @var string Name of the cookie set after logging in **/ | 17 | /** @var string Name of the cookie set after logging in **/ |
15 | public static $LOGGED_IN_COOKIE = 'shaarli_staySignedIn'; | 18 | public static $LOGGED_IN_COOKIE = 'shaarli_staySignedIn'; |
@@ -20,6 +23,9 @@ class SessionManager | |||
20 | /** @var ConfigManager Configuration Manager instance **/ | 23 | /** @var ConfigManager Configuration Manager instance **/ |
21 | protected $conf = null; | 24 | protected $conf = null; |
22 | 25 | ||
26 | /** @var bool Whether the user should stay signed in (LONG_TIMEOUT) */ | ||
27 | protected $staySignedIn = false; | ||
28 | |||
23 | /** | 29 | /** |
24 | * Constructor | 30 | * Constructor |
25 | * | 31 | * |
@@ -33,6 +39,16 @@ class SessionManager | |||
33 | } | 39 | } |
34 | 40 | ||
35 | /** | 41 | /** |
42 | * Define whether the user should stay signed in across browser sessions | ||
43 | * | ||
44 | * @param bool $staySignedIn Keep the user signed in | ||
45 | */ | ||
46 | public function setStaySignedIn($staySignedIn) | ||
47 | { | ||
48 | $this->staySignedIn = $staySignedIn; | ||
49 | } | ||
50 | |||
51 | /** | ||
36 | * Generates a session token | 52 | * Generates a session token |
37 | * | 53 | * |
38 | * @return string token | 54 | * @return string token |
@@ -104,7 +120,7 @@ class SessionManager | |||
104 | $this->session['uid'] = sha1(uniqid('', true) . '_' . mt_rand()); | 120 | $this->session['uid'] = sha1(uniqid('', true) . '_' . mt_rand()); |
105 | $this->session['ip'] = $clientIpId; | 121 | $this->session['ip'] = $clientIpId; |
106 | $this->session['username'] = $this->conf->get('credentials.login'); | 122 | $this->session['username'] = $this->conf->get('credentials.login'); |
107 | $this->session['expires_on'] = time() + self::$INACTIVITY_TIMEOUT; | 123 | $this->extendTimeValidityBy(self::$SHORT_TIMEOUT); |
108 | } | 124 | } |
109 | 125 | ||
110 | /** | 126 | /** |
@@ -112,12 +128,24 @@ class SessionManager | |||
112 | */ | 128 | */ |
113 | public function extendSession() | 129 | public function extendSession() |
114 | { | 130 | { |
115 | if (! empty($this->session['longlastingsession'])) { | 131 | if ($this->staySignedIn) { |
116 | // "Stay signed in" is enabled | 132 | return $this->extendTimeValidityBy(self::$LONG_TIMEOUT); |
117 | $this->session['expires_on'] = time() + $this->session['longlastingsession']; | ||
118 | return; | ||
119 | } | 133 | } |
120 | $this->session['expires_on'] = time() + self::$INACTIVITY_TIMEOUT; | 134 | return $this->extendTimeValidityBy(self::$SHORT_TIMEOUT); |
135 | } | ||
136 | |||
137 | /** | ||
138 | * Extend expiration time | ||
139 | * | ||
140 | * @param int $duration Expiration time extension (seconds) | ||
141 | * | ||
142 | * @return int New session expiration time | ||
143 | */ | ||
144 | protected function extendTimeValidityBy($duration) | ||
145 | { | ||
146 | $expirationTime = time() + $duration; | ||
147 | $this->session['expires_on'] = $expirationTime; | ||
148 | return $expirationTime; | ||
121 | } | 149 | } |
122 | 150 | ||
123 | /** | 151 | /** |
@@ -125,19 +153,17 @@ class SessionManager | |||
125 | * | 153 | * |
126 | * See: | 154 | * See: |
127 | * - https://secure.php.net/manual/en/function.setcookie.php | 155 | * - https://secure.php.net/manual/en/function.setcookie.php |
128 | * | ||
129 | * @param string $webPath path on the server in which the cookie will be available on | ||
130 | */ | 156 | */ |
131 | public function logout($webPath) | 157 | public function logout() |
132 | { | 158 | { |
133 | if (isset($this->session)) { | 159 | if (isset($this->session)) { |
134 | unset($this->session['uid']); | 160 | unset($this->session['uid']); |
135 | unset($this->session['ip']); | 161 | unset($this->session['ip']); |
162 | unset($this->session['expires_on']); | ||
136 | unset($this->session['username']); | 163 | unset($this->session['username']); |
137 | unset($this->session['visibility']); | 164 | unset($this->session['visibility']); |
138 | unset($this->session['untaggedonly']); | 165 | unset($this->session['untaggedonly']); |
139 | } | 166 | } |
140 | setcookie(self::$LOGGED_IN_COOKIE, 'false', 0, $webPath); | ||
141 | } | 167 | } |
142 | 168 | ||
143 | /** | 169 | /** |
@@ -179,7 +179,7 @@ if (! is_file($conf->getConfigFileExt())) { | |||
179 | // a token depending of deployment salt, user password, and the current ip | 179 | // a token depending of deployment salt, user password, and the current ip |
180 | define('STAY_SIGNED_IN_TOKEN', sha1($conf->get('credentials.hash') . $_SERVER['REMOTE_ADDR'] . $conf->get('credentials.salt'))); | 180 | define('STAY_SIGNED_IN_TOKEN', sha1($conf->get('credentials.hash') . $_SERVER['REMOTE_ADDR'] . $conf->get('credentials.salt'))); |
181 | 181 | ||
182 | $loginManager->checkLoginState($_COOKIE, WEB_PATH, $clientIpId, STAY_SIGNED_IN_TOKEN); | 182 | $loginManager->checkLoginState($_COOKIE, $clientIpId, STAY_SIGNED_IN_TOKEN); |
183 | 183 | ||
184 | /** | 184 | /** |
185 | * Adapter function to ensure compatibility with third-party templates | 185 | * Adapter function to ensure compatibility with third-party templates |
@@ -205,31 +205,35 @@ if (isset($_POST['login'])) { | |||
205 | && $sessionManager->checkToken($_POST['token']) | 205 | && $sessionManager->checkToken($_POST['token']) |
206 | && $loginManager->checkCredentials($_SERVER['REMOTE_ADDR'], $clientIpId, $_POST['login'], $_POST['password']) | 206 | && $loginManager->checkCredentials($_SERVER['REMOTE_ADDR'], $clientIpId, $_POST['login'], $_POST['password']) |
207 | ) { | 207 | ) { |
208 | // Login/password is OK. | ||
209 | $loginManager->handleSuccessfulLogin($_SERVER); | 208 | $loginManager->handleSuccessfulLogin($_SERVER); |
210 | 209 | ||
211 | // If user wants to keep the session cookie even after the browser closes: | 210 | $cookiedir = ''; |
212 | if (!empty($_POST['longlastingsession'])) { | 211 | if (dirname($_SERVER['SCRIPT_NAME']) != '/') { |
213 | $_SESSION['longlastingsession'] = 31536000; // (31536000 seconds = 1 year) | ||
214 | $expiration = time() + $_SESSION['longlastingsession']; // calculate relative cookie expiration (1 year from now) | ||
215 | setcookie($sessionManager::$LOGGED_IN_COOKIE, STAY_SIGNED_IN_TOKEN, $expiration, WEB_PATH); | ||
216 | $_SESSION['expires_on'] = $expiration; // Set session expiration on server-side. | ||
217 | |||
218 | $cookiedir = ''; | ||
219 | if (dirname($_SERVER['SCRIPT_NAME']) != '/') { | ||
220 | $cookiedir = dirname($_SERVER["SCRIPT_NAME"]) . '/'; | ||
221 | } | ||
222 | session_set_cookie_params($_SESSION['longlastingsession'],$cookiedir,$_SERVER['SERVER_NAME']); // Set session cookie expiration on client side | ||
223 | // Note: Never forget the trailing slash on the cookie path! | 212 | // Note: Never forget the trailing slash on the cookie path! |
224 | session_regenerate_id(true); // Send cookie with new expiration date to browser. | 213 | $cookiedir = dirname($_SERVER["SCRIPT_NAME"]) . '/'; |
225 | } | 214 | } |
226 | else // Standard session expiration (=when browser closes) | 215 | |
227 | { | 216 | if (!empty($_POST['longlastingsession'])) { |
228 | $cookiedir = ''; if(dirname($_SERVER['SCRIPT_NAME'])!='/') $cookiedir=dirname($_SERVER["SCRIPT_NAME"]).'/'; | 217 | // Keep the session cookie even after the browser closes |
229 | session_set_cookie_params(0,$cookiedir,$_SERVER['SERVER_NAME']); // 0 means "When browser closes" | 218 | $sessionManager->setStaySignedIn(true); |
230 | session_regenerate_id(true); | 219 | $expirationTime = $sessionManager->extendSession(); |
220 | |||
221 | setcookie( | ||
222 | $sessionManager::$LOGGED_IN_COOKIE, | ||
223 | STAY_SIGNED_IN_TOKEN, | ||
224 | $expirationTime, | ||
225 | WEB_PATH | ||
226 | ); | ||
227 | |||
228 | } else { | ||
229 | // Standard session expiration (=when browser closes) | ||
230 | $expirationTime = 0; | ||
231 | } | 231 | } |
232 | 232 | ||
233 | // Send cookie with the new expiration date to the browser | ||
234 | session_set_cookie_params($expirationTime, $cookiedir, $_SERVER['SERVER_NAME']); | ||
235 | session_regenerate_id(true); | ||
236 | |||
233 | // Optional redirect after login: | 237 | // Optional redirect after login: |
234 | if (isset($_GET['post'])) { | 238 | if (isset($_GET['post'])) { |
235 | $uri = '?post='. urlencode($_GET['post']); | 239 | $uri = '?post='. urlencode($_GET['post']); |
@@ -590,7 +594,8 @@ function renderPage($conf, $pluginManager, $LINKSDB, $history, $sessionManager, | |||
590 | if (isset($_SERVER['QUERY_STRING']) && startsWith($_SERVER['QUERY_STRING'], 'do=logout')) | 594 | if (isset($_SERVER['QUERY_STRING']) && startsWith($_SERVER['QUERY_STRING'], 'do=logout')) |
591 | { | 595 | { |
592 | invalidateCaches($conf->get('resource.page_cache')); | 596 | invalidateCaches($conf->get('resource.page_cache')); |
593 | $sessionManager->logout(WEB_PATH); | 597 | $sessionManager->logout(); |
598 | setcookie(SessionManager::$LOGGED_IN_COOKIE, 'false', 0, WEB_PATH); | ||
594 | header('Location: ?'); | 599 | header('Location: ?'); |
595 | exit; | 600 | exit; |
596 | } | 601 | } |
diff --git a/tests/security/SessionManagerTest.php b/tests/security/SessionManagerTest.php index e4e1cfbc..e1c72707 100644 --- a/tests/security/SessionManagerTest.php +++ b/tests/security/SessionManagerTest.php | |||
@@ -14,11 +14,17 @@ use \PHPUnit\Framework\TestCase; | |||
14 | */ | 14 | */ |
15 | class SessionManagerTest extends TestCase | 15 | class SessionManagerTest extends TestCase |
16 | { | 16 | { |
17 | // Session ID hashes | 17 | /** @var array Session ID hashes */ |
18 | protected static $sidHashes = null; | 18 | protected static $sidHashes = null; |
19 | 19 | ||
20 | // Fake ConfigManager | 20 | /** @var FakeConfigManager ConfigManager substitute for testing */ |
21 | protected static $conf = null; | 21 | protected $conf = null; |
22 | |||
23 | /** @var array $_SESSION array for testing */ | ||
24 | protected $session = []; | ||
25 | |||
26 | /** @var SessionManager Server-side session management abstraction */ | ||
27 | protected $sessionManager = null; | ||
22 | 28 | ||
23 | /** | 29 | /** |
24 | * Assign reference data | 30 | * Assign reference data |
@@ -26,7 +32,20 @@ class SessionManagerTest extends TestCase | |||
26 | public static function setUpBeforeClass() | 32 | public static function setUpBeforeClass() |
27 | { | 33 | { |
28 | self::$sidHashes = ReferenceSessionIdHashes::getHashes(); | 34 | self::$sidHashes = ReferenceSessionIdHashes::getHashes(); |
29 | self::$conf = new FakeConfigManager(); | 35 | } |
36 | |||
37 | /** | ||
38 | * Initialize or reset test resources | ||
39 | */ | ||
40 | public function setUp() | ||
41 | { | ||
42 | $this->conf = new FakeConfigManager([ | ||
43 | 'credentials.login' => 'johndoe', | ||
44 | 'credentials.salt' => 'salt', | ||
45 | 'security.session_protection_disabled' => false, | ||
46 | ]); | ||
47 | $this->session = []; | ||
48 | $this->sessionManager = new SessionManager($this->session, $this->conf); | ||
30 | } | 49 | } |
31 | 50 | ||
32 | /** | 51 | /** |
@@ -34,12 +53,9 @@ class SessionManagerTest extends TestCase | |||
34 | */ | 53 | */ |
35 | public function testGenerateToken() | 54 | public function testGenerateToken() |
36 | { | 55 | { |
37 | $session = []; | 56 | $token = $this->sessionManager->generateToken(); |
38 | $sessionManager = new SessionManager($session, self::$conf); | ||
39 | |||
40 | $token = $sessionManager->generateToken(); | ||
41 | 57 | ||
42 | $this->assertEquals(1, $session['tokens'][$token]); | 58 | $this->assertEquals(1, $this->session['tokens'][$token]); |
43 | $this->assertEquals(40, strlen($token)); | 59 | $this->assertEquals(40, strlen($token)); |
44 | } | 60 | } |
45 | 61 | ||
@@ -54,7 +70,7 @@ class SessionManagerTest extends TestCase | |||
54 | $token => 1, | 70 | $token => 1, |
55 | ], | 71 | ], |
56 | ]; | 72 | ]; |
57 | $sessionManager = new SessionManager($session, self::$conf); | 73 | $sessionManager = new SessionManager($session, $this->conf); |
58 | 74 | ||
59 | // check and destroy the token | 75 | // check and destroy the token |
60 | $this->assertTrue($sessionManager->checkToken($token)); | 76 | $this->assertTrue($sessionManager->checkToken($token)); |
@@ -69,21 +85,18 @@ class SessionManagerTest extends TestCase | |||
69 | */ | 85 | */ |
70 | public function testGenerateAndCheckToken() | 86 | public function testGenerateAndCheckToken() |
71 | { | 87 | { |
72 | $session = []; | 88 | $token = $this->sessionManager->generateToken(); |
73 | $sessionManager = new SessionManager($session, self::$conf); | ||
74 | |||
75 | $token = $sessionManager->generateToken(); | ||
76 | 89 | ||
77 | // ensure a token has been generated | 90 | // ensure a token has been generated |
78 | $this->assertEquals(1, $session['tokens'][$token]); | 91 | $this->assertEquals(1, $this->session['tokens'][$token]); |
79 | $this->assertEquals(40, strlen($token)); | 92 | $this->assertEquals(40, strlen($token)); |
80 | 93 | ||
81 | // check and destroy the token | 94 | // check and destroy the token |
82 | $this->assertTrue($sessionManager->checkToken($token)); | 95 | $this->assertTrue($this->sessionManager->checkToken($token)); |
83 | $this->assertFalse(isset($session['tokens'][$token])); | 96 | $this->assertFalse(isset($this->session['tokens'][$token])); |
84 | 97 | ||
85 | // ensure the token has been destroyed | 98 | // ensure the token has been destroyed |
86 | $this->assertFalse($sessionManager->checkToken($token)); | 99 | $this->assertFalse($this->sessionManager->checkToken($token)); |
87 | } | 100 | } |
88 | 101 | ||
89 | /** | 102 | /** |
@@ -91,10 +104,7 @@ class SessionManagerTest extends TestCase | |||
91 | */ | 104 | */ |
92 | public function testCheckInvalidToken() | 105 | public function testCheckInvalidToken() |
93 | { | 106 | { |
94 | $session = []; | 107 | $this->assertFalse($this->sessionManager->checkToken('4dccc3a45ad9d03e5542b90c37d8db6d10f2b38b')); |
95 | $sessionManager = new SessionManager($session, self::$conf); | ||
96 | |||
97 | $this->assertFalse($sessionManager->checkToken('4dccc3a45ad9d03e5542b90c37d8db6d10f2b38b')); | ||
98 | } | 108 | } |
99 | 109 | ||
100 | /** | 110 | /** |
@@ -146,4 +156,131 @@ class SessionManagerTest extends TestCase | |||
146 | SessionManager::checkId('c0ZqcWF3VFE2NmJBdm1HMVQ0ZHJ3UmZPbTFsNGhkNHI=') | 156 | SessionManager::checkId('c0ZqcWF3VFE2NmJBdm1HMVQ0ZHJ3UmZPbTFsNGhkNHI=') |
147 | ); | 157 | ); |
148 | } | 158 | } |
159 | |||
160 | /** | ||
161 | * Store login information after a successful login | ||
162 | */ | ||
163 | public function testStoreLoginInfo() | ||
164 | { | ||
165 | $this->sessionManager->storeLoginInfo('ip_id'); | ||
166 | |||
167 | $this->assertTrue(isset($this->session['uid'])); | ||
168 | $this->assertGreaterThan(time(), $this->session['expires_on']); | ||
169 | $this->assertEquals('ip_id', $this->session['ip']); | ||
170 | $this->assertEquals('johndoe', $this->session['username']); | ||
171 | } | ||
172 | |||
173 | /** | ||
174 | * Extend a server-side session by SessionManager::$SHORT_TIMEOUT | ||
175 | */ | ||
176 | public function testExtendSession() | ||
177 | { | ||
178 | $this->sessionManager->extendSession(); | ||
179 | |||
180 | $this->assertGreaterThan(time(), $this->session['expires_on']); | ||
181 | $this->assertLessThanOrEqual( | ||
182 | time() + SessionManager::$SHORT_TIMEOUT, | ||
183 | $this->session['expires_on'] | ||
184 | ); | ||
185 | } | ||
186 | |||
187 | /** | ||
188 | * Extend a server-side session by SessionManager::$LONG_TIMEOUT | ||
189 | */ | ||
190 | public function testExtendSessionStaySignedIn() | ||
191 | { | ||
192 | $this->sessionManager->setStaySignedIn(true); | ||
193 | $this->sessionManager->extendSession(); | ||
194 | |||
195 | $this->assertGreaterThan(time(), $this->session['expires_on']); | ||
196 | $this->assertGreaterThan( | ||
197 | time() + SessionManager::$LONG_TIMEOUT - 10, | ||
198 | $this->session['expires_on'] | ||
199 | ); | ||
200 | $this->assertLessThanOrEqual( | ||
201 | time() + SessionManager::$LONG_TIMEOUT, | ||
202 | $this->session['expires_on'] | ||
203 | ); | ||
204 | } | ||
205 | |||
206 | /** | ||
207 | * Unset session variables after logging out | ||
208 | */ | ||
209 | public function testLogout() | ||
210 | { | ||
211 | $this->session = [ | ||
212 | 'uid' => 'some-uid', | ||
213 | 'ip' => 'ip_id', | ||
214 | 'expires_on' => time() + 1000, | ||
215 | 'username' => 'johndoe', | ||
216 | 'visibility' => 'public', | ||
217 | 'untaggedonly' => false, | ||
218 | ]; | ||
219 | $this->sessionManager->logout(); | ||
220 | |||
221 | $this->assertFalse(isset($this->session['uid'])); | ||
222 | $this->assertFalse(isset($this->session['ip'])); | ||
223 | $this->assertFalse(isset($this->session['expires_on'])); | ||
224 | $this->assertFalse(isset($this->session['username'])); | ||
225 | $this->assertFalse(isset($this->session['visibility'])); | ||
226 | $this->assertFalse(isset($this->session['untaggedonly'])); | ||
227 | } | ||
228 | |||
229 | /** | ||
230 | * The session is considered as expired because the UID is missing | ||
231 | */ | ||
232 | public function testHasExpiredNoUid() | ||
233 | { | ||
234 | $this->assertTrue($this->sessionManager->hasSessionExpired()); | ||
235 | } | ||
236 | |||
237 | /** | ||
238 | * The session is active and expiration time has been reached | ||
239 | */ | ||
240 | public function testHasExpiredTimeElapsed() | ||
241 | { | ||
242 | $this->session['uid'] = 'some-uid'; | ||
243 | $this->session['expires_on'] = time() - 10; | ||
244 | |||
245 | $this->assertTrue($this->sessionManager->hasSessionExpired()); | ||
246 | } | ||
247 | |||
248 | /** | ||
249 | * The session is active and expiration time has not been reached | ||
250 | */ | ||
251 | public function testHasNotExpired() | ||
252 | { | ||
253 | $this->session['uid'] = 'some-uid'; | ||
254 | $this->session['expires_on'] = time() + 1000; | ||
255 | |||
256 | $this->assertFalse($this->sessionManager->hasSessionExpired()); | ||
257 | } | ||
258 | |||
259 | /** | ||
260 | * Session hijacking protection is disabled, we assume the IP has not changed | ||
261 | */ | ||
262 | public function testHasClientIpChangedNoSessionProtection() | ||
263 | { | ||
264 | $this->conf->set('security.session_protection_disabled', true); | ||
265 | |||
266 | $this->assertFalse($this->sessionManager->hasClientIpChanged('')); | ||
267 | } | ||
268 | |||
269 | /** | ||
270 | * The client IP identifier has not changed | ||
271 | */ | ||
272 | public function testHasClientIpChangedNope() | ||
273 | { | ||
274 | $this->session['ip'] = 'ip_id'; | ||
275 | $this->assertFalse($this->sessionManager->hasClientIpChanged('ip_id')); | ||
276 | } | ||
277 | |||
278 | /** | ||
279 | * The client IP identifier has changed | ||
280 | */ | ||
281 | public function testHasClientIpChanged() | ||
282 | { | ||
283 | $this->session['ip'] = 'ip_id_one'; | ||
284 | $this->assertTrue($this->sessionManager->hasClientIpChanged('ip_id_two')); | ||
285 | } | ||
149 | } | 286 | } |