From 50f2913a341bcf22304d84a61772cda990ecb933 Mon Sep 17 00:00:00 2001 From: Sveneld Date: Sun, 10 Nov 2024 16:03:34 +0100 Subject: [PATCH] symfony security --- admin.php | 2 +- command.php | 10 - composer.json | 3 +- config/bundles.php | 5 +- config/packages/framework.php | 9 +- config/packages/security.php | 67 +++++ config/routes.php | 5 + config/services.php | 1 + index.php | 2 +- public/images/logo_small.svg | 7 + scan.php | 1 - src/App/Entity/User.php | 82 ++++++ src/App/Security/TokenProvider.php | 93 +++++++ src/App/Security/UserProvider.php | 109 ++++++++ src/Authentication/Auth.php | 108 ++------ src/Controller/SecurityController.php | 40 +++ templates/base.html.twig | 15 +- templates/index.html.twig | 168 ++++-------- templates/security/login.html.twig | 36 +++ tests/Authentication/AuthTest.php | 379 -------------------------- 20 files changed, 534 insertions(+), 608 deletions(-) create mode 100644 config/packages/security.php create mode 100644 public/images/logo_small.svg create mode 100644 src/App/Entity/User.php create mode 100644 src/App/Security/TokenProvider.php create mode 100644 src/App/Security/UserProvider.php create mode 100644 src/Controller/SecurityController.php create mode 100644 templates/security/login.html.twig delete mode 100644 tests/Authentication/AuthTest.php 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..10c43f5 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,15 +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; 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..81e246a --- /dev/null +++ b/config/packages/security.php @@ -0,0 +1,67 @@ +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('^/') + ->roles(['ROLE_USER']); +}; \ No newline at end of file diff --git a/config/routes.php b/config/routes.php index f0ebe04..0f33fef 100644 --- a/config/routes.php +++ b/config/routes.php @@ -19,4 +19,9 @@ ->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']); + }; \ 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/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..0314c90 --- /dev/null +++ b/src/App/Entity/User.php @@ -0,0 +1,82 @@ +userId = $userId; + $this->number = $number; + $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 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 + } +} \ No newline at end of file diff --git a/src/App/Security/TokenProvider.php b/src/App/Security/TokenProvider.php new file mode 100644 index 0000000..486f6ee --- /dev/null +++ b/src/App/Security/TokenProvider.php @@ -0,0 +1,93 @@ +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; + } +} \ No newline at end of file diff --git a/src/App/Security/UserProvider.php b/src/App/Security/UserProvider.php new file mode 100644 index 0000000..2cbb929 --- /dev/null +++ b/src/App/Security/UserProvider.php @@ -0,0 +1,109 @@ +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, 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['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 + { + // TODO: when hashed passwords are in use, this method should: + // 1. persist the new password in the user storage + // 2. update the $user object with $user->setPassword($newHashedPassword); + } +} \ No newline at end of file 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..af8d2e4 --- /dev/null +++ b/src/Controller/SecurityController.php @@ -0,0 +1,40 @@ +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'); + } +} \ No newline at end of file diff --git a/templates/base.html.twig b/templates/base.html.twig index c9cdee4..ebeb3b7 100644 --- a/templates/base.html.twig +++ b/templates/base.html.twig @@ -23,18 +23,25 @@ Home (current) + {% if app.user %} + + {% endif %} 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..67cab9b --- /dev/null +++ b/templates/security/login.html.twig @@ -0,0 +1,36 @@ +{% 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/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(); - } -}