/**
  * 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);
 }
 /**
  * Logs this member out.
  */
 public function logOut()
 {
     $this->extend('beforeMemberLoggedOut');
     Session::clear("loggedInAs");
     if (Member::config()->login_marker_cookie) {
         Cookie::set(Member::config()->login_marker_cookie, null, 0);
     }
     Session::destroy();
     $this->extend('memberLoggedOut');
     // Clears any potential previous hashes for this member
     RememberLoginHash::clear($this, Cookie::get('alc_device'));
     Cookie::set('alc_enc', null);
     // // Clear the Remember Me cookie
     Cookie::force_expiry('alc_enc');
     Cookie::set('alc_device', null);
     Cookie::force_expiry('alc_device');
     // Switch back to live in order to avoid infinite loops when
     // redirecting to the login screen (if this login screen is versioned)
     Session::clear('readingMode');
     $this->write();
     // Audit logging hook
     $this->extend('memberLoggedOut');
 }
    /**
     * Constructor
     *
     * @skipUpgrade
     * @param Controller $controller The parent controller, necessary to
     *                               create the appropriate form action tag.
     * @param string $name The method on the controller that will return this
     *                     form object.
     * @param FieldList $fields All of the fields in the form - a
     *                                   {@link FieldList} of {@link FormField}
     *                                   objects.
     * @param FieldList|FormAction $actions All of the action buttons in the
     *                                     form - a {@link FieldList} of
     *                                     {@link FormAction} objects
     * @param bool $checkCurrentUser If set to TRUE, it will be checked if a
     *                               the user is currently logged in, and if
     *                               so, only a logout button will be rendered
     */
    public function __construct($controller, $name, $fields = null, $actions = null, $checkCurrentUser = true)
    {
        // This is now set on the class directly to make it easier to create subclasses
        // $this->authenticator_class = $authenticatorClassName;
        $customCSS = project() . '/css/member_login.css';
        if (Director::fileExists($customCSS)) {
            Requirements::css($customCSS);
        }
        if (isset($_REQUEST['BackURL'])) {
            $backURL = $_REQUEST['BackURL'];
        } else {
            $backURL = Session::get('BackURL');
        }
        if ($checkCurrentUser && Member::currentUser() && Member::logged_in_session_exists()) {
            $fields = FieldList::create(HiddenField::create("AuthenticationMethod", null, $this->authenticator_class, $this));
            $actions = FieldList::create(FormAction::create("logout", _t('Member.BUTTONLOGINOTHER', "Log in as someone else")));
        } else {
            if (!$fields) {
                $label = Member::singleton()->fieldLabel(Member::config()->unique_identifier_field);
                $fields = FieldList::create(HiddenField::create("AuthenticationMethod", null, $this->authenticator_class, $this), $emailField = TextField::create("Email", $label, null, null, $this), PasswordField::create("Password", _t('Member.PASSWORD', 'Password')));
                if (Security::config()->remember_username) {
                    $emailField->setValue(Session::get('SessionForms.MemberLoginForm.Email'));
                } else {
                    // Some browsers won't respect this attribute unless it's added to the form
                    $this->setAttribute('autocomplete', 'off');
                    $emailField->setAttribute('autocomplete', 'off');
                }
                if (Security::config()->autologin_enabled) {
                    $fields->push(CheckboxField::create("Remember", _t('Member.KEEPMESIGNEDIN', "Keep me signed in"))->setAttribute('title', sprintf(_t('Member.REMEMBERME', "Remember me next time? (for %d days on this device)"), RememberLoginHash::config()->get('token_expiry_days'))));
                }
            }
            if (!$actions) {
                $actions = FieldList::create(FormAction::create('dologin', _t('Member.BUTTONLOGIN', "Log in")), LiteralField::create('forgotPassword', '<p id="ForgotPassword"><a href="' . Security::lost_password_url() . '">' . _t('Member.BUTTONLOSTPASSWORD', "I've lost my password") . '</a></p>'));
            }
        }
        if (isset($backURL)) {
            $fields->push(HiddenField::create('BackURL', 'BackURL', $backURL));
        }
        // Reduce attack surface by enforcing POST requests
        $this->setFormMethod('POST', true);
        parent::__construct($controller, $name, $fields, $actions);
        $this->setValidator(RequiredFields::create('Email', 'Password'));
        // Focus on the email input when the page is loaded
        $js = <<<JS
\t\t\t(function() {
\t\t\t\tvar el = document.getElementById("MemberLoginForm_LoginForm_Email");
\t\t\t\tif(el && el.focus && (typeof jQuery == 'undefined' || jQuery(el).is(':visible'))) el.focus();
\t\t\t})();
JS;
        Requirements::customScript($js, 'MemberLoginFormFieldFocus');
    }