/**
  * Check if the submitted member data is valid (server-side)
  *
  * Check if a member with that email doesn't already exist, or if it does
  * that it is this member.
  *
  * @param array $data Submitted data
  * @return bool Returns TRUE if the submitted data is valid, otherwise
  *              FALSE.
  */
 public function php($data)
 {
     $valid = parent::php($data);
     $identifierField = (string) Member::config()->unique_identifier_field;
     // Only validate identifier field if it's actually set. This could be the case if
     // somebody removes `Email` from the list of required fields.
     if (isset($data[$identifierField])) {
         $id = isset($data['ID']) ? (int) $data['ID'] : 0;
         if (!$id && ($ctrl = $this->form->getController())) {
             // get the record when within GridField (Member editing page in CMS)
             if ($ctrl instanceof GridFieldDetailForm_ItemRequest && ($record = $ctrl->getRecord())) {
                 $id = $record->ID;
             }
         }
         // If there's no ID passed via controller or form-data, use the assigned member (if available)
         if (!$id && ($member = $this->getForMember())) {
             $id = $member->exists() ? $member->ID : 0;
         }
         // set the found ID to the data array, so that extensions can also use it
         $data['ID'] = $id;
         $members = Member::get()->filter($identifierField, $data[$identifierField]);
         if ($id) {
             $members = $members->exclude('ID', $id);
         }
         if ($members->count() > 0) {
             $this->validationError($identifierField, _t('Member.VALIDATIONMEMBEREXISTS', 'A member already exists with the same {identifier}', array('identifier' => Member::singleton()->fieldLabel($identifierField))), 'required');
             $valid = false;
         }
     }
     // Execute the validators on the extensions
     $results = $this->extend('updatePHP', $data, $this->form);
     $results[] = $valid;
     return min($results);
 }
 public function testCustomIdentifierField()
 {
     $origField = Member::config()->unique_identifier_field;
     Member::config()->unique_identifier_field = 'Username';
     $label = singleton('SilverStripe\\Security\\Member')->fieldLabel(Member::config()->unique_identifier_field);
     $this->assertEquals($label, 'Username');
     Member::config()->unique_identifier_field = $origField;
 }
 /**
  * Attempt to find and authenticate member if possible from the given data
  *
  * @param array $data
  * @param Form $form
  * @param bool &$success Success flag
  * @return Member Found member, regardless of successful login
  */
 protected static function authenticate_member($data, $form, &$success)
 {
     // Default success to false
     $success = false;
     // Attempt to identify by temporary ID
     $member = null;
     $email = null;
     if (!empty($data['tempid'])) {
         // Find user by tempid, in case they are re-validating an existing session
         $member = Member::member_from_tempid($data['tempid']);
         if ($member) {
             $email = $member->Email;
         }
     }
     // Otherwise, get email from posted value instead
     /** @skipUpgrade */
     if (!$member && !empty($data['Email'])) {
         $email = $data['Email'];
     }
     // Check default login (see Security::setDefaultAdmin())
     $asDefaultAdmin = $email === Security::default_admin_username();
     if ($asDefaultAdmin) {
         // If logging is as default admin, ensure record is setup correctly
         $member = Member::default_admin();
         $success = !$member->isLockedOut() && Security::check_default_admin($email, $data['Password']);
         //protect against failed login
         if ($success) {
             return $member;
         }
     }
     // Attempt to identify user by email
     if (!$member && $email) {
         // Find user by email
         $member = Member::get()->filter(Member::config()->unique_identifier_field, $email)->first();
     }
     // Validate against member if possible
     if ($member && !$asDefaultAdmin) {
         $result = $member->checkPassword($data['Password']);
         $success = $result->valid();
     } else {
         $result = new ValidationResult(false, _t('Member.ERRORWRONGCRED'));
     }
     // Emit failure to member and form (if available)
     if (!$success) {
         if ($member) {
             $member->registerFailedLogin();
         }
         if ($form) {
             $form->sessionMessage($result->message(), 'bad');
         }
     } else {
         if ($member) {
             $member->registerSuccessfulLogin();
         }
     }
     return $member;
 }
 public function setUp()
 {
     parent::setUp();
     // Fixtures assume Email is the field used to identify the log in identity
     Member::config()->unique_identifier_field = 'Email';
     Security::$force_database_is_ready = true;
     // Prevents Member test subclasses breaking ready test
     Member::config()->lock_out_after_incorrect_logins = 10;
 }
 public function testExtendedPermissionsStopEditingOwnProfile()
 {
     $existingExtensions = Member::config()->get('extensions');
     Member::config()->update('extensions', array('CMSProfileControllerTestExtension'));
     $member = $this->objFromFixture('SilverStripe\\Security\\Member', 'user1');
     $this->session()->inst_set('loggedInAs', $member->ID);
     $response = $this->post('admin/myprofile/EditForm', array('action_save' => 1, 'ID' => $member->ID, 'FirstName' => 'JoeEdited', 'Surname' => 'BloggsEdited', 'Email' => $member->Email, 'Locale' => $member->Locale, 'Password[_Password]' => 'password', 'Password[_ConfirmPassword]' => 'password'));
     $member = $this->objFromFixture('SilverStripe\\Security\\Member', 'user1');
     $this->assertNotEquals($member->FirstName, 'JoeEdited', 'FirstName field was NOT changed because we modified canEdit');
     Member::config()->remove('extensions')->update('extensions', $existingExtensions);
 }
    /**
     * Constructor
     *
     * @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 = singleton('SilverStripe\\Security\\Member')->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)"), Config::inst()->get('SilverStripe\\Security\\RememberLoginHash', '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');
    }
 public function testAlternatingRepeatedLoginAttempts()
 {
     Member::config()->lock_out_after_incorrect_logins = 3;
     // ATTEMPTING LOG-IN TWICE WITH ONE ACCOUNT AND TWICE WITH ANOTHER SHOULDN'T LOCK ANYBODY OUT
     $this->doTestLoginForm('*****@*****.**', 'incorrectpassword');
     $this->doTestLoginForm('*****@*****.**', 'incorrectpassword');
     $this->doTestLoginForm('*****@*****.**', 'incorrectpassword');
     $this->doTestLoginForm('*****@*****.**', 'incorrectpassword');
     $member1 = DataObject::get_by_id("SilverStripe\\Security\\Member", $this->idFromFixture('SilverStripe\\Security\\Member', 'test'));
     $member2 = DataObject::get_by_id("SilverStripe\\Security\\Member", $this->idFromFixture('SilverStripe\\Security\\Member', 'noexpiry'));
     $this->assertNull($member1->LockedOutUntil);
     $this->assertNull($member2->LockedOutUntil);
     // BUT, DOING AN ADDITIONAL LOG-IN WITH EITHER OF THEM WILL LOCK OUT, SINCE THAT IS THE 3RD FAILURE IN
     // THIS SESSION
     $this->doTestLoginForm('*****@*****.**', 'incorrectpassword');
     $member1 = DataObject::get_by_id("SilverStripe\\Security\\Member", $this->idFromFixture('SilverStripe\\Security\\Member', 'test'));
     $this->assertNotNull($member1->LockedOutUntil);
     $this->doTestLoginForm('*****@*****.**', 'incorrectpassword');
     $member2 = DataObject::get_by_id("SilverStripe\\Security\\Member", $this->idFromFixture('SilverStripe\\Security\\Member', 'noexpiry'));
     $this->assertNotNull($member2->LockedOutUntil);
 }
 /**
  * Test that the PasswordExpiry date is set when passwords are changed
  */
 public function testPasswordExpirySetting()
 {
     Member::config()->password_expiry_days = 90;
     $member = $this->objFromFixture('SilverStripe\\Security\\Member', 'test');
     $this->assertNotNull($member);
     $valid = $member->changePassword("Xx?1234234");
     $this->assertTrue($valid->valid());
     $expiryDate = date('Y-m-d', time() + 90 * 86400);
     $this->assertEquals($expiryDate, $member->PasswordExpiry);
     Member::config()->password_expiry_days = null;
     $valid = $member->changePassword("Xx?1234235");
     $this->assertTrue($valid->valid());
     $this->assertNull($member->PasswordExpiry);
 }
Exemplo n.º 9
0
 /**
  * Event handler called before writing to the database.
  */
 public function onBeforeWrite()
 {
     if ($this->SetPassword) {
         $this->Password = $this->SetPassword;
     }
     // If a member with the same "unique identifier" already exists with a different ID, don't allow merging.
     // Note: This does not a full replacement for safeguards in the controller layer (e.g. in a registration form),
     // but rather a last line of defense against data inconsistencies.
     $identifierField = Member::config()->unique_identifier_field;
     if ($this->{$identifierField}) {
         // Note: Same logic as Member_Validator class
         $filter = array("\"{$identifierField}\"" => $this->{$identifierField});
         if ($this->ID) {
             $filter[] = array('"Member"."ID" <> ?' => $this->ID);
         }
         $existingRecord = DataObject::get_one('SilverStripe\\Security\\Member', $filter);
         if ($existingRecord) {
             throw new ValidationException(ValidationResult::create(false, _t('Member.ValidationIdentifierFailed', 'Can\'t overwrite existing member #{id} with identical identifier ({name} = {value}))', 'Values in brackets show "fieldname = value", usually denoting an existing email address', array('id' => $existingRecord->ID, 'name' => $identifierField, 'value' => $this->{$identifierField}))));
         }
     }
     // We don't send emails out on dev/tests sites to prevent accidentally spamming users.
     // However, if TestMailer is in use this isn't a risk.
     if ((Director::isLive() || Email::mailer() instanceof TestMailer) && $this->isChanged('Password') && $this->record['Password'] && $this->config()->notify_password_change) {
         /** @var Email $e */
         $e = Email::create();
         $e->setSubject(_t('Member.SUBJECTPASSWORDCHANGED', "Your password has been changed", 'Email subject'));
         $e->setTemplate('ChangePasswordEmail');
         $e->populateTemplate($this);
         $e->setTo($this->Email);
         $e->send();
     }
     // The test on $this->ID is used for when records are initially created.
     // Note that this only works with cleartext passwords, as we can't rehash
     // existing passwords.
     if (!$this->ID && $this->Password || $this->isChanged('Password')) {
         //reset salt so that it gets regenerated - this will invalidate any persistant login cookies
         // or other information encrypted with this Member's settings (see self::encryptWithUserSettings)
         $this->Salt = '';
         // Password was changed: encrypt the password according the settings
         $encryption_details = Security::encrypt_password($this->Password, $this->Salt, $this->PasswordEncryption ? $this->PasswordEncryption : Security::config()->password_encryption_algorithm, $this);
         // Overwrite the Password property with the hashed value
         $this->Password = $encryption_details['password'];
         $this->Salt = $encryption_details['salt'];
         $this->PasswordEncryption = $encryption_details['algorithm'];
         // If we haven't manually set a password expiry
         if (!$this->isChanged('PasswordExpiry')) {
             // then set it for us
             if (self::config()->password_expiry_days) {
                 $this->PasswordExpiry = date('Y-m-d', time() + 86400 * self::config()->password_expiry_days);
             } else {
                 $this->PasswordExpiry = null;
             }
         }
     }
     // save locale
     if (!$this->Locale) {
         $this->Locale = i18n::get_locale();
     }
     parent::onBeforeWrite();
 }
 public function testFailedLoginCount()
 {
     $maxFailedLoginsAllowed = 3;
     //set up the config variables to enable login lockouts
     Member::config()->update('lock_out_after_incorrect_logins', $maxFailedLoginsAllowed);
     $member = $this->objFromFixture('SilverStripe\\Security\\Member', 'test');
     $failedLoginCount = $member->FailedLoginCount;
     for ($i = 1; $i < $maxFailedLoginsAllowed; ++$i) {
         $member->registerFailedLogin();
         $this->assertEquals(++$failedLoginCount, $member->FailedLoginCount, 'Failed to increment $member->FailedLoginCount');
         $this->assertFalse($member->isLockedOut(), "Member has been locked out too early");
     }
 }