/** * Log the user in if the "remember login" cookie is set * * The <i>remember login token</i> will be changed on every successful * auto-login. */ public static function autoLogin() { // Don't bother trying this multiple times if (!class_exists('SapphireTest', false) || !SapphireTest::is_running_test()) { self::$_already_tried_to_auto_log_in = true; } if (strpos(Cookie::get('alc_enc'), ':') === false || Session::get("loggedInAs") || !Security::database_is_ready()) { return; } if (strpos(Cookie::get('alc_enc'), ':') && Cookie::get('alc_device') && !Session::get("loggedInAs")) { list($uid, $token) = explode(':', Cookie::get('alc_enc'), 2); if (!$uid || !$token) { return; } $deviceID = Cookie::get('alc_device'); $member = Member::get()->byID($uid); $rememberLoginHash = null; // check if autologin token matches if ($member) { $hash = $member->encryptWithUserSettings($token); $rememberLoginHash = RememberLoginHash::get()->filter(array('MemberID' => $member->ID, 'DeviceID' => $deviceID, 'Hash' => $hash))->First(); if (!$rememberLoginHash) { $member = null; } else { // Check for expired token $expiryDate = new DateTime($rememberLoginHash->ExpiryDate); $now = DBDatetime::now(); $now = new DateTime($now->Rfc2822()); if ($now > $expiryDate) { $member = null; } } } if ($member) { self::session_regenerate_id(); Session::set("loggedInAs", $member->ID); // This lets apache rules detect whether the user has logged in if (Member::config()->login_marker_cookie) { Cookie::set(Member::config()->login_marker_cookie, 1, 0, null, null, false, true); } if ($rememberLoginHash) { $rememberLoginHash->renew(); $tokenExpiryDays = Config::inst()->get('SilverStripe\\Security\\RememberLoginHash', 'token_expiry_days'); Cookie::set('alc_enc', $member->ID . ':' . $rememberLoginHash->getToken(), $tokenExpiryDays, null, null, false, true); } $member->write(); // Audit logging hook $member->extend('memberAutoLoggedIn'); } } }
/** * Deletes existing tokens for this member * if logout_across_devices is true, all tokens are deleted, otherwise * only the token for the provided device ID will be removed * * @param Member $member * @param string $alcDevice */ public static function clear(Member $member, $alcDevice = null) { if (!$member->exists()) { return; } $filter = array('MemberID' => $member->ID); if (!static::config()->logout_across_devices && $alcDevice) { $filter['DeviceID'] = $alcDevice; } RememberLoginHash::get()->filter($filter)->removeAll(); }
public function testRememberMeMultipleDevices() { $m1 = $this->objFromFixture('SilverStripe\\Security\\Member', 'noexpiry'); // First device $m1->login(true); Cookie::set('alc_device', null); // Second device $m1->login(true); // Hash of first device $firstHash = RememberLoginHash::get()->filter('MemberID', $m1->ID)->First(); $this->assertNotNull($firstHash); // Hash of second device $secondHash = RememberLoginHash::get()->filter('MemberID', $m1->ID)->Last(); $this->assertNotNull($secondHash); // DeviceIDs are different $this->assertNotEquals($firstHash->DeviceID, $secondHash->DeviceID); // re-generates the hashes so we can get the tokens $firstHash->Hash = $firstHash->getNewHash($m1); $firstToken = $firstHash->getToken(); $firstHash->write(); $secondHash->Hash = $secondHash->getNewHash($m1); $secondToken = $secondHash->getToken(); $secondHash->write(); // Accessing the login page should show the user's name straight away $response = $this->get('Security/login', $this->session(), null, array('alc_enc' => $m1->ID . ':' . $firstToken, 'alc_device' => $firstHash->DeviceID)); $message = _t('Member.LOGGEDINAS', "You're logged in as {name}.", array('name' => $m1->FirstName)); $this->assertContains($message, $response->getBody()); $this->session()->inst_set('loggedInAs', null); // Accessing the login page from the second device $response = $this->get('Security/login', $this->session(), null, array('alc_enc' => $m1->ID . ':' . $secondToken, 'alc_device' => $secondHash->DeviceID)); $this->assertContains($message, $response->getBody()); $logout_across_devices = Config::inst()->get('SilverStripe\\Security\\RememberLoginHash', 'logout_across_devices'); // Logging out from the second device - only one device being logged out Config::inst()->update('SilverStripe\\Security\\RememberLoginHash', 'logout_across_devices', false); $response = $this->get('Security/logout', $this->session(), null, array('alc_enc' => $m1->ID . ':' . $secondToken, 'alc_device' => $secondHash->DeviceID)); $this->assertEquals(RememberLoginHash::get()->filter(array('MemberID' => $m1->ID, 'DeviceID' => $firstHash->DeviceID))->Count(), 1); // Logging out from any device when all login hashes should be removed Config::inst()->update('SilverStripe\\Security\\RememberLoginHash', 'logout_across_devices', true); $m1->login(true); $response = $this->get('Security/logout', $this->session()); $this->assertEquals(RememberLoginHash::get()->filter('MemberID', $m1->ID)->Count(), 0); Config::inst()->update('SilverStripe\\Security\\RememberLoginHash', 'logout_across_devices', $logout_across_devices); }