/** * 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; }
/** * Test combining validation results together */ public function testCombineResults() { $result = new ValidationResult(); $anotherresult = new ValidationResult(); $yetanotherresult = new ValidationResult(); $anotherresult->error("Eat with your mouth closed", "EATING101"); $yetanotherresult->error("You didn't wash your hands", "BECLEAN"); $this->assertTrue($result->valid()); $this->assertFalse($anotherresult->valid()); $this->assertFalse($yetanotherresult->valid()); $result->combineAnd($anotherresult)->combineAnd($yetanotherresult); $this->assertFalse($result->valid()); $this->assertEquals(array("EATING101" => "Eat with your mouth closed", "BECLEAN" => "You didn't wash your hands"), $result->messageList()); }
/** * @param String $password * @param Member $member * @return ValidationResult */ public function validate($password, $member) { $valid = ValidationResult::create(); if ($this->minLength) { if (strlen($password) < $this->minLength) { $valid->error(sprintf(_t('PasswordValidator.TOOSHORT', 'Password is too short, it must be %s or more characters long'), $this->minLength), 'TOO_SHORT'); } } if ($this->minScore) { $score = 0; $missedTests = array(); foreach ($this->testNames as $name) { if (preg_match(self::config()->character_strength_tests[$name], $password)) { $score++; } else { $missedTests[] = _t('PasswordValidator.STRENGTHTEST' . strtoupper($name), $name, 'The user needs to add this to their password for more complexity'); } } if ($score < $this->minScore) { $valid->error(sprintf(_t('PasswordValidator.LOWCHARSTRENGTH', 'Please increase password strength by adding some of the following characters: %s'), implode(', ', $missedTests)), 'LOW_CHARACTER_STRENGTH'); } } if ($this->historicalPasswordCount) { $previousPasswords = MemberPassword::get()->where(array('"MemberPassword"."MemberID"' => $member->ID))->sort('"Created" DESC, "ID" DESC')->limit($this->historicalPasswordCount); /** @var MemberPassword $previousPassword */ foreach ($previousPasswords as $previousPassword) { if ($previousPassword->checkPassword($password)) { $valid->error(_t('PasswordValidator.PREVPASSWORD', 'You\'ve already used that password in the past, please choose a new password'), 'PREVIOUS_PASSWORD'); break; } } } return $valid; }
/** * Validate the owner object - check for existence of infinite loops. * * @param ValidationResult $validationResult */ public function validate(ValidationResult $validationResult) { // The object is new, won't be looping. if (!$this->owner->ID) { return; } // The object has no parent, won't be looping. if (!$this->owner->ParentID) { return; } // The parent has not changed, skip the check for performance reasons. if (!$this->owner->isChanged('ParentID')) { return; } // Walk the hierarchy upwards until we reach the top, or until we reach the originating node again. $node = $this->owner; while ($node) { if ($node->ParentID == $this->owner->ID) { // Hierarchy is looping. $validationResult->error(_t('Hierarchy.InfiniteLoopNotAllowed', 'Infinite loop found within the "{type}" hierarchy. Please change the parent to resolve this', 'First argument is the class that makes up the hierarchy.', array('type' => $this->owner->class)), 'INFINITE_LOOP'); break; } $node = $node->ParentID ? $node->Parent() : null; } // At this point the $validationResult contains the response. }
/** * Construct a new ValidationException with an optional ValidationResult object * * @param ValidationResult|string $result The ValidationResult containing the * failed result. Can be substituted with an error message instead if no * ValidationResult exists. * @param string|integer $message The error message. If $result was given the * message string rather than a ValidationResult object then this will have * the error code number. * @param integer $code The error code number, if not given in the second parameter */ public function __construct($result = null, $message = null, $code = 0) { // Check arguments if (!$result instanceof ValidationResult) { // Shift parameters if no ValidationResult is given $code = $message; $message = $result; // Infer ValidationResult from parameters $result = new ValidationResult(false, $message); } elseif (empty($message)) { // Infer message if not given $message = $result->message(); } // Construct $this->result = $result; parent::__construct($message, $code); }
/** * Validate the current object. * * By default, there is no validation - objects are always valid! However, you can overload this method in your * DataObject sub-classes to specify custom validation, or use the hook through DataExtension. * * Invalid objects won't be able to be written - a warning will be thrown and no write will occur. onBeforeWrite() * and onAfterWrite() won't get called either. * * It is expected that you call validate() in your own application to test that an object is valid before * attempting a write, and respond appropriately if it isn't. * * @see {@link ValidationResult} * @return ValidationResult */ public function validate() { $result = ValidationResult::create(); $this->extend('validate', $result); return $result; }
/** * Hook to validate this record against a validation result * * @param ValidationResult $result * @param string $filename Optional filename to validate. If omitted, the current value is validated. * @return bool Valid flag */ public function validate(ValidationResult $result, $filename = null) { if (empty($filename)) { $filename = $this->getFilename(); } if (empty($filename) || $this->isValidFilename($filename)) { return true; } // Check allowed extensions $extensions = $this->getAllowedExtensions(); if (empty($extensions)) { $extensions = File::config()->allowed_extensions; } sort($extensions); $message = _t('File.INVALIDEXTENSION', 'Extension is not allowed (valid: {extensions})', 'Argument 1: Comma-separated list of valid extensions', array('extensions' => wordwrap(implode(', ', $extensions)))); $result->error($message); return false; }
/** * 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')) { // 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(); }
/** * Combine this Validation Result with the ValidationResult given in other. * It will be valid if both this and the other result are valid. * This object will be modified to contain the new validation information. * * @param ValidationResult the validation result object to combine * @return ValidationResult this */ public function combineAnd(ValidationResult $other) { $this->isValid = $this->isValid && $other->valid(); $this->errorList = array_merge($this->errorList, $other->messageList()); return $this; }