/**
  * 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);
 }