[TESTS][Security] Fix SecurityTest. Remove nickname normalization on register (a plugin can handle that). Move from filter_var(FILTER_VALIDATE_EMAIL) as it does not support dotless domains

This commit is contained in:
Hugo Sales 2021-11-14 23:15:24 +00:00
parent bf5ffe7d3d
commit eab6de3609
No known key found for this signature in database
GPG Key ID: 7D0C7EAFC9D835A0
7 changed files with 62 additions and 43 deletions

View File

@ -26,6 +26,7 @@ use App\Util\Exception\NicknameInvalidException;
use App\Util\Exception\NicknameNotAllowedException;
use App\Util\Exception\NicknameTakenException;
use App\Util\Exception\NicknameTooLongException;
use App\Util\Exception\NotFoundException;
use App\Util\Exception\ServerException;
use App\Util\Form\FormFields;
use App\Util\Nickname;
@ -126,10 +127,24 @@ class Security extends Controller
$data = $form->getData();
$data['password'] = $form->get('password')->getData();
// TODO: ensure there's no user with this email registered already
// Already used is checked below
$sanitized_nickname = Nickname::normalize($data['nickname'], check_already_used: false, which: Nickname::CHECK_LOCAL_USER, check_is_allowed: false);
$sanitized_nickname = null;
if (Event::handle('SanitizeNickname', [$data['nickname'], &$sanitized_nickname]) != Event::stop) {
$sanitized_nickname = $data['nickname'];
// $sanitized_nickname = Nickname::normalize($data['nickname'], check_already_used: false, which: Nickname::CHECK_LOCAL_USER, check_is_allowed: false);
}
try {
$found_user = DB::findOneBy('local_user', ['or' => ['nickname' => $sanitized_nickname, 'outgoing_email' => $data['email']]]);
if ($found_user->getNickname() === $sanitized_nickname) {
throw new NicknameTakenException($found_user->getActor());
} elseif ($found_user->getOutgoingEmail() === $data['email']) {
throw new EmailTakenException($found_user->getActor());
}
unset($found_user);
} catch (NotFoundException) {
// continue
}
try {
// This already checks if the nickname is being used
@ -153,9 +168,9 @@ class Security extends Controller
// @codeCoverageIgnoreStart
} catch (UniqueConstraintViolationException $e) {
// _something_ was duplicated, but since we already check if nickname is in use, we can't tell what went wrong
$e = 'An error occurred while trying to register';
Log::critical($e . " with nickname: '{$sanitized_nickname}' and email '{$data['email']}'");
throw new ServerException(_m($e));
$m = 'An error occurred while trying to register';
Log::critical($m . " with nickname: '{$sanitized_nickname}' and email '{$data['email']}'");
throw new ServerException(_m($m), previous: $e);
}
// @codeCoverageIgnoreEnd

View File

@ -362,7 +362,8 @@ class LocalUser extends Entity implements UserInterface
*/
public static function getByEmail(string $email): ?self
{
return Cache::get("user-email-{$email}", fn () => DB::findOneBy('local_user', ['or' => ['outgoing_email' => $email, 'incoming_email' => $email]]));
$key = str_replace('@', '-', $email);
return Cache::get("user-email-{$key}", fn () => DB::findOneBy('local_user', ['or' => ['outgoing_email' => $email, 'incoming_email' => $email]]));
}
public static function schemaDef(): array

View File

@ -25,8 +25,10 @@ use function App\Core\I18n\_m;
use App\Core\Router\Router;
use App\Entity\LocalUser;
use App\Entity\User;
use App\Util\Common;
use App\Util\Exception\NoSuchActorException;
use App\Util\Exception\NotFoundException;
use App\Util\Nickname;
use Stringable;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\Request;
@ -99,9 +101,9 @@ class Authenticator extends AbstractFormLoginAuthenticator
}
$user = null;
try {
if (filter_var($credentials['nickname_or_email'], \FILTER_VALIDATE_EMAIL) !== false) {
if (Common::isValidEmail($credentials['nickname_or_email'])) {
$user = LocalUser::getByEmail($credentials['nickname_or_email']);
} else {
} elseif (Nickname::isValid($credentials['nickname_or_email'])) {
$user = LocalUser::getByNickname($credentials['nickname_or_email']);
}
if ($user === null) {

View File

@ -39,6 +39,8 @@ use App\Core\Security;
use App\Entity\Actor;
use App\Entity\LocalUser;
use App\Util\Exception\NoLoggedInUser;
use Egulias\EmailValidator\EmailValidator;
use Egulias\EmailValidator\Validation\RFCValidation as RFCEmailValidation;
use Functional as F;
use Symfony\Component\DependencyInjection\ParameterBag\ContainerBagInterface;
use Symfony\Component\HttpFoundation\Request;
@ -293,6 +295,11 @@ abstract class Common
&& preg_match($regex, parse_url($url, \PHP_URL_SCHEME));
}
public static function isValidEmail(string $email): bool
{
return (new EmailValidator())->isValid($email, new RFCEmailValidation());
}
/**
* Flatten an array of ['note' => note, 'replies' => [notes]] to an array of notes
*/

View File

@ -39,11 +39,11 @@ use App\Entity\Actor;
class EmailTakenException extends EmailException
{
public ?Actor $profile = null; // the Actor which occupies the email
public ?Actor $actor = null; // the Actor which occupies the email
public function __construct(?Actor $profile = null, ?string $msg = null, int $code = 400)
public function __construct(?Actor $actor = null, ?string $msg = null, int $code = 400)
{
$this->profile = $profile;
$this->profile = $actor;
parent::__construct($msg, $code);
}

View File

@ -48,11 +48,11 @@ use App\Entity\Actor;
class NicknameTakenException extends NicknameException
{
public ?Actor $profile = null; // the Actor which occupies the nickname
public ?Actor $actor = null; // the Actor which occupies the nickname
public function __construct(?Actor $profile = null, ?string $msg = null, int $code = 400)
public function __construct(?Actor $actor = null, ?string $msg = null, int $code = 400)
{
$this->profile = $profile;
$this->profile = $actor;
parent::__construct($msg, $code);
}

View File

@ -66,9 +66,8 @@ class SecurityTest extends GNUsocialTestCase
{
self::testLogin('taken_user', 'wrong password');
$this->assertResponseIsSuccessful();
// TODO(eliseu) Login page doesn't have this error
// $this->assertSelectorTextContains('.alert', 'Invalid login credentials');
$this->assertRouteSame('login');
$this->assertSelectorTextContains('.alert', 'Invalid login credentials');
$this->assertRouteSame('security_login');
}
public function testLoginEmail()
@ -120,7 +119,7 @@ class SecurityTest extends GNUsocialTestCase
]);
$this->assertSelectorTextContains('form[name=register] ul li', 'The password fields must match');
$this->assertResponseStatusCodeSame(200);
$this->assertRouteSame('register');
$this->assertRouteSame('security_register');
}
private function testRegisterPasswordLength(string $password, string $error)
@ -128,7 +127,7 @@ class SecurityTest extends GNUsocialTestCase
self::testRegister('new_nickname', 'email@provider', $password);
$this->assertResponseIsSuccessful();
$this->assertSelectorTextContains('.help-block > ul > li', $error);
$this->assertRouteSame('register');
$this->assertRouteSame('security_register');
}
public function testRegisterPasswordEmpty()
@ -151,7 +150,7 @@ class SecurityTest extends GNUsocialTestCase
self::testRegister('new_nickname', '', 'foobar');
$this->assertResponseIsSuccessful();
$this->assertSelectorTextContains('.help-block > ul > li', 'Please enter an email');
$this->assertRouteSame('register');
$this->assertRouteSame('security_register');
}
private function testRegisterNicknameLength(string $nickname, string $error)
@ -159,7 +158,7 @@ class SecurityTest extends GNUsocialTestCase
self::testRegister($nickname, 'email@provider', 'foobar');
$this->assertResponseIsSuccessful();
$this->assertSelectorTextContains('.help-block > ul > li', $error);
$this->assertRouteSame('register');
$this->assertRouteSame('security_register');
}
public function testRegisterNicknameEmpty()
@ -167,11 +166,6 @@ class SecurityTest extends GNUsocialTestCase
self::testRegisterNicknameLength('', error: 'Please enter a nickname');
}
public function testRegisterNicknameShort()
{
self::testRegisterNicknameLength('f', error: 'Your nickname must be at least');
}
public function testRegisterNicknameLong()
{
self::testRegisterNicknameLength(str_repeat('f', 128), error: 'Your nickname must be at most');