diff --git a/actions-web.php b/actions-web.php index e2f0a65..d59ba28 100644 --- a/actions-web.php +++ b/actions-web.php @@ -516,38 +516,6 @@ function changecity($userid, $city) } -function resetpassword($number) -{ - global $db, $mailer; - - $number = $db->escape(trim($number)); - - $result = $db->query("SELECT mail,userName FROM users WHERE number='$number'"); - if (!$result->num_rows) { - response(_('No such user found.'), 1); - } - - $row = $result->fetch_assoc(); - $email = $row['mail']; - $username = $row['userName']; - - $subject = _('Password reset'); - - mt_srand(crc32(microtime())); - $password = substr(md5(mt_rand() . microtime() . $email), 0, 8); - - $result = $db->query("UPDATE users SET password=SHA2('$password',512) WHERE number='" . $number . "'"); - - $names = preg_split("/[\s,]+/", $username); - $firstname = $names[0]; - $message = _('Hello') . ' ' . $firstname . ",\n\n" . - _('Your password has been reset successfully.') . "\n\n" . - _('Your new password is:') . "\n" . $password; - - $mailer->sendMail($email, $subject, $message); - response(_('Your password has been reset successfully.') . ' ' . _('Check your email.')); -} - function mapgetmarkers($userId) { global $db, $configuration, $user; diff --git a/admin.php b/admin.php index 147f9b3..1d0de6a 100644 --- a/admin.php +++ b/admin.php @@ -63,7 +63,7 @@
  • isLoggedIn()): ?> -
  • +
  • diff --git a/command.php b/command.php index eb9bb17..ed2fed7 100644 --- a/command.php +++ b/command.php @@ -6,7 +6,6 @@ require_once 'actions-web.php'; $userid = $auth->getUserId(); -$session = $auth->getSessionId(); /** * @var RentSystemInterface $rentSystem @@ -33,18 +32,6 @@ $existing=trim($_GET["existing"]); register($number,$smscode,$checkcode,$fullname,$useremail,$password,$password2,$existing); break; - case "login": - $number=trim($_POST["number"]); - $number = $phonePurifier->purify($number); - $password=trim($_POST["password"]); - $auth->login($number,$password); - break; - case "logout": - $auth->logout(); - break; - case "resetpassword": - resetpassword($_GET["number"]); - break; case "list": $stand=trim($_GET["stand"]); listbikes($stand); diff --git a/composer.json b/composer.json index 320dc41..574d2d4 100644 --- a/composer.json +++ b/composer.json @@ -40,7 +40,8 @@ "symfony/twig-bundle": "^5.4", "symfony/dotenv": "^5.4", "symfony/translation": "^5.4", - "symfony/asset": "^5.4" + "symfony/asset": "^5.4", + "symfony/security-bundle": "^5.4" }, "require-dev": { "squizlabs/php_codesniffer": "^3.10|^4.0", diff --git a/config/bundles.php b/config/bundles.php index b503e88..fe06784 100644 --- a/config/bundles.php +++ b/config/bundles.php @@ -3,7 +3,8 @@ declare(strict_types=1); return [ - Symfony\Bundle\FrameworkBundle\FrameworkBundle::class => ['all' => true], - Symfony\Bundle\MonologBundle\MonologBundle::class => ['all' => true], + \Symfony\Bundle\FrameworkBundle\FrameworkBundle::class => ['all' => true], + \Symfony\Bundle\MonologBundle\MonologBundle::class => ['all' => true], \Symfony\Bundle\TwigBundle\TwigBundle::class => ['all' => true], + \Symfony\Bundle\SecurityBundle\SecurityBundle::class => ['all' => true], ]; \ No newline at end of file diff --git a/config/packages/framework.php b/config/packages/framework.php index 3343a07..fdbae3c 100644 --- a/config/packages/framework.php +++ b/config/packages/framework.php @@ -2,6 +2,7 @@ declare(strict_types=1); +use Symfony\Component\HttpFoundation\Cookie; use Symfony\Config\FrameworkConfig; return static function (FrameworkConfig $framework): void { @@ -9,8 +10,12 @@ ->throw(false); $framework->session() - ->storageFactoryId('session.storage.factory.php_bridge') - ->handlerId(null); + ->enabled(true) + ->handlerId(null) + ->cookieSecure('auto') + ->cookieSamesite(Cookie::SAMESITE_LAX) + ->storageFactoryId('session.storage.factory.native') + ; $framework->secret('%env(APP_SECRET)%'); diff --git a/config/packages/security.php b/config/packages/security.php new file mode 100644 index 0000000..ada7dd2 --- /dev/null +++ b/config/packages/security.php @@ -0,0 +1,71 @@ +passwordHasher(PasswordAuthenticatedUserInterface::class) + ->algorithm('sha512') + ->encodeAsBase64(false) + ->iterations(1); + + $security + ->provider('app_user_provider') + ->id(UserProvider::class); + + $security + ->firewall('dev') + ->pattern('^/(_(profiler|wdt)|css|images|js)/') + ->security(false); + + $mainFirewall = $security->firewall('main'); + $mainFirewall->anonymous(); + $mainFirewall + ->formLogin() + ->loginPath('login') + ->checkPath('login'); + $mainFirewall + ->logout() + ->path('logout') + ->target('/'); + $mainFirewall + ->rememberMe() + ->secret('%kernel.secret%') + ->lifetime(604800) // 1 week in seconds + ->tokenProvider( + [ + 'service' => TokenProvider::class, + ] + ); + + $security + ->accessControl() + ->path('^/login$') + ->roles(['IS_AUTHENTICATED_ANONYMOUSLY']); + $security + ->accessControl() + ->path('^/(sms/)?receive.php$') + ->roles(['IS_AUTHENTICATED_ANONYMOUSLY']); + $security + ->accessControl() + ->path('^/register.php$') + ->roles(['IS_AUTHENTICATED_ANONYMOUSLY']); + $security + ->accessControl() + ->path('^/agree.php$') + ->roles(['IS_AUTHENTICATED_ANONYMOUSLY']); + $security + ->accessControl() + ->path('^/resetPassword$') + ->roles(['IS_AUTHENTICATED_ANONYMOUSLY']); + + $security + ->accessControl() + ->path('^/') + ->roles(['ROLE_USER']); +}; \ No newline at end of file diff --git a/config/routes.php b/config/routes.php index f0ebe04..00cff88 100644 --- a/config/routes.php +++ b/config/routes.php @@ -19,4 +19,10 @@ ->controller([\BikeShare\Controller\SmsRequestController::class, 'index']); $routes->add('agree', '/agree.php') ->controller([\BikeShare\Controller\AgreeController::class, 'index']); + $routes->add('login', '/login') + ->controller([\BikeShare\Controller\SecurityController::class, 'login']); + $routes->add('logout', '/logout') + ->controller([\BikeShare\Controller\SecurityController::class, 'logout']); + $routes->add('reset_password', '/resetPassword') + ->controller([\BikeShare\Controller\SecurityController::class, 'resetPassword']); }; \ No newline at end of file diff --git a/config/services.php b/config/services.php index c66aa92..54284c7 100644 --- a/config/services.php +++ b/config/services.php @@ -50,6 +50,7 @@ '../src/SmsConnector/SmsGateway/SmsGateway.php', '../src/App/Configuration.php', '../src/App/Kernel.php', + '../src/App/Entity', ]); $services->get(MysqliDb::class) diff --git a/index.php b/index.php index 1a8700d..3ac9d72 100644 --- a/index.php +++ b/index.php @@ -109,7 +109,7 @@ echo ''; } - echo '
  • ', _('Log out'), '
  • '; + echo '
  • ', _('Log out'), '
  • '; } ?> diff --git a/public/images/logo_small.svg b/public/images/logo_small.svg new file mode 100644 index 0000000..207a5de --- /dev/null +++ b/public/images/logo_small.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/public/js/functions.js b/public/js/functions.js index 99b7f52..b10a02e 100644 --- a/public/js/functions.js +++ b/public/js/functions.js @@ -11,7 +11,6 @@ $(document).ready(function () { $('.bicycleactions').hide(); $('#notetext').hide(); $('#couponblock').hide(); - $('#passwordresetblock').hide(); $("#rent").hide(); $(document).ajaxStart(function () { $('#overlay').show(); @@ -19,13 +18,6 @@ $(document).ready(function () { $(document).ajaxStop(function () { $('#overlay').hide(); }); - $("#password").focus(function () { - $('#passwordresetblock').show(); - }); - $("#resetpassword").click(function () { - if (window.ga) ga('send', 'event', 'buttons', 'click', 'password-reset'); - resetpassword(); - }); $("#rent").click(function () { if (window.ga) ga('send', 'event', 'buttons', 'click', 'bike-rent'); rent(); @@ -504,20 +496,6 @@ function validatecoupon() { }); } -function resetpassword() { - $('#passwordresetblock').hide(); - if (sms == 0 && $('#number').val() > 0) { - $.ajax({ - url: "command.php?action=resetpassword&number=" + $('#number').val() - }).done(function (jsonresponse) { - jsonobject = $.parseJSON(jsonresponse); - handleresponse(jsonobject); - }); - } else if (sms == 1 && $('#number').val() > 0) { - window.location = "register.php#reset" + $('#number').val(); - } -} - function attachbicycleinfo(element, attachto) { $('#' + attachto + ' .bikenumber').html($(element).attr('data-id')); // show warning, if exists: diff --git a/scan.php b/scan.php index 596a38d..20b3db9 100644 --- a/scan.php +++ b/scan.php @@ -7,7 +7,6 @@ $auth->refreshSession(); $userid = $auth->getUserId(); -$session = $auth->getSessionId(); if (!$auth->isLoggedIn()) { response("

    " . _('You are not logged in.') . "

    ", ERROR); diff --git a/src/App/Entity/User.php b/src/App/Entity/User.php new file mode 100644 index 0000000..ba64ec3 --- /dev/null +++ b/src/App/Entity/User.php @@ -0,0 +1,91 @@ +userId = $userId; + $this->number = $number; + $this->email = $email; + $this->password = $password; + $this->city = $city; + $this->userName = $userName; + $this->privileges = $privileges; + } + + public function getCity(): string + { + return $this->city; + } + + public function getNumber(): string + { + return $this->number; + } + + public function getEmail(): string + { + return $this->email; + } + + public function getPrivileges(): int + { + return $this->privileges; + } + + public function getUserId(): int + { + return $this->userId; + } + + public function getUsername(): string + { + return $this->userName; + } + + public function getPassword(): string + { + return $this->password; + } + + public function getUserIdentifier(): string + { + return $this->number; + } + + public function getRoles(): array + { + return ['ROLE_USER']; + } + + public function getSalt(): ?string + { + return null; + } + + public function eraseCredentials() + { + // If you store any temporary, sensitive data on the user, clear it here + } +} diff --git a/src/App/Security/TokenProvider.php b/src/App/Security/TokenProvider.php new file mode 100644 index 0000000..4354856 --- /dev/null +++ b/src/App/Security/TokenProvider.php @@ -0,0 +1,94 @@ +db = $db; + } + + public function loadTokenBySeries(string $series) + { + if (!isset($this->tokens[$series])) { + $result = $this->db->query( + "SELECT * FROM remember_me_tokens WHERE series='$series'" + ); + if (!$result || $result->rowCount() == 0) { + throw new TokenNotFoundException('No token found.'); + } + + $row = $result->fetchAssoc(); + + $this->tokens[$series] = new PersistentToken( + $row['class'], + $row['username'], + $row['series'], + $row['value'], + new \DateTime($row['lastUsed']) + ); + } + + return $this->tokens[$series]; + } + + /** + * {@inheritdoc} + */ + public function updateToken(string $series, string $tokenValue, \DateTime $lastUsed) + { + $currentToken = $this->loadTokenBySeries($series); + + $token = new PersistentToken( + $currentToken->getClass(), + method_exists($currentToken, 'getUserIdentifier') ? + $currentToken->getUserIdentifier() : $currentToken->getUsername(), + $series, + $tokenValue, + $lastUsed + ); + $this->tokens[$series] = $token; + } + + /** + * {@inheritdoc} + */ + public function deleteTokenBySeries(string $series) + { + $this->db->query( + "DELETE FROM remember_me_tokens WHERE series='$series'" + ); + + unset($this->tokens[$series]); + } + + /** + * {@inheritdoc} + */ + public function createNewToken(PersistentTokenInterface $token) + { + $this->db->query( + "INSERT INTO remember_me_tokens (class, username, series, value, lastUsed) + VALUES ('{$token->getClass()}', + '{$token->getUsername()}', + '{$token->getSeries()}', + '{$token->getTokenValue()}', + '{$token->getLastUsed()->format('Y-m-d H:i:s')}')" + ); + + $this->tokens[$token->getSeries()] = $token; + } +} diff --git a/src/App/Security/UserProvider.php b/src/App/Security/UserProvider.php new file mode 100644 index 0000000..4162d31 --- /dev/null +++ b/src/App/Security/UserProvider.php @@ -0,0 +1,120 @@ +db = $db; + $this->phonePurifier = $phonePurifier; + } + + /** + * @deprecated use loadUserByIdentifier() instead + */ + public function loadUserByUsername(string $username) + { + return $this->loadUserByIdentifier($username); + } + + /** + * + * Symfony calls this method if you use features like switch_user + * or remember_me. If you're not using these features, you do not + * need to implement this method. + * + * @throws UserNotFoundException if the user is not found + */ + public function loadUserByIdentifier(string $identifier): UserInterface + { + $identifier = $this->phonePurifier->purify($identifier); + $result = $this->db->query( + "SELECT userId, number, mail, password, city, userName, privileges FROM users WHERE number='$identifier'" + ); + if (!$result || $result->rowCount() == 0) { + throw new UserNotFoundException(sprintf('Unknown user %s', $identifier)); + } + + $row = $result->fetchAssoc(); + + return new User( + (int)$row['userId'], + $row['number'], + $row['mail'], + $row['password'], + $row['city'], + $row['userName'], + (int)$row['privileges'], + ); + } + + /** + * Refreshes the user after being reloaded from the session. + * + * When a user is logged in, at the beginning of each request, the + * User object is loaded from the session and then this method is + * called. Your job is to make sure the user's data is still fresh by, + * for example, re-querying for fresh User data. + * + * If your firewall is "stateless: true" (for a pure API), this + * method is not called. + * + * @return UserInterface + */ + public function refreshUser(UserInterface $user): UserInterface + { + if (!$user instanceof User) { + throw new UnsupportedUserException(sprintf('Invalid user class "%s".', get_class($user))); + } + + $user = $this->loadUserByIdentifier($user->getNumber()); + + return $user; + } + + /** + * Tells Symfony to use this provider for this User class. + */ + public function supportsClass(string $class): bool + { + return User::class === $class || is_subclass_of($class, User::class); + } + + /** + * Upgrades the hashed password of a user, typically for using a better hash algorithm. + */ + public function upgradePassword(PasswordAuthenticatedUserInterface $user, string $newHashedPassword): void + { + $this->db->query( + "UPDATE users SET password='$newHashedPassword' WHERE number='" . $user->getNumber() . "'" + ); + + $user = new User( + $user->getUserId(), + $user->getNumber(), + $user->getEmail(), + $newHashedPassword, + $user->getCity(), + $user->getUsername(), + $user->getPrivileges() + ); + } +} diff --git a/src/Authentication/Auth.php b/src/Authentication/Auth.php index 53ab4a5..0a9c8f8 100644 --- a/src/Authentication/Auth.php +++ b/src/Authentication/Auth.php @@ -2,125 +2,51 @@ namespace BikeShare\Authentication; -use BikeShare\Db\DbInterface; +use Symfony\Component\Security\Core\Security; +/** + * @deprecated + */ class Auth { - private const SESSION_EXPIRATION = 86400 * 14; // 14 days to keep user logged in - - /** - * @var DbInterface - */ - private $db; + private Security $security; public function __construct( - DbInterface $db + Security $security ) { - $this->db = $db; + $this->security = $security; } public function getUserId() { - if (isset($_COOKIE["loguserid"])) { - return (int)$this->db->escape(trim($_COOKIE["loguserid"])); + /** + * @var \BikeShare\App\Entity\User $user + */ + $user = $this->security->getUser(); + if (!is_null($user)) { + return $user->getUserId(); } else { return 0; } } - public function getSessionId() - { - if (isset($_COOKIE["logsession"])) { - return $this->db->escape(trim($_COOKIE["logsession"])); - } else { - return ''; - } - } - public function login($number, $password) { - $number = $this->db->escape(trim($number)); - $password = $this->db->escape(trim($password)); - - $result = $this->db->query( - "SELECT userId FROM users WHERE number='$number' AND password=SHA2('$password',512)" - ); - if ($result && $result->rowCount() == 1) { - $row = $result->fetchAssoc(); - $userId = $row['userId']; - $sessionId = hash('sha256', $userId . $number . time()); - $timeStamp = time() + self::SESSION_EXPIRATION; - $this->db->query("DELETE FROM sessions WHERE userId='$userId'"); - $this->db->query( - "INSERT INTO sessions SET userId='$userId',sessionId='$sessionId',timeStamp='$timeStamp'" - ); - $this->db->commit(); - setcookie('loguserid', $userId, $timeStamp); - setcookie('logsession', $sessionId, $timeStamp); - header('HTTP/1.1 302 Found'); - header('Location: /'); - header('Connection: close'); - } else { - header('HTTP/1.1 302 Found'); - header('Location: /?error=1'); - header('Connection: close'); - } + throw new \Exception("Deprecated method"); } public function logout() { - if ($this->isLoggedIn()) { - $userid = $this->getUserId(); - $sessionId = $this->getSessionId(); - $this->db->query("DELETE FROM sessions WHERE userId='$userid' OR sessionId='$sessionId'"); - $this->db->commit(); - } - setcookie("loguserid", "0", ['expires' => time() - 3600, 'path' => "/"]); - setcookie("logsession", "", ['expires' => time() - 3600, 'path' => "/"]); - header('HTTP/1.1 302 Found'); - header('Location: /'); - header('Connection: close'); + throw new \Exception("Deprecated method"); } public function refreshSession() { - if (!$this->isLoggedIn()) { - return; - } - - $this->db->query("DELETE FROM sessions WHERE timeStamp<='" . time() . "'"); - $userid = $this->getUserId(); - $sessionId = $this->getSessionId(); - $result = $this->db->query( - "SELECT sessionId FROM sessions WHERE userId='$userid' - AND sessionId='$sessionId' AND timeStamp>'" . time() . "'" - ); - if ($result->rowCount() == 1) { - $timestamp = time() + self::SESSION_EXPIRATION; - $this->db->query( - "UPDATE sessions SET timeStamp='$timestamp' WHERE userId='$userid' AND sessionId='$sessionId'" - ); - $this->db->commit(); - } else { - $this->logout(); - } +// throw new \Exception("Deprecated method"); } public function isLoggedIn() { - $session = $this->getSessionId(); - - if (!empty($session)) { - $userid = $this->getUserId(); - $result = $this->db->query( - "SELECT sessionId FROM sessions WHERE - userId='$userid' AND sessionId='$session' AND timeStamp>'" . time() . "'" - ); - if ($result && $result->rowCount() == 1) { - return true; - } - } - - return false; + return !is_null($this->security->getUser()); } } diff --git a/src/Controller/SecurityController.php b/src/Controller/SecurityController.php new file mode 100644 index 0000000..55f0ad4 --- /dev/null +++ b/src/Controller/SecurityController.php @@ -0,0 +1,105 @@ +getLastAuthenticationError(); + $lastUsername = $authenticationUtils->getLastUsername(); + + return $this->render( + 'security/login.html.twig', + [ + 'isSmsSystemEnabled' => $configuration->get('connectors')['sms'] == '', + 'last_username' => $lastUsername, + 'error' => $error, + ] + ); + } + + /** + * @Route("/logout", name="logout") + */ + public function logout() + { + // controller can be blank: it will never be executed! + throw new \Exception('Don\'t forget to activate logout in security.php'); + } + + /** + * @Route("/resetPassword", name="reset_password") + */ + public function resetPassword( + Request $request, + Configuration $configuration, + MailSenderInterface $mailer, + UserProviderInterface $userProvider, + UserPasswordHasherInterface $passwordHasher, + TranslatorInterface $translator + ) { + if ($request->isMethod('POST')) { + $number = $request->request->get('_username'); + + try { + $user = $userProvider->loadUserByIdentifier($number); + } catch (UserNotFoundException $e) { + $user = null; + } + + if (!is_null($user)) { + mt_srand(crc32(microtime())); + $plainPassword = substr(md5(mt_rand() . microtime() . $user->getUsername()), 0, 8); + $hashedPassword = $passwordHasher->hashPassword( + $user, + $plainPassword + ); + $userProvider->upgradePassword($user, $hashedPassword); + + $subject = $translator->trans('Password reset'); + $names = preg_split("/[\s,]+/", $user->getUsername()); + $firstname = $names[0]; + $message = $translator->trans('Hello') . ' ' . $firstname . ",\n\n" . + $translator->trans('Your password has been reset successfully.') . "\n\n" . + $translator->trans('Your new password is:') . "\n" . $plainPassword; + + $mailer->sendMail($user->getEmail(), $subject, $message); + } + + $this->addFlash( + 'success', + $translator->trans('Your password has been reset successfully.') + . ' ' + . $translator->trans('Check your email.') + ); + + return $this->redirectToRoute('reset_password'); + } + + return $this->render( + 'security/reset_password.html.twig', + [ + 'isSmsSystemEnabled' => $configuration->get('connectors')['sms'] == '', + ] + ); + } +} diff --git a/templates/base.html.twig b/templates/base.html.twig index c9cdee4..43e8fc3 100644 --- a/templates/base.html.twig +++ b/templates/base.html.twig @@ -9,7 +9,7 @@ diff --git a/templates/index.html.twig b/templates/index.html.twig index f401af6..a2be36d 100644 --- a/templates/index.html.twig +++ b/templates/index.html.twig @@ -57,12 +57,7 @@ --> -{% if auth.isLoggedIn %} -
    -{% else %} - {{ siteName }} -{% endif %} +
    - {% if auth.isLoggedIn %} -
    -
    -

    {{ siteName }}

    -
    -
    -
    -
    -

    -
    -
    -
    -
    -
    -
    -
    +
    +
    +

    {{ siteName }}

    -
    -
    -
    - -
    +
    +
    +
    + +

    +
    +
    +
    +
    +
    -
    +
    +
    +
    -
    +
    -
    -
    -
    -
    - -
    -
    +
    +
    +
    +
    -
    -
    -
    - - (and - {{ 'report problem'|trans }} - - ) -
    +
    +
    +
    +
    +
    +
    - -
    - {% else %} -
    -

    {{ 'Log in'|trans }}

    - {% if error and error == 1 %} - - {% elseif error and error == 2 %} - - {% endif %} -
    -
    -
    - - -
    -
    -
    -
    - - -
    -
    -
    -
    -
    - -
    -
    -
    -
    -
    - +
    +
    +
    + + (and + {{ 'report problem'|trans }} + + )
    - {% endif %} +
    + +
    diff --git a/templates/security/login.html.twig b/templates/security/login.html.twig new file mode 100644 index 0000000..dd3dcd1 --- /dev/null +++ b/templates/security/login.html.twig @@ -0,0 +1,39 @@ +{% extends 'base.html.twig' %} + +{% block body %} +
    + {% if error %} +
    {{ error.messageKey|trans(error.messageData, 'security') }}
    + {% endif %} + + +
    +{% endblock %} \ No newline at end of file diff --git a/templates/security/reset_password.html.twig b/templates/security/reset_password.html.twig new file mode 100644 index 0000000..e785169 --- /dev/null +++ b/templates/security/reset_password.html.twig @@ -0,0 +1,25 @@ +{% extends 'base.html.twig' %} + +{% block body %} +
    + + {% for message in app.flashes('success') %} +
    + {{ message }} +
    + {% endfor %} + + +
    +{% endblock %} \ No newline at end of file diff --git a/tests/Authentication/AuthTest.php b/tests/Authentication/AuthTest.php deleted file mode 100644 index 4460704..0000000 --- a/tests/Authentication/AuthTest.php +++ /dev/null @@ -1,379 +0,0 @@ -db = $this->createMock(DbInterface::class); - $this->auth = new Auth( - $this->db - ); - } - - protected function tearDown(): void - { - unset( - $this->db, - $this->auth - ); - } - - - /** - * @dataProvider getUserIdDataProvider - */ - public function testGetUserId( - $cookieValue = null, - $expectedUserId = 0 - ) { - if (!is_null($cookieValue)) { - $_COOKIE["loguserid"] = $cookieValue; - $this->db->expects($this->once()) - ->method('escape') - ->with($cookieValue) - ->willReturn($cookieValue); - } - $this->assertEquals($expectedUserId, $this->auth->getUserId()); - } - - public function getUserIdDataProvider() - { - - yield 'no cookie' => [ - 'cookieValue' => null, - 'expectedUserId' => 0, - ]; - yield 'empty cookie' => [ - 'cookieValue' => '', - 'expectedUserId' => 0, - ]; - yield 'not a number' => [ - 'cookieValue' => 'not a number', - 'expectedUserId' => 0, - ]; - yield 'number' => [ - 'cookieValue' => '123', - 'expectedUserId' => 123, - ]; - yield 'sql injection' => [ - 'cookieValue' => '123; DROP TABLE users', - 'expectedUserId' => 123, - ]; - } - - /** - * @dataProvider getSessionIdDataProvider - */ - public function testGetSessionId( - $cookieValue = null, - $expectedSessionId = 0 - ) { - if (!is_null($cookieValue)) { - $_COOKIE["logsession"] = $cookieValue; - $this->db->expects($this->once()) - ->method('escape') - ->with($cookieValue) - ->willReturn(str_replace(';', '\;', $cookieValue));# just an example for test - } - $this->assertEquals($expectedSessionId, $this->auth->getSessionId()); - } - - public function getSessionIdDataProvider() - { - - yield 'no cookie' => [ - 'cookieValue' => null, - 'expectedSessionId' => '', - ]; - yield 'empty cookie' => [ - 'cookieValue' => '', - 'expectedSessionId' => '', - ]; - yield 'not a number' => [ - 'cookieValue' => 'not a number', - 'expectedSessionId' => 'not a number', - ]; - yield 'number' => [ - 'cookieValue' => '123', - 'expectedSessionId' => '123', - ]; - yield 'sql injection' => [ - 'cookieValue' => '123; DROP TABLE users', - 'expectedSessionId' => '123\; DROP TABLE users', - ]; - } - - - public function testLogin() - { - $number = 'number'; - $password = 'password'; - $userId = '123'; - - $this->getFunctionMock('BikeShare\Authentication', 'time') - ->expects($this->exactly(2)) - ->willReturn(9999); - - $this->db->expects($this->exactly(2)) - ->method('escape') - ->withConsecutive( - [$number], - [$password] - )->willReturnOnConsecutiveCalls($number, $password); - - $sessionId = hash('sha256', $userId . $number . '9999'); - - $foundUser = $this->createMock(DbResultInterface::class); - $foundUser->expects($this->once()) - ->method('fetchAssoc') - ->willReturn(['userId' => $userId]); - $foundUser->expects($this->once()) - ->method('rowCount') - ->willReturn(1); - - $this->db->expects($this->exactly(3)) - ->method('query') - ->withConsecutive( - ["SELECT userId FROM users WHERE number='$number' AND password=SHA2('$password',512)"], - ["DELETE FROM sessions WHERE userId='{$userId}'"], - ["INSERT INTO sessions SET userId='{$userId}',sessionId='{$sessionId}',timeStamp='1219599'"] - ) - ->willReturnOnConsecutiveCalls( - $foundUser, - null, - null - ); - - $this->getFunctionMock('BikeShare\Authentication', 'setcookie') - ->expects($this->exactly(2)) - ->withConsecutive( - ['loguserid', $userId, 1219599], - ['logsession', $sessionId, 1219599] - ) - ->willReturn(true); - - $this->getFunctionMock('BikeShare\Authentication', 'header') - ->expects($this->exactly(3)) - ->withConsecutive( - ['HTTP/1.1 302 Found'], - ['Location: /'], - ['Connection: close'] - ); - - $this->auth->login($number, $password); - } - - /** - * @dataProvider isLoggedInDataProvider - */ - public function testIsLoggedIn( - $userId, - $sessionId, - $escapeCallParams, - $escapeCallResults, - $sessionFindResult, - $expectedResult = false - ) { - if ($userId) { - $_COOKIE["loguserid"] = $userId; - } else { - $_COOKIE["loguserid"] = null; - } - if ($sessionId) { - $_COOKIE["logsession"] = $sessionId; - } else { - $_COOKIE["logsession"] = null; - } - - $this->getFunctionMock('BikeShare\Authentication', 'time') - ->expects(count($escapeCallParams) > 0 ? $this->once() : $this->never()) - ->willReturn(9999); - - $this->db - ->expects($this->exactly(count($escapeCallParams))) - ->method('escape') - ->withConsecutive(...$escapeCallParams) - ->willReturnOnConsecutiveCalls(...$escapeCallResults); - - $this->db - ->expects(count($escapeCallParams) > 0 ? $this->once() : $this->never()) - ->method('query') - ->with( - "SELECT sessionId FROM sessions WHERE - userId='$userId' AND sessionId='$sessionId' AND timeStamp>'9999'" - ) - ->willReturn($sessionFindResult); - - $this->assertEquals($expectedResult, $this->auth->isLoggedIn()); - } - - public function isLoggedInDataProvider() - { - yield 'no user id' => [ - 'userId' => 0, - 'sessionId' => '', - 'escapeCallParams' => [], - 'escapeCallResults' => [], - 'sessionFindResult' => $this->createMock(DbResultInterface::class), - 'expectedResult' => false, - ]; - yield 'no session id' => [ - 'userId' => 1, - 'sessionId' => '', - 'escapeCallParams' => [], - 'escapeCallResults' => [], - 'sessionFindResult' => $this->createMock(DbResultInterface::class), - 'expectedResult' => false, - ]; - $sessionFindResult = $this->createMock(DbResultInterface::class); - $sessionFindResult->expects($this->once()) - ->method('rowCount') - ->willReturn(1); - yield 'user id and session id' => [ - 'userId' => 1, - 'sessionId' => '123', - 'escapeCallParams' => [ - ['123'], - [1], - ], - 'escapeCallResults' => [ - '123', - 1, - ], - 'sessionFindResult' => $sessionFindResult, - 'expectedResult' => true, - ]; - } - - public function testLogout() - { - $userId = 1; - $sessionId = '123'; - $_COOKIE["loguserid"] = $userId; - $_COOKIE["logsession"] = $sessionId; - $this->db->expects($this->exactly(4)) - ->method('escape') - ->withConsecutive( - [$sessionId], - [$userId], - [$userId], - [$sessionId] - ) - ->willReturnOnConsecutiveCalls( - $sessionId, - $userId, - $userId, - $sessionId - ); - - $this->getFunctionMock('BikeShare\Authentication', 'time') - ->expects($this->exactly(3)) - ->willReturn(9999); - - $this->getFunctionMock('BikeShare\Authentication', 'setcookie') - ->expects($this->exactly(2)) - ->withConsecutive( - ['loguserid', '0', ['expires' => 6399, 'path' => '/']], - ['logsession', '', ['expires' => 6399, 'path' => '/']] - ) - ->willReturn(true); - - $this->getFunctionMock('BikeShare\Authentication', 'header') - ->expects($this->exactly(3)) - ->withConsecutive( - ['HTTP/1.1 302 Found'], - ['Location: /'], - ['Connection: close'] - ); - - $sessionFindResult = $this->createMock(DbResultInterface::class); - $sessionFindResult->expects($this->once()) - ->method('rowCount') - ->willReturn(1); - - $this->db->expects($this->exactly(2)) - ->method('query') - ->withConsecutive( - ["SELECT sessionId FROM sessions WHERE - userId='1' AND sessionId='123' AND timeStamp>'9999'"], - ["DELETE FROM sessions WHERE userId='$userId' OR sessionId='$sessionId'"] - ) - ->willReturnOnConsecutiveCalls( - $sessionFindResult, - null - ); - - $this->auth->logout(); - } - - public function testRefreshSession() - { - $userId = 1; - $sessionId = '123'; - $_COOKIE["loguserid"] = $userId; - $_COOKIE["logsession"] = $sessionId; - $this->db->expects($this->exactly(4)) - ->method('escape') - ->withConsecutive( - [$sessionId], - [$userId], - [$userId], - [$sessionId] - ) - ->willReturnOnConsecutiveCalls( - $sessionId, - $userId, - $userId, - $sessionId - ); - - $this->getFunctionMock('BikeShare\Authentication', 'time') - ->expects($this->exactly(4)) - ->willReturn(9999); - - $sessionFindResult = $this->createMock(DbResultInterface::class); - $sessionFindResult->expects($this->exactly(2)) - ->method('rowCount') - ->willReturn(1); - - $this->db->expects($this->exactly(4)) - ->method('query') - ->withConsecutive( - ["SELECT sessionId FROM sessions WHERE - userId='1' AND sessionId='123' AND timeStamp>'9999'"], - ["DELETE FROM sessions WHERE timeStamp<='9999'"], - ["SELECT sessionId FROM sessions WHERE userId='1' - AND sessionId='123' AND timeStamp>'9999'"], - ["UPDATE sessions SET timeStamp='1219599' WHERE userId='1' AND sessionId='123'"] - ) - ->willReturnOnConsecutiveCalls( - $sessionFindResult, - null, - $sessionFindResult, - null - ); - - $this->auth->refreshSession(); - } -} diff --git a/translations/messages+intl-icu.en.php b/translations/messages+intl-icu.en.php index 5e124d8..a5b5702 100644 --- a/translations/messages+intl-icu.en.php +++ b/translations/messages+intl-icu.en.php @@ -213,10 +213,10 @@ 'Invalid coupon, try again.' => '', 'No such user found.' => '', 'Password reset' => 'Password reset', - 'Hello' => '', - 'Your password has been reset successfully.' => '', - 'Your new password is:' => '', - 'Check your email.' => '', + 'Hello' => 'Hello', + 'Your password has been reset successfully.' => 'Your password has been reset successfully.', + 'Your new password is:' => 'Your new password is:', + 'Check your email.' => 'Check your email.', 'registration' => '', 'Registration' => '', 'Step 1 - Confirm your phone number' => '',