From 5d4bb843683c9b7d71d79a1985cc96f83536af67 Mon Sep 17 00:00:00 2001 From: Yanis DAHMANE-BOUNOUA Date: Tue, 21 Nov 2023 21:30:12 +0100 Subject: [PATCH 1/8] front-controller (#17) I did the FRONT CONTROLLERRRRR. (Salva version lol) # When you create a new controller When you create a new controller, define the path to call it here (image 1). Don't forget to associate a controller with a role. # The action's main method The main method have to return a `ViewHttpResponse`. If you create a new action, please make sure that the action's main method have the same name than the action (image 2 and 3) and to make the main method public. Co-authored-by: DahmaneYanis Co-authored-by: d_yanis Co-authored-by: Override-6 Reviewed-on: https://codefirst.iut.uca.fr/git/IQBall/Application-Web/pulls/17 Co-authored-by: Yanis DAHMANE-BOUNOUA Co-committed-by: Yanis DAHMANE-BOUNOUA --- Documentation/http.puml | 2 +- Documentation/php.puml | 8 ++ public/index.php | 81 +------------- src/Controller/EditorController.php | 14 ++- src/Controller/FrontController.php | 162 ++++++++++++++++++++++++++++ src/Controller/UserController.php | 16 +++ src/Views/home.twig | 13 +++ 7 files changed, 210 insertions(+), 86 deletions(-) create mode 100644 Documentation/php.puml create mode 100644 src/Controller/FrontController.php create mode 100644 src/Controller/UserController.php create mode 100644 src/Views/home.twig diff --git a/Documentation/http.puml b/Documentation/http.puml index b41135d..9fbe606 100644 --- a/Documentation/http.puml +++ b/Documentation/http.puml @@ -35,7 +35,7 @@ class ViewHttpResponse extends HttpResponse { - arguments: array - kind: int - + __construct(kind: int, file: string, arguments: array, code: int = HttpCodes::OK) + - __construct(kind: int, file: string, arguments: array, code: int = HttpCodes::OK) + getViewKind(): int + getFile(): string + getArguments(): array diff --git a/Documentation/php.puml b/Documentation/php.puml new file mode 100644 index 0000000..8641900 --- /dev/null +++ b/Documentation/php.puml @@ -0,0 +1,8 @@ +@startuml + +class FrontController { + - router : AltoRouter + +} + +@enduml \ No newline at end of file diff --git a/public/index.php b/public/index.php index edc4cb3..f898d7f 100644 --- a/public/index.php +++ b/public/index.php @@ -4,83 +4,10 @@ require "../vendor/autoload.php"; require "../config.php"; require "../sql/database.php"; require "utils.php"; +require "../src/react-display.php"; -use App\Connexion; -use App\Controller\EditorController; -use App\Controller\SampleFormController; -use App\Gateway\FormResultGateway; -use App\Gateway\TacticInfoGateway; -use App\Http\JsonHttpResponse; -use App\Http\ViewHttpResponse; -use App\Model\TacticModel; -use Twig\Loader\FilesystemLoader; -use App\Gateway\AuthGateway; -use App\Controller\AuthController; -use App\Validation\ValidationFail; -use App\Controller\ErrorController; -use App\Controller\VisualizerController; - -$loader = new FilesystemLoader('../src/Views/'); -$twig = new \Twig\Environment($loader); +use App\Controller\FrontController; $basePath = get_public_path(); -$con = new Connexion(get_database()); - -// routes initialization -$router = new AltoRouter(); -$router->setBasePath($basePath); - -$sampleFormController = new SampleFormController(new FormResultGateway($con)); -$authGateway = new AuthGateway($con); -$authController = new \App\Controller\AuthController(new \App\Model\AuthModel($authGateway)); -$editorController = new EditorController(new TacticModel(new TacticInfoGateway($con))); -$visualizerController = new VisualizerController(new TacticModel(new TacticInfoGateway($con))); - - -$router->map("GET", "/", fn() => $sampleFormController->displayFormReact()); -$router->map("POST", "/submit", fn() => $sampleFormController->submitFormReact($_POST)); -$router->map("GET", "/twig", fn() => $sampleFormController->displayFormTwig()); -$router->map("POST", "/submit-twig", fn() => $sampleFormController->submitFormTwig($_POST)); -$router->map("GET", "/register", fn() => $authController->displayRegister()); -$router->map("POST", "/register", fn() => $authController->confirmRegister($_POST)); -$router->map("GET", "/login", fn() => $authController->displayLogin()); -$router->map("POST", "/login", fn() => $authController->confirmLogin($_POST)); -$router->map("GET", "/tactic/new", fn() => $editorController->makeNew()); -$router->map("GET", "/tactic/[i:id]/edit", fn(int $id) => $editorController->openEditorFor($id)); -$router->map("GET", "/tactic/[i:id]", fn(int $id) => $visualizerController->openVisualizer($id)); - -$match = $router->match(); - -if ($match == null) { - http_response_code(404); - ErrorController::displayFailures([ValidationFail::notFound("Cette page n'existe pas")], $twig); - return; -} - -$response = call_user_func_array($match['target'], $match['params']); - -http_response_code($response->getCode()); - -if ($response instanceof ViewHttpResponse) { - $file = $response->getFile(); - $args = $response->getArguments(); - - switch ($response->getViewKind()) { - case ViewHttpResponse::REACT_VIEW: - send_react_front($file, $args); - break; - case ViewHttpResponse::TWIG_VIEW: - try { - $twig->display($file, $args); - } catch (\Twig\Error\RuntimeError|\Twig\Error\SyntaxError $e) { - http_response_code(500); - echo "There was an error rendering your view, please refer to an administrator.\nlogs date: " . date("YYYD, d M Y H:i:s"); - throw $e; - } - break; - } - -} elseif ($response instanceof JsonHttpResponse) { - header('Content-type: application/json'); - echo $response->getJson(); -} +$frontController = new FrontController($basePath); +$frontController->run(); diff --git a/src/Controller/EditorController.php b/src/Controller/EditorController.php index ed270d1..eb07184 100644 --- a/src/Controller/EditorController.php +++ b/src/Controller/EditorController.php @@ -2,9 +2,10 @@ namespace App\Controller; +use App\Connexion; use App\Data\TacticInfo; +use App\Gateway\TacticInfoGateway; use App\Http\HttpCodes; -use App\Http\HttpRequest; use App\Http\HttpResponse; use App\Http\JsonHttpResponse; use App\Http\ViewHttpResponse; @@ -13,18 +14,15 @@ use App\Model\TacticModel; class EditorController { private TacticModel $model; - /** - * @param TacticModel $model - */ - public function __construct(TacticModel $model) { - $this->model = $model; + public function __construct() { + $this->model = new TacticModel(new TacticInfoGateway(new Connexion(get_database()))); } private function openEditor(TacticInfo $tactic): HttpResponse { return ViewHttpResponse::react("views/Editor.tsx", ["name" => $tactic->getName(), "id" => $tactic->getId()]); } - public function makeNew(): HttpResponse { + public function create(): HttpResponse { $tactic = $this->model->makeNewDefault(); return $this->openEditor($tactic); } @@ -34,7 +32,7 @@ class EditorController { * @param int $id the targeted tactic identifier * @return HttpResponse */ - public function openEditorFor(int $id): HttpResponse { + public function edit(int $id): HttpResponse { $tactic = $this->model->get($id); if ($tactic == null) { diff --git a/src/Controller/FrontController.php b/src/Controller/FrontController.php new file mode 100644 index 0000000..66d5d4f --- /dev/null +++ b/src/Controller/FrontController.php @@ -0,0 +1,162 @@ +router = $this->createRouter($basePath); + $this->initializeRouterMap(); + } + + /** + * Main behavior of the FrontController + * + * @return void + */ + public function run(): void { + $match = $this->router->match(); + if ($match != null) { + $this->handleMatch($match); + } else { + $this->displayViewByKind(ViewHttpResponse::twig("error.html.twig", [], HttpCodes::NOT_FOUND)); + } + } + + /** + * Create a new instance of an AltoRouter + * + * @param string $basePath + * @return AltoRouter + */ + public function createRouter(string $basePath): AltoRouter { + $router = new AltoRouter(); + $router->setBasePath($basePath); + return $router; + } + + /** + * Initialize project's routes + * + * @return void + */ + private function initializeRouterMap(): void { + $this->router->map("GET", "/", "UserController"); + $this->router->map("GET", "/[a:action]?", "UserController"); + $this->router->map("GET", "/tactic/[a:action]/[i:idTactic]?", "EditorController"); + } + + /** + * @param array $match + * @return void + */ + private function handleMatch(array $match): void { + $tag = $match['target']; + + $action = $this->getAction($match); + $params = $match["params"]; + unset($params['action']); + $this->handleResponseByType($this->tryToCall($tag, $action, array_values($params))); + } + + /** + * @param string $controller + * @param string $action + * @param array $params + * @return HttpResponse + */ + private function tryToCall(string $controller, string $action, array $params): HttpResponse { + $controller = $this->getController($controller); + try { + if (is_callable([$controller, $action])) { + return call_user_func_array([$controller, $action], $params); + } else { + return ViewHttpResponse::twig("error.html.twig", [], HttpCodes::NOT_FOUND); + } + } catch (Exception $e) { + return ViewHttpResponse::twig("error.html.twig", [], HttpCodes::NOT_FOUND); + } + } + + /** + * Get the right method to call to do an action + * + * @param array $match + * @return string + */ + private function getAction(array $match): string { + if (isset($match["params"]["action"])) { + return $match["params"]["action"]; + } + return "default"; + } + + /** + * Initialize the right controller by the user's role + * + * @param string $controller + * @return mixed + */ + private function getController(string $controller) { + $namespace = "\\App\\Controller\\"; + $controller = $namespace . $controller; + return new $controller(); + } + + /** + * Redirect the return by the response's type + * + * @param HttpResponse $response + * @return void + */ + private function handleResponseByType(HttpResponse $response): void { + http_response_code($response->getCode()); + if ($response instanceof ViewHttpResponse) { + $this->displayViewByKind($response); + } elseif ($response instanceof JsonHttpResponse) { + header('Content-type: application/json'); + echo $response->getJson(); + } + } + + /** + * Use the right method to display the response + * + * @param ViewHttpResponse $response + * @return void + */ + private function displayViewByKind(ViewHttpResponse $response): void { + $file = $response->getFile(); + $args = $response->getArguments(); + + switch ($response->getViewKind()) { + case ViewHttpResponse::REACT_VIEW: + send_react_front($file, $args); + break; + case ViewHttpResponse::TWIG_VIEW: + try { + $loader = new FilesystemLoader('../src/Views/'); + $twig = new Environment($loader); + $twig->display($file, $args); + } catch (RuntimeError|SyntaxError|LoaderError $e) { + http_response_code(500); + echo "There was an error rendering your view, please refer to an administrator.\nlogs date: " . date("YYYD, d M Y H:i:s"); + throw $e; + } + break; + } + } +} diff --git a/src/Controller/UserController.php b/src/Controller/UserController.php new file mode 100644 index 0000000..930cd67 --- /dev/null +++ b/src/Controller/UserController.php @@ -0,0 +1,16 @@ + + + + + + + Document + + +

Page Home à faire

+ + \ No newline at end of file From 53584a518e44200be3f47bfc99cfc6191fab300f Mon Sep 17 00:00:00 2001 From: Override-6 Date: Tue, 21 Nov 2023 22:57:36 +0100 Subject: [PATCH 2/8] add salva-like routes and controller architecture --- src/Controller/ErrorController.php | 26 -------- src/Controller/FrontController.php | 2 +- src/Controller/SampleFormController.php | 64 ------------------- src/Controller/{ => Sub}/AuthController.php | 4 +- src/Controller/{ => Sub}/EditorController.php | 4 +- .../{ => Sub}/VisualizerController.php | 5 +- src/Controller/UserController.php | 36 ++++++++++- src/Model/AuthModel.php | 1 - 8 files changed, 40 insertions(+), 102 deletions(-) delete mode 100644 src/Controller/ErrorController.php delete mode 100644 src/Controller/SampleFormController.php rename src/Controller/{ => Sub}/AuthController.php (97%) rename src/Controller/{ => Sub}/EditorController.php (93%) rename src/Controller/{ => Sub}/VisualizerController.php (88%) diff --git a/src/Controller/ErrorController.php b/src/Controller/ErrorController.php deleted file mode 100644 index 7fc5239..0000000 --- a/src/Controller/ErrorController.php +++ /dev/null @@ -1,26 +0,0 @@ -display("error.html.twig", ['failures' => $failures]); - } catch (LoaderError|RuntimeError|SyntaxError $e) { - echo "Twig error: $e"; - } - } -} diff --git a/src/Controller/FrontController.php b/src/Controller/FrontController.php index 66d5d4f..0600a01 100644 --- a/src/Controller/FrontController.php +++ b/src/Controller/FrontController.php @@ -56,7 +56,7 @@ class FrontController { private function initializeRouterMap(): void { $this->router->map("GET", "/", "UserController"); $this->router->map("GET", "/[a:action]?", "UserController"); - $this->router->map("GET", "/tactic/[a:action]/[i:idTactic]?", "EditorController"); + $this->router->map("GET", "/tactic/[a:action]/[i:idTactic]?", "UserController"); } /** diff --git a/src/Controller/SampleFormController.php b/src/Controller/SampleFormController.php deleted file mode 100644 index 773a4db..0000000 --- a/src/Controller/SampleFormController.php +++ /dev/null @@ -1,64 +0,0 @@ -gateway = $gateway; - } - - - public function displayFormReact(): HttpResponse { - return ViewHttpResponse::react("views/SampleForm.tsx", []); - } - - public function displayFormTwig(): HttpResponse { - return ViewHttpResponse::twig('sample_form.html.twig', []); - } - - /** - * @param array $form - * @param callable(array>): ViewHttpResponse $response - * @return HttpResponse - */ - private function submitForm(array $form, callable $response): HttpResponse { - return Control::runCheckedFrom($form, [ - "name" => [Validators::lenBetween(0, 32), Validators::name("Le nom ne peut contenir que des lettres, des chiffres et des accents")], - "description" => [Validators::lenBetween(0, 512)], - ], function (HttpRequest $req) use ($response) { - $description = htmlspecialchars($req["description"]); - $this->gateway->insert($req["name"], $description); - $results = ["results" => $this->gateway->listResults()]; - return call_user_func_array($response, [$results]); - }, false); - } - - /** - * @param array $form - * @return HttpResponse - */ - public function submitFormTwig(array $form): HttpResponse { - return $this->submitForm($form, fn(array $results) => ViewHttpResponse::twig('display_results.html.twig', $results)); - } - - /** - * @param array $form - * @return HttpResponse - */ - public function submitFormReact(array $form): HttpResponse { - return $this->submitForm($form, fn(array $results) => ViewHttpResponse::react('views/DisplayResults.tsx', $results)); - } -} diff --git a/src/Controller/AuthController.php b/src/Controller/Sub/AuthController.php similarity index 97% rename from src/Controller/AuthController.php rename to src/Controller/Sub/AuthController.php index e42c27d..a78b02e 100644 --- a/src/Controller/AuthController.php +++ b/src/Controller/Sub/AuthController.php @@ -1,8 +1,7 @@ $tactic->getName(), "id" => $tactic->getId()]); } - public function create(): HttpResponse { + public function createNew(): HttpResponse { $tactic = $this->model->makeNewDefault(); return $this->openEditor($tactic); } diff --git a/src/Controller/VisualizerController.php b/src/Controller/Sub/VisualizerController.php similarity index 88% rename from src/Controller/VisualizerController.php rename to src/Controller/Sub/VisualizerController.php index 3c8b55e..e3b5663 100644 --- a/src/Controller/VisualizerController.php +++ b/src/Controller/Sub/VisualizerController.php @@ -1,6 +1,6 @@ tacticModel = $tacticModel; } - public function openVisualizer(int $id): HttpResponse { + public function visualize(int $id): HttpResponse { $tactic = $this->tacticModel->get($id); if ($tactic == null) { @@ -27,6 +27,5 @@ class VisualizerController { } return ViewHttpResponse::react("views/Visualizer.tsx", ["name" => $tactic->getName()]); - } } diff --git a/src/Controller/UserController.php b/src/Controller/UserController.php index 930cd67..2a95bcc 100644 --- a/src/Controller/UserController.php +++ b/src/Controller/UserController.php @@ -2,15 +2,47 @@ namespace App\Controller; +use App\Connexion; +use App\Gateway\AuthGateway; +use App\Gateway\TacticInfoGateway; use App\Http\HttpResponse; use App\Http\ViewHttpResponse; +use App\Model\AuthModel; +use App\Model\TacticModel; class UserController { public function home(): HttpResponse { return ViewHttpResponse::twig("home.twig", []); } - public function default(): HttpResponse { - return self::home(); + public function register(): HttpResponse { + $model = new AuthModel(new AuthGateway(new Connexion(get_database()))); + if ($_SERVER['REQUEST_METHOD'] === 'GET') { + return (new Sub\AuthController($model))->displayRegister(); + } + return (new Sub\AuthController($model))->confirmRegister($_POST); + } + + public function login(): HttpResponse { + $model = new AuthModel(new AuthGateway(new Connexion(get_database()))); + if ($_SERVER['REQUEST_METHOD'] === 'GET') { + return (new Sub\AuthController($model))->displayLogin(); + } + return (new Sub\AuthController($model))->confirmLogin($_POST); + } + + public function open(int $id): HttpResponse { + $model = new TacticModel(new TacticInfoGateway(new Connexion(get_database()))); + return (new Sub\VisualizerController($model))->visualize($id); + } + + public function edit(int $id): HttpResponse { + $model = new TacticModel(new TacticInfoGateway(new Connexion(get_database()))); + return (new Sub\EditorController($model))->edit($id); + } + + public function create(): HttpResponse { + $model = new TacticModel(new TacticInfoGateway(new Connexion(get_database()))); + return (new Sub\EditorController($model))->createNew(); } } diff --git a/src/Model/AuthModel.php b/src/Model/AuthModel.php index 45b63e4..312cca8 100644 --- a/src/Model/AuthModel.php +++ b/src/Model/AuthModel.php @@ -2,7 +2,6 @@ namespace App\Model; -use App\Controller\AuthController; use App\Gateway\AuthGateway; use App\Validation\FieldValidationFail; use App\Validation\ValidationFail; From 14612226cd1f9680776ad2de295a6e0afbc88b8f Mon Sep 17 00:00:00 2001 From: "vivien.dufour" Date: Wed, 22 Nov 2023 08:38:58 +0100 Subject: [PATCH 3/8] fix on routes --- src/Controller/FrontController.php | 49 ++++++++++++++----------- src/Controller/Sub/EditorController.php | 4 +- 2 files changed, 30 insertions(+), 23 deletions(-) diff --git a/src/Controller/FrontController.php b/src/Controller/FrontController.php index 0600a01..58e5d9d 100644 --- a/src/Controller/FrontController.php +++ b/src/Controller/FrontController.php @@ -14,10 +14,12 @@ use Twig\Error\RuntimeError; use Twig\Error\SyntaxError; use Twig\Loader\FilesystemLoader; -class FrontController { +class FrontController +{ private AltoRouter $router; - public function __construct(string $basePath) { + public function __construct(string $basePath) + { $this->router = $this->createRouter($basePath); $this->initializeRouterMap(); } @@ -27,7 +29,8 @@ class FrontController { * * @return void */ - public function run(): void { + public function run(): void + { $match = $this->router->match(); if ($match != null) { $this->handleMatch($match); @@ -42,7 +45,8 @@ class FrontController { * @param string $basePath * @return AltoRouter */ - public function createRouter(string $basePath): AltoRouter { + public function createRouter(string $basePath): AltoRouter + { $router = new AltoRouter(); $router->setBasePath($basePath); return $router; @@ -53,17 +57,19 @@ class FrontController { * * @return void */ - private function initializeRouterMap(): void { + private function initializeRouterMap(): void + { $this->router->map("GET", "/", "UserController"); - $this->router->map("GET", "/[a:action]?", "UserController"); - $this->router->map("GET", "/tactic/[a:action]/[i:idTactic]?", "UserController"); + $this->router->map("GET|POST", "/[a:action]?", "UserController"); + $this->router->map("GET|POST", "/tactic/[a:action]/[i:idTactic]?", "UserController"); } /** * @param array $match * @return void */ - private function handleMatch(array $match): void { + private function handleMatch(array $match): void + { $tag = $match['target']; $action = $this->getAction($match); @@ -78,15 +84,12 @@ class FrontController { * @param array $params * @return HttpResponse */ - private function tryToCall(string $controller, string $action, array $params): HttpResponse { + private function tryToCall(string $controller, string $action, array $params): HttpResponse + { $controller = $this->getController($controller); - try { - if (is_callable([$controller, $action])) { - return call_user_func_array([$controller, $action], $params); - } else { - return ViewHttpResponse::twig("error.html.twig", [], HttpCodes::NOT_FOUND); - } - } catch (Exception $e) { + if (is_callable([$controller, $action])) { + return call_user_func_array([$controller, $action], $params); + } else { return ViewHttpResponse::twig("error.html.twig", [], HttpCodes::NOT_FOUND); } } @@ -97,7 +100,8 @@ class FrontController { * @param array $match * @return string */ - private function getAction(array $match): string { + private function getAction(array $match): string + { if (isset($match["params"]["action"])) { return $match["params"]["action"]; } @@ -110,7 +114,8 @@ class FrontController { * @param string $controller * @return mixed */ - private function getController(string $controller) { + private function getController(string $controller) + { $namespace = "\\App\\Controller\\"; $controller = $namespace . $controller; return new $controller(); @@ -122,7 +127,8 @@ class FrontController { * @param HttpResponse $response * @return void */ - private function handleResponseByType(HttpResponse $response): void { + private function handleResponseByType(HttpResponse $response): void + { http_response_code($response->getCode()); if ($response instanceof ViewHttpResponse) { $this->displayViewByKind($response); @@ -138,7 +144,8 @@ class FrontController { * @param ViewHttpResponse $response * @return void */ - private function displayViewByKind(ViewHttpResponse $response): void { + private function displayViewByKind(ViewHttpResponse $response): void + { $file = $response->getFile(); $args = $response->getArguments(); @@ -151,7 +158,7 @@ class FrontController { $loader = new FilesystemLoader('../src/Views/'); $twig = new Environment($loader); $twig->display($file, $args); - } catch (RuntimeError|SyntaxError|LoaderError $e) { + } catch (RuntimeError | SyntaxError | LoaderError $e) { http_response_code(500); echo "There was an error rendering your view, please refer to an administrator.\nlogs date: " . date("YYYD, d M Y H:i:s"); throw $e; diff --git a/src/Controller/Sub/EditorController.php b/src/Controller/Sub/EditorController.php index def6ac9..ce7a0a3 100644 --- a/src/Controller/Sub/EditorController.php +++ b/src/Controller/Sub/EditorController.php @@ -14,8 +14,8 @@ use App\Model\TacticModel; class EditorController { private TacticModel $model; - public function __construct() { - $this->model = new TacticModel(new TacticInfoGateway(new Connexion(get_database()))); + public function __construct(TacticModel $model) { + $this->model = $model; } private function openEditor(TacticInfo $tactic): HttpResponse { From 3d7eb7bbb10eb1a0012ed531dc19f760addf7af0 Mon Sep 17 00:00:00 2001 From: Override-6 Date: Tue, 21 Nov 2023 19:40:30 +0100 Subject: [PATCH 4/8] add session handle --- Documentation/models.puml | 8 +-- public/index.php | 5 +- sql/setup-tables.sql | 24 +++++---- src/Controller/FrontController.php | 52 +++++++++---------- src/Controller/Sub/AuthController.php | 24 ++++----- src/Controller/UserController.php | 6 +-- src/Data/Account.php | 73 ++++----------------------- src/Gateway/AccountGateway.php | 56 ++++++++++++++++++++ src/Gateway/AuthGateway.php | 47 ----------------- src/Model/AuthModel.php | 66 ++++++++++++------------ src/Session/MutableSessionHandle.php | 9 ++++ src/Session/PhpSessionHandle.php | 24 +++++++++ src/Session/SessionHandle.php | 11 ++++ 13 files changed, 206 insertions(+), 199 deletions(-) create mode 100644 src/Gateway/AccountGateway.php delete mode 100644 src/Gateway/AuthGateway.php create mode 100644 src/Session/MutableSessionHandle.php create mode 100644 src/Session/PhpSessionHandle.php create mode 100644 src/Session/SessionHandle.php diff --git a/Documentation/models.puml b/Documentation/models.puml index d95343c..82037bc 100755 --- a/Documentation/models.puml +++ b/Documentation/models.puml @@ -81,7 +81,7 @@ AuthController --> "- model" AuthModel class AuthModel{ + register(username : string, password : string, confirmPassword : string, email : string): array - + getUserFields(email : string):array + + getAccount(email : string):array + login(email : string, password : string) } AuthModel --> "- gateway" AuthGateway @@ -89,9 +89,9 @@ AuthModel --> "- gateway" AuthGateway class AuthGateway{ -con : Connection - + mailExist(email : string) : bool + + mailExists(email : string) : bool + insertAccount(username : string, hash : string, email : string) - + getUserHash(email : string):string - + getUserFields (email : string): array + + getHash(email : string):string + + getAccount (email : string): array } @enduml \ No newline at end of file diff --git a/public/index.php b/public/index.php index f898d7f..ce1df33 100644 --- a/public/index.php +++ b/public/index.php @@ -1,5 +1,6 @@ run(); + +$frontController->run(PhpSessionHandle::init()); diff --git a/sql/setup-tables.sql b/sql/setup-tables.sql index 108b62a..a289336 100644 --- a/sql/setup-tables.sql +++ b/sql/setup-tables.sql @@ -1,18 +1,24 @@ - -- drop tables here DROP TABLE IF EXISTS FormEntries; -DROP TABLE IF EXISTS AccountUser; +DROP TABLE IF EXISTS Account; DROP TABLE IF EXISTS TacticInfo; -CREATE TABLE FormEntries(name varchar, description varchar); -CREATE TABLE AccountUser( +CREATE TABLE FormEntries +( + name varchar, + description varchar +); +CREATE TABLE Account +( username varchar, - hash varchar, - email varchar unique + hash varchar, + email varchar unique, + token varchar(256) NOT NULL UNIQUE ); -CREATE TABLE TacticInfo( - id integer PRIMARY KEY AUTOINCREMENT, - name varchar, +CREATE TABLE TacticInfo +( + id integer PRIMARY KEY AUTOINCREMENT, + name varchar, creation_date timestamp DEFAULT CURRENT_TIMESTAMP ); \ No newline at end of file diff --git a/src/Controller/FrontController.php b/src/Controller/FrontController.php index 58e5d9d..d5c6417 100644 --- a/src/Controller/FrontController.php +++ b/src/Controller/FrontController.php @@ -7,6 +7,7 @@ use App\Http\HttpCodes; use App\Http\HttpResponse; use App\Http\JsonHttpResponse; use App\Http\ViewHttpResponse; +use App\Session\MutableSessionHandle; use Exception; use Twig\Environment; use Twig\Error\LoaderError; @@ -14,12 +15,10 @@ use Twig\Error\RuntimeError; use Twig\Error\SyntaxError; use Twig\Loader\FilesystemLoader; -class FrontController -{ +class FrontController { private AltoRouter $router; - public function __construct(string $basePath) - { + public function __construct(string $basePath) { $this->router = $this->createRouter($basePath); $this->initializeRouterMap(); } @@ -29,11 +28,10 @@ class FrontController * * @return void */ - public function run(): void - { + public function run(MutableSessionHandle $session): void { $match = $this->router->match(); if ($match != null) { - $this->handleMatch($match); + $this->handleMatch($match, $session); } else { $this->displayViewByKind(ViewHttpResponse::twig("error.html.twig", [], HttpCodes::NOT_FOUND)); } @@ -45,8 +43,7 @@ class FrontController * @param string $basePath * @return AltoRouter */ - public function createRouter(string $basePath): AltoRouter - { + public function createRouter(string $basePath): AltoRouter { $router = new AltoRouter(); $router->setBasePath($basePath); return $router; @@ -57,8 +54,7 @@ class FrontController * * @return void */ - private function initializeRouterMap(): void - { + private function initializeRouterMap(): void { $this->router->map("GET", "/", "UserController"); $this->router->map("GET|POST", "/[a:action]?", "UserController"); $this->router->map("GET|POST", "/tactic/[a:action]/[i:idTactic]?", "UserController"); @@ -68,28 +64,34 @@ class FrontController * @param array $match * @return void */ - private function handleMatch(array $match): void - { + private function handleMatch(array $match, MutableSessionHandle $session): void { $tag = $match['target']; $action = $this->getAction($match); $params = $match["params"]; unset($params['action']); - $this->handleResponseByType($this->tryToCall($tag, $action, array_values($params))); + $this->handleResponseByType($this->tryToCall($tag, $action, array_values($params), $session)); } /** * @param string $controller * @param string $action * @param array $params + * @param MutableSessionHandle $session * @return HttpResponse */ - private function tryToCall(string $controller, string $action, array $params): HttpResponse - { + + private function tryToCall(string $controller, string $action, array $params, MutableSessionHandle $session): HttpResponse { $controller = $this->getController($controller); - if (is_callable([$controller, $action])) { - return call_user_func_array([$controller, $action], $params); - } else { + try { + if (is_callable([$controller, $action])) { + // append the session as the last parameter of a controller function + $params[] = $session; + return call_user_func_array([$controller, $action], $params); + } else { + return ViewHttpResponse::twig("error.html.twig", [], HttpCodes::NOT_FOUND); + } + } catch (Exception $e) { return ViewHttpResponse::twig("error.html.twig", [], HttpCodes::NOT_FOUND); } } @@ -100,8 +102,7 @@ class FrontController * @param array $match * @return string */ - private function getAction(array $match): string - { + private function getAction(array $match): string { if (isset($match["params"]["action"])) { return $match["params"]["action"]; } @@ -114,8 +115,7 @@ class FrontController * @param string $controller * @return mixed */ - private function getController(string $controller) - { + private function getController(string $controller) { $namespace = "\\App\\Controller\\"; $controller = $namespace . $controller; return new $controller(); @@ -127,8 +127,7 @@ class FrontController * @param HttpResponse $response * @return void */ - private function handleResponseByType(HttpResponse $response): void - { + private function handleResponseByType(HttpResponse $response): void { http_response_code($response->getCode()); if ($response instanceof ViewHttpResponse) { $this->displayViewByKind($response); @@ -144,8 +143,7 @@ class FrontController * @param ViewHttpResponse $response * @return void */ - private function displayViewByKind(ViewHttpResponse $response): void - { + private function displayViewByKind(ViewHttpResponse $response): void { $file = $response->getFile(); $args = $response->getArguments(); diff --git a/src/Controller/Sub/AuthController.php b/src/Controller/Sub/AuthController.php index a78b02e..7a537f2 100644 --- a/src/Controller/Sub/AuthController.php +++ b/src/Controller/Sub/AuthController.php @@ -2,6 +2,7 @@ namespace App\Controller\Sub; +use App\Gateway\AccountGateway; use App\Http\HttpRequest; use App\Http\HttpResponse; use App\Http\ViewHttpResponse; @@ -49,17 +50,16 @@ class AuthController { "username" => [Validators::name(), Validators::lenBetween(2, 32)], "password" => [Validators::lenBetween(6, 256)], "confirmpassword" => [Validators::lenBetween(6, 256)], - "email" => [Validators::regex("/^\\S+@\\S+\\.\\S+$/"),Validators::lenBetween(5, 256)], + "email" => [Validators::regex("/^\\S+@\\S+\\.\\S+$/"), Validators::lenBetween(5, 256)], ]); if (!empty($fails)) { return $this->displayBadFields("display_register.html.twig", $fails); } - $fails = $this->model->register($request['username'], $request["password"], $request['confirmpassword'], $request['email']); - if (empty($fails)) { - $results = $this->model->getUserFields($request['email']); - return ViewHttpResponse::twig("display_auth_confirm.html.twig", ['username' => $results['username'], 'email' => $results['email']]); + $account = $this->model->register($request['username'], $request["password"], $request['confirmpassword'], $request['email'], $fails); + if (!empty($fails)) { + return $this->displayBadFields("display_register.html.twig", $fails); } - return $this->displayBadFields("display_register.html.twig", $fails); + return ViewHttpResponse::twig("display_auth_confirm.html.twig", ['username' => $account->getName(), 'email' => $account->getEmail()]); } @@ -75,18 +75,18 @@ class AuthController { $fails = []; $request = HttpRequest::from($request, $fails, [ "password" => [Validators::lenBetween(6, 256)], - "email" => [Validators::regex("/^\\S+@\\S+\\.\\S+$/"),Validators::lenBetween(5, 256)], + "email" => [Validators::regex("/^\\S+@\\S+\\.\\S+$/"), Validators::lenBetween(5, 256)], ]); if (!empty($fails)) { return $this->displayBadFields("display_login.html.twig", $fails); } - $fails = $this->model->login($request['email'], $request['password']); - if (empty($fails)) { - $results = $this->model->getUserFields($request['email']); - return ViewHttpResponse::twig("display_auth_confirm.html.twig", ['username' => $results['username'], 'email' => $results['email']]); + $account = $this->model->login($request['email'], $request['password'], $fails); + if (!empty($fails)) { + return $this->displayBadFields("display_login.html.twig", $fails); } - return $this->displayBadFields("display_login.html.twig", $fails); + + return ViewHttpResponse::twig("display_auth_confirm.html.twig", ['username' => $account->getName(), 'email' => $account->getEmail()]); } } diff --git a/src/Controller/UserController.php b/src/Controller/UserController.php index 2a95bcc..360c9a3 100644 --- a/src/Controller/UserController.php +++ b/src/Controller/UserController.php @@ -3,7 +3,7 @@ namespace App\Controller; use App\Connexion; -use App\Gateway\AuthGateway; +use App\Gateway\AccountGateway; use App\Gateway\TacticInfoGateway; use App\Http\HttpResponse; use App\Http\ViewHttpResponse; @@ -16,7 +16,7 @@ class UserController { } public function register(): HttpResponse { - $model = new AuthModel(new AuthGateway(new Connexion(get_database()))); + $model = new AuthModel(new AccountGateway(new Connexion(get_database()))); if ($_SERVER['REQUEST_METHOD'] === 'GET') { return (new Sub\AuthController($model))->displayRegister(); } @@ -24,7 +24,7 @@ class UserController { } public function login(): HttpResponse { - $model = new AuthModel(new AuthGateway(new Connexion(get_database()))); + $model = new AuthModel(new AccountGateway(new Connexion(get_database()))); if ($_SERVER['REQUEST_METHOD'] === 'GET') { return (new Sub\AuthController($model))->displayLogin(); } diff --git a/src/Data/Account.php b/src/Data/Account.php index 2a21bf1..8b58e1c 100755 --- a/src/Data/Account.php +++ b/src/Data/Account.php @@ -4,8 +4,6 @@ namespace App\Data; use http\Exception\InvalidArgumentException; -const PHONE_NUMBER_REGEXP = "/^\\+[0-9]+$/"; - /** * Base class of a user account. * Contains the private information that we don't want @@ -16,27 +14,16 @@ class Account { * @var string $email account's mail address */ private string $email; - /** - * @var string account's phone number. - * its format is specified by the {@link PHONE_NUMBER_REGEXP} constant - * - */ - private string $phoneNumber; /** - * @var AccountUser account's public and shared information + * @var string string token */ - private AccountUser $user; + private string $token; /** - * @var Team[] account's teams + * @var string the account's username */ - private array $teams; - - /** - * @var int account's unique identifier - */ - private int $id; + private string $name; /** @@ -46,60 +33,22 @@ class Account { * @param Team[] $teams * @param int $id */ - public function __construct(string $email, string $phoneNumber, AccountUser $user, array $teams, int $id) { + public function __construct(string $email, string $name, string $token) { $this->email = $email; - $this->phoneNumber = $phoneNumber; - $this->user = $user; - $this->teams = $teams; - $this->id = $id; + $this->name = $name; + $this->token = $token; } - /** - * @return string - */ public function getEmail(): string { return $this->email; } - /** - * @param string $email - */ - public function setEmail(string $email): void { - if (!filter_var($email, FILTER_VALIDATE_EMAIL)) { - throw new InvalidArgumentException("Invalid mail address"); - } - $this->email = $email; - } - - /** - * @return string - */ - public function getPhoneNumber(): string { - return $this->phoneNumber; + public function getToken(): string { + return $this->token; } - /** - * @param string $phoneNumber - */ - public function setPhoneNumber(string $phoneNumber): void { - if (!preg_match(PHONE_NUMBER_REGEXP, $phoneNumber)) { - throw new InvalidArgumentException("Invalid phone number"); - } - $this->phoneNumber = $phoneNumber; + public function getName(): string { + return $this->name; } - public function getId(): int { - return $this->id; - } - - /** - * @return Team[] - */ - public function getTeams(): array { - return $this->teams; - } - - public function getUser(): AccountUser { - return $this->user; - } } diff --git a/src/Gateway/AccountGateway.php b/src/Gateway/AccountGateway.php new file mode 100644 index 0000000..a4686d6 --- /dev/null +++ b/src/Gateway/AccountGateway.php @@ -0,0 +1,56 @@ +con = $con; + } + + + public function exists(string $email): bool { + return $this->getAccount($email) != null; + } + + + public function insertAccount(Account $account, string $hash): void { + $this->con->exec("INSERT INTO Account VALUES (:username,:hash,:email)", [ + ':username' => [$account->getName(), PDO::PARAM_STR], + ':hash' => [$hash, PDO::PARAM_STR], + ':email' => [$account->getEmail(), PDO::PARAM_STR], + ':token' => [$account->getToken(), PDO::PARAM_STR] + ]); + } + + public function getHash(string $email): string { + $results = $this->con->fetch("SELECT hash FROM Account WHERE email = :email", [ + ':email' => [$email, PDO::PARAM_STR] + ]); + return $results[0]['hash']; + } + + + /** + * @param string $email + * @return Account|null + */ + public function getAccount(string $email): ?Account { + $results = $this->con->fetch("SELECT username,email,token FROM Account WHERE email = :email", [':email' => [$email, PDO::PARAM_STR]]); + if (empty($results)) + return null; + + $acc = $results[0]; + return new Account($acc["email"], $acc["name"], $acc["token"]); + } + + +} diff --git a/src/Gateway/AuthGateway.php b/src/Gateway/AuthGateway.php deleted file mode 100644 index 5acc01c..0000000 --- a/src/Gateway/AuthGateway.php +++ /dev/null @@ -1,47 +0,0 @@ -con = $con; - } - - - public function mailExist(string $email): bool { - return $this->getUserFields($email) != null; - } - - - public function insertAccount(string $username, string $hash, string $email): void { - $this->con->exec("INSERT INTO AccountUser VALUES (:username,:hash,:email)", [':username' => [$username, PDO::PARAM_STR],':hash' => [$hash, PDO::PARAM_STR],':email' => [$email, PDO::PARAM_STR]]); - } - - public function getUserHash(string $email): string { - $results = $this->con->fetch("SELECT hash FROM AccountUser WHERE email = :email", [':email' => [$email, PDO::PARAM_STR]]); - return $results[0]['hash']; - } - - - /** - * @param string $email - * @return array|null - */ - public function getUserFields(string $email): ?array { - $results = $this->con->fetch("SELECT username,email FROM AccountUser WHERE email = :email", [':email' => [$email, PDO::PARAM_STR]]); - $firstRow = $results[0] ?? null; - return $firstRow; - } - - - - -} diff --git a/src/Model/AuthModel.php b/src/Model/AuthModel.php index 312cca8..b786c2e 100644 --- a/src/Model/AuthModel.php +++ b/src/Model/AuthModel.php @@ -2,16 +2,19 @@ namespace App\Model; -use App\Gateway\AuthGateway; +use App\Controller\AuthController; +use App\Data\Account; +use App\Gateway\AccountGateway; use App\Validation\FieldValidationFail; use App\Validation\ValidationFail; class AuthModel { - private AuthGateway $gateway; + private AccountGateway $gateway; + /** - * @param AuthGateway $gateway + * @param AccountGateway $gateway */ - public function __construct(AuthGateway $gateway) { + public function __construct(AccountGateway $gateway) { $this->gateway = $gateway; } @@ -21,59 +24,54 @@ class AuthModel { * @param string $password * @param string $confirmPassword * @param string $email - * @return ValidationFail[] + * @param ValidationFail[] $failures + * @return Account|null the registered account or null if failures occurred */ - public function register(string $username, string $password, string $confirmPassword, string $email): array { - $errors = []; + public function register(string $username, string $password, string $confirmPassword, string $email, array &$failures): ?Account { if ($password != $confirmPassword) { - $errors[] = new FieldValidationFail("confirmpassword", "password and password confirmation are not equals"); + $failures[] = new FieldValidationFail("confirmpassword", "password and password confirmation are not equals"); } - if ($this->gateway->mailExist($email)) { - $errors[] = new FieldValidationFail("email", "email already exist"); + if ($this->gateway->exists($email)) { + $failures[] = new FieldValidationFail("email", "email already exist"); } - if(empty($errors)) { - $hash = password_hash($password, PASSWORD_DEFAULT); - $this->gateway->insertAccount($username, $hash, $email); + if (!empty($errors)) { + return null; } - return $errors; - } + $hash = password_hash($password, PASSWORD_DEFAULT); - /** - * @param string $email - * @return array|null - */ - public function getUserFields(string $email): ?array { - return $this->gateway->getUserFields($email); + $account = new Account($email, $username, $this->generateToken()); + $this->gateway->insertAccount($account, $hash); + return $account; } + private function generateToken(): string { + return base64_encode(random_bytes(64)); + } /** * @param string $email * @param string $password - * @return ValidationFail[] $errors + * @param ValidationFail[] $failures + * @return Account|null the authenticated account or null if failures occurred */ - public function login(string $email, string $password): array { - $errors = []; - - if (!$this->gateway->mailExist($email)) { - $errors[] = new FieldValidationFail("email", "email doesnt exists"); - return $errors; + public function login(string $email, string $password, array &$failures): ?Account { + if (!$this->gateway->exists($email)) { + $failures = new FieldValidationFail("email", "email doesnt exists"); + return null; } - $hash = $this->gateway->getUserHash($email); + $hash = $this->gateway->getHash($email); if (!password_verify($password, $hash)) { - $errors[] = new FieldValidationFail("password", "invalid password"); + $failures = new FieldValidationFail("password", "invalid password"); + return null; } - return $errors; + return $this->gateway->getAccount($email); } - - - } diff --git a/src/Session/MutableSessionHandle.php b/src/Session/MutableSessionHandle.php new file mode 100644 index 0000000..a7822a3 --- /dev/null +++ b/src/Session/MutableSessionHandle.php @@ -0,0 +1,9 @@ + Date: Wed, 22 Nov 2023 01:58:50 +0100 Subject: [PATCH 5/8] integrate sessions for tactics --- sql/.guard | 0 sql/setup-tables.sql | 22 ++++--- src/Controller/FrontController.php | 66 ++++++++++++++------- src/Controller/Sub/AuthController.php | 11 +++- src/Controller/Sub/EditorController.php | 18 ++++-- src/Controller/Sub/VisualizerController.php | 11 +++- src/Controller/UserController.php | 33 +++-------- src/Controller/VisitorController.php | 30 ++++++++++ src/Data/Account.php | 17 ++++-- src/Data/TacticInfo.php | 13 +++- src/Gateway/AccountGateway.php | 15 ++--- src/Gateway/FormResultGateway.php | 35 ----------- src/Gateway/TacticInfoGateway.php | 15 +++-- src/Http/HttpCodes.php | 4 ++ src/Http/HttpResponse.php | 25 +++++++- src/Http/JsonHttpResponse.php | 2 +- src/Http/ViewHttpResponse.php | 2 +- src/Model/AuthModel.php | 13 ++-- src/Model/TacticModel.php | 8 +-- src/Session/MutableSessionHandle.php | 1 + src/Validation/ValidationFail.php | 2 +- src/Validator/TacticValidator.php | 22 +++++++ src/Views/error.html.twig | 2 +- 23 files changed, 231 insertions(+), 136 deletions(-) delete mode 100644 sql/.guard create mode 100644 src/Controller/VisitorController.php delete mode 100644 src/Gateway/FormResultGateway.php create mode 100644 src/Validator/TacticValidator.php diff --git a/sql/.guard b/sql/.guard deleted file mode 100644 index e69de29..0000000 diff --git a/sql/setup-tables.sql b/sql/setup-tables.sql index a289336..457803c 100644 --- a/sql/setup-tables.sql +++ b/sql/setup-tables.sql @@ -5,20 +5,24 @@ DROP TABLE IF EXISTS TacticInfo; CREATE TABLE FormEntries ( - name varchar, - description varchar + name varchar NOT NULL, + description varchar NOT NULL ); CREATE TABLE Account ( - username varchar, - hash varchar, - email varchar unique, - token varchar(256) NOT NULL UNIQUE + id integer PRIMARY KEY AUTOINCREMENT, + email varchar UNIQUE NOT NULL, + username varchar NOT NULL, + token varchar UNIQUE NOT NULL, + hash varchar NOT NULL ); CREATE TABLE TacticInfo ( id integer PRIMARY KEY AUTOINCREMENT, - name varchar, - creation_date timestamp DEFAULT CURRENT_TIMESTAMP -); \ No newline at end of file + name varchar NOT NULL, + creation_date timestamp DEFAULT CURRENT_TIMESTAMP NOT NULL, + owner integer NOT NULL, + FOREIGN KEY (owner) REFERENCES Account +); + diff --git a/src/Controller/FrontController.php b/src/Controller/FrontController.php index d5c6417..3d73d80 100644 --- a/src/Controller/FrontController.php +++ b/src/Controller/FrontController.php @@ -8,7 +8,7 @@ use App\Http\HttpResponse; use App\Http\JsonHttpResponse; use App\Http\ViewHttpResponse; use App\Session\MutableSessionHandle; -use Exception; +use App\Validation\ValidationFail; use Twig\Environment; use Twig\Error\LoaderError; use Twig\Error\RuntimeError; @@ -17,23 +17,33 @@ use Twig\Loader\FilesystemLoader; class FrontController { private AltoRouter $router; + private string $basePath; + + private const USER_CONTROLLER = "UserController"; + private const VISITOR_CONTROLLER = "VisitorController"; + public function __construct(string $basePath) { $this->router = $this->createRouter($basePath); $this->initializeRouterMap(); + $this->basePath = $basePath; } /** - * Main behavior of the FrontController - * + * @param MutableSessionHandle $session * @return void + * @throws LoaderError + * @throws RuntimeError + * @throws SyntaxError */ public function run(MutableSessionHandle $session): void { $match = $this->router->match(); - if ($match != null) { + if ($match) { $this->handleMatch($match, $session); } else { - $this->displayViewByKind(ViewHttpResponse::twig("error.html.twig", [], HttpCodes::NOT_FOUND)); + $this->displayViewByKind(ViewHttpResponse::twig("error.html.twig", [ + 'failures' => [ValidationFail::notFound("Could not find page ${_SERVER['REQUEST_URI']}.")] + ], HttpCodes::NOT_FOUND)); } } @@ -55,13 +65,14 @@ class FrontController { * @return void */ private function initializeRouterMap(): void { - $this->router->map("GET", "/", "UserController"); - $this->router->map("GET|POST", "/[a:action]?", "UserController"); - $this->router->map("GET|POST", "/tactic/[a:action]/[i:idTactic]?", "UserController"); + $this->router->map("GET", "/home", self::USER_CONTROLLER); + $this->router->map("GET|POST", "/user/[a:action]/[i:idTactic]?", self::USER_CONTROLLER); + $this->router->map("GET|POST", "/visitor/[a:action]", self::VISITOR_CONTROLLER); } /** * @param array $match + * @param MutableSessionHandle $session * @return void */ private function handleMatch(array $match, MutableSessionHandle $session): void { @@ -73,26 +84,32 @@ class FrontController { $this->handleResponseByType($this->tryToCall($tag, $action, array_values($params), $session)); } + /** - * @param string $controller + * @param string $controllerName * @param string $action * @param array $params * @param MutableSessionHandle $session * @return HttpResponse */ - - private function tryToCall(string $controller, string $action, array $params, MutableSessionHandle $session): HttpResponse { - $controller = $this->getController($controller); - try { - if (is_callable([$controller, $action])) { - // append the session as the last parameter of a controller function - $params[] = $session; - return call_user_func_array([$controller, $action], $params); - } else { - return ViewHttpResponse::twig("error.html.twig", [], HttpCodes::NOT_FOUND); + private function tryToCall(string $controllerName, string $action, array $params, MutableSessionHandle $session): HttpResponse { + if ($controllerName != self::VISITOR_CONTROLLER) { + $account = $session->getAccount(); + if ($account == null) { + return HttpResponse::redirect($this->basePath . "/visitor/login"); } - } catch (Exception $e) { - return ViewHttpResponse::twig("error.html.twig", [], HttpCodes::NOT_FOUND); + } + + $controller = $this->getController($controllerName); + + if (is_callable([$controller, $action])) { + // append the session as the last parameter of a controller function + $params[] = $session; + return call_user_func_array([$controller, $action], $params); + } else { + return ViewHttpResponse::twig("error.html.twig", [ + 'failures' => [ValidationFail::notFound("Could not find page ${_SERVER['REQUEST_URI']}.")] + ], HttpCodes::NOT_FOUND); } } @@ -106,7 +123,7 @@ class FrontController { if (isset($match["params"]["action"])) { return $match["params"]["action"]; } - return "default"; + return "home"; } /** @@ -129,6 +146,11 @@ class FrontController { */ private function handleResponseByType(HttpResponse $response): void { http_response_code($response->getCode()); + + foreach ($response->getHeaders() as $header => $value) { + header("$header: $value"); + } + if ($response instanceof ViewHttpResponse) { $this->displayViewByKind($response); } elseif ($response instanceof JsonHttpResponse) { diff --git a/src/Controller/Sub/AuthController.php b/src/Controller/Sub/AuthController.php index 7a537f2..fc72f67 100644 --- a/src/Controller/Sub/AuthController.php +++ b/src/Controller/Sub/AuthController.php @@ -7,6 +7,7 @@ use App\Http\HttpRequest; use App\Http\HttpResponse; use App\Http\ViewHttpResponse; use App\Model\AuthModel; +use App\Session\MutableSessionHandle; use App\Validation\FieldValidationFail; use App\Validation\ValidationFail; use App\Validation\Validators; @@ -42,9 +43,10 @@ class AuthController { /** * @param mixed[] $request + * @param MutableSessionHandle $session * @return HttpResponse */ - public function confirmRegister(array $request): HttpResponse { + public function confirmRegister(array $request, MutableSessionHandle $session): HttpResponse { $fails = []; $request = HttpRequest::from($request, $fails, [ "username" => [Validators::name(), Validators::lenBetween(2, 32)], @@ -59,6 +61,9 @@ class AuthController { if (!empty($fails)) { return $this->displayBadFields("display_register.html.twig", $fails); } + + $session->setAccount($account); + return ViewHttpResponse::twig("display_auth_confirm.html.twig", ['username' => $account->getName(), 'email' => $account->getEmail()]); } @@ -71,7 +76,7 @@ class AuthController { * @param mixed[] $request * @return HttpResponse */ - public function confirmLogin(array $request): HttpResponse { + public function confirmLogin(array $request, MutableSessionHandle $session): HttpResponse { $fails = []; $request = HttpRequest::from($request, $fails, [ "password" => [Validators::lenBetween(6, 256)], @@ -86,6 +91,8 @@ class AuthController { return $this->displayBadFields("display_login.html.twig", $fails); } + $session->setAccount($account); + return ViewHttpResponse::twig("display_auth_confirm.html.twig", ['username' => $account->getName(), 'email' => $account->getEmail()]); } diff --git a/src/Controller/Sub/EditorController.php b/src/Controller/Sub/EditorController.php index ce7a0a3..1188bc9 100644 --- a/src/Controller/Sub/EditorController.php +++ b/src/Controller/Sub/EditorController.php @@ -3,6 +3,7 @@ namespace App\Controller\Sub; use App\Connexion; +use App\Controller\VisitorController; use App\Data\TacticInfo; use App\Gateway\TacticInfoGateway; use App\Http\HttpCodes; @@ -10,6 +11,9 @@ use App\Http\HttpResponse; use App\Http\JsonHttpResponse; use App\Http\ViewHttpResponse; use App\Model\TacticModel; +use App\Session\SessionHandle; +use App\Validation\ValidationFail; +use App\Validator\TacticValidator; class EditorController { private TacticModel $model; @@ -22,24 +26,28 @@ class EditorController { return ViewHttpResponse::react("views/Editor.tsx", ["name" => $tactic->getName(), "id" => $tactic->getId()]); } - public function createNew(): HttpResponse { - $tactic = $this->model->makeNewDefault(); + public function createNew(SessionHandle $session): HttpResponse { + $tactic = $this->model->makeNewDefault($session->getAccount()->getId()); return $this->openEditor($tactic); } /** * returns an editor view for a given tactic * @param int $id the targeted tactic identifier + * @param SessionHandle $session * @return HttpResponse */ - public function edit(int $id): HttpResponse { + public function edit(int $id, SessionHandle $session): HttpResponse { $tactic = $this->model->get($id); - if ($tactic == null) { - return new JsonHttpResponse("la tactique " . $id . " n'existe pas", HttpCodes::NOT_FOUND); + $failure = TacticValidator::validateAccess($tactic, $session->getAccount()->getId()); + + if ($failure != null) { + return ViewHttpResponse::twig('error.html.twig', ['failures' => [$failure]], HttpCodes::NOT_FOUND); } return $this->openEditor($tactic); } + } diff --git a/src/Controller/Sub/VisualizerController.php b/src/Controller/Sub/VisualizerController.php index e3b5663..f70b18e 100644 --- a/src/Controller/Sub/VisualizerController.php +++ b/src/Controller/Sub/VisualizerController.php @@ -7,6 +7,9 @@ use App\Http\HttpResponse; use App\Http\JsonHttpResponse; use App\Http\ViewHttpResponse; use App\Model\TacticModel; +use App\Session\SessionHandle; +use App\Validation\ValidationFail; +use App\Validator\TacticValidator; class VisualizerController { private TacticModel $tacticModel; @@ -19,11 +22,13 @@ class VisualizerController { $this->tacticModel = $tacticModel; } - public function visualize(int $id): HttpResponse { + public function visualize(int $id, SessionHandle $session): HttpResponse { $tactic = $this->tacticModel->get($id); - if ($tactic == null) { - return new JsonHttpResponse("la tactique " . $id . " n'existe pas", HttpCodes::NOT_FOUND); + $failure = TacticValidator::validateAccess($tactic, $session->getAccount()->getId()); + + if ($failure != null) { + return ViewHttpResponse::twig('error.html.twig', ['failures' => [$failure]], HttpCodes::NOT_FOUND); } return ViewHttpResponse::react("views/Visualizer.tsx", ["name" => $tactic->getName()]); diff --git a/src/Controller/UserController.php b/src/Controller/UserController.php index 360c9a3..64a0caa 100644 --- a/src/Controller/UserController.php +++ b/src/Controller/UserController.php @@ -3,46 +3,29 @@ namespace App\Controller; use App\Connexion; -use App\Gateway\AccountGateway; use App\Gateway\TacticInfoGateway; use App\Http\HttpResponse; use App\Http\ViewHttpResponse; -use App\Model\AuthModel; use App\Model\TacticModel; +use App\Session\SessionHandle; -class UserController { +class UserController extends VisitorController { public function home(): HttpResponse { return ViewHttpResponse::twig("home.twig", []); } - public function register(): HttpResponse { - $model = new AuthModel(new AccountGateway(new Connexion(get_database()))); - if ($_SERVER['REQUEST_METHOD'] === 'GET') { - return (new Sub\AuthController($model))->displayRegister(); - } - return (new Sub\AuthController($model))->confirmRegister($_POST); - } - - public function login(): HttpResponse { - $model = new AuthModel(new AccountGateway(new Connexion(get_database()))); - if ($_SERVER['REQUEST_METHOD'] === 'GET') { - return (new Sub\AuthController($model))->displayLogin(); - } - return (new Sub\AuthController($model))->confirmLogin($_POST); - } - - public function open(int $id): HttpResponse { + public function view(int $id, SessionHandle $session): HttpResponse { $model = new TacticModel(new TacticInfoGateway(new Connexion(get_database()))); - return (new Sub\VisualizerController($model))->visualize($id); + return (new Sub\VisualizerController($model))->visualize($id, $session); } - public function edit(int $id): HttpResponse { + public function edit(int $id, SessionHandle $session): HttpResponse { $model = new TacticModel(new TacticInfoGateway(new Connexion(get_database()))); - return (new Sub\EditorController($model))->edit($id); + return (new Sub\EditorController($model))->edit($id, $session); } - public function create(): HttpResponse { + public function create(SessionHandle $session): HttpResponse { $model = new TacticModel(new TacticInfoGateway(new Connexion(get_database()))); - return (new Sub\EditorController($model))->createNew(); + return (new Sub\EditorController($model))->createNew($session); } } diff --git a/src/Controller/VisitorController.php b/src/Controller/VisitorController.php new file mode 100644 index 0000000..56e0ec2 --- /dev/null +++ b/src/Controller/VisitorController.php @@ -0,0 +1,30 @@ +displayRegister(); + } + return (new Sub\AuthController($model))->confirmRegister($_POST, $session); + } + + public final function login(MutableSessionHandle $session): HttpResponse { + $model = new AuthModel(new AccountGateway(new Connexion(get_database()))); + if ($_SERVER['REQUEST_METHOD'] === 'GET') { + return (new Sub\AuthController($model))->displayLogin(); + } + return (new Sub\AuthController($model))->confirmLogin($_POST, $session); + } + +} \ No newline at end of file diff --git a/src/Data/Account.php b/src/Data/Account.php index 8b58e1c..0ed4339 100755 --- a/src/Data/Account.php +++ b/src/Data/Account.php @@ -25,18 +25,27 @@ class Account { */ private string $name; + /** + * @var int + */ + private int $id; + /** * @param string $email - * @param string $phoneNumber - * @param AccountUser $user - * @param Team[] $teams + * @param string $name + * @param string $token * @param int $id */ - public function __construct(string $email, string $name, string $token) { + public function __construct(string $email, string $name, string $token, int $id) { $this->email = $email; $this->name = $name; $this->token = $token; + $this->id = $id; + } + + public function getId(): int { + return $this->id; } public function getEmail(): string { diff --git a/src/Data/TacticInfo.php b/src/Data/TacticInfo.php index eef7bb3..a2a576e 100644 --- a/src/Data/TacticInfo.php +++ b/src/Data/TacticInfo.php @@ -7,14 +7,18 @@ class TacticInfo implements \JsonSerializable { private string $name; private int $creation_date; + private int $ownerId; + /** * @param int $id * @param string $name * @param int $creation_date + * @param int $ownerId */ - public function __construct(int $id, string $name, int $creation_date) { + public function __construct(int $id, string $name, int $creation_date, int $ownerId) { $this->id = $id; $this->name = $name; + $this->ownerId = $ownerId; $this->creation_date = $creation_date; } @@ -26,6 +30,13 @@ class TacticInfo implements \JsonSerializable { return $this->name; } + /** + * @return int + */ + public function getOwnerId(): int { + return $this->ownerId; + } + public function getCreationTimestamp(): int { return $this->creation_date; } diff --git a/src/Gateway/AccountGateway.php b/src/Gateway/AccountGateway.php index a4686d6..8320f33 100644 --- a/src/Gateway/AccountGateway.php +++ b/src/Gateway/AccountGateway.php @@ -22,13 +22,14 @@ class AccountGateway { } - public function insertAccount(Account $account, string $hash): void { - $this->con->exec("INSERT INTO Account VALUES (:username,:hash,:email)", [ - ':username' => [$account->getName(), PDO::PARAM_STR], + public function insertAccount(string $name, string $email, string $token, string $hash): int { + $this->con->exec("INSERT INTO Account(username, hash, email, token) VALUES (:username,:hash,:email,:token)", [ + ':username' => [$name, PDO::PARAM_STR], ':hash' => [$hash, PDO::PARAM_STR], - ':email' => [$account->getEmail(), PDO::PARAM_STR], - ':token' => [$account->getToken(), PDO::PARAM_STR] + ':email' => [$email, PDO::PARAM_STR], + ':token' => [$token, PDO::PARAM_STR] ]); + return intval($this->con->lastInsertId()); } public function getHash(string $email): string { @@ -44,12 +45,12 @@ class AccountGateway { * @return Account|null */ public function getAccount(string $email): ?Account { - $results = $this->con->fetch("SELECT username,email,token FROM Account WHERE email = :email", [':email' => [$email, PDO::PARAM_STR]]); + $results = $this->con->fetch("SELECT * FROM Account WHERE email = :email", [':email' => [$email, PDO::PARAM_STR]]); if (empty($results)) return null; $acc = $results[0]; - return new Account($acc["email"], $acc["name"], $acc["token"]); + return new Account($acc["email"], $acc["username"], $acc["token"], $acc["id"]); } diff --git a/src/Gateway/FormResultGateway.php b/src/Gateway/FormResultGateway.php deleted file mode 100644 index 36178ad..0000000 --- a/src/Gateway/FormResultGateway.php +++ /dev/null @@ -1,35 +0,0 @@ -con = $con; - } - - - public function insert(string $username, string $description): void { - $this->con->exec( - "INSERT INTO FormEntries VALUES (:name, :description)", - [ - ":name" => [$username, PDO::PARAM_STR], - "description" => [$description, PDO::PARAM_STR], - ] - ); - } - - /** - * @return array - */ - public function listResults(): array { - return $this->con->fetch("SELECT * FROM FormEntries", []); - } -} diff --git a/src/Gateway/TacticInfoGateway.php b/src/Gateway/TacticInfoGateway.php index 3441c9a..6aa2cca 100644 --- a/src/Gateway/TacticInfoGateway.php +++ b/src/Gateway/TacticInfoGateway.php @@ -28,19 +28,22 @@ class TacticInfoGateway { $row = $res[0]; - return new TacticInfo($id, $row["name"], strtotime($row["creation_date"])); + return new TacticInfo($id, $row["name"], strtotime($row["creation_date"]), $row["owner"]); } - public function insert(string $name): TacticInfo { + public function insert(string $name, int $owner): TacticInfo { $this->con->exec( - "INSERT INTO TacticInfo(name) VALUES(:name)", - [":name" => [$name, PDO::PARAM_STR]] + "INSERT INTO TacticInfo(name, owner) VALUES(:name, :owner)", + [ + ":name" => [$name, PDO::PARAM_STR], + ":owner" => [$owner, PDO::PARAM_INT] + ] ); $row = $this->con->fetch( - "SELECT id, creation_date FROM TacticInfo WHERE :id = id", + "SELECT id, creation_date, owner FROM TacticInfo WHERE :id = id", [':id' => [$this->con->lastInsertId(), PDO::PARAM_INT]] )[0]; - return new TacticInfo(intval($row["id"]), $name, strtotime($row["creation_date"])); + return new TacticInfo(intval($row["id"]), $name, strtotime($row["creation_date"]), $row["owner"]); } public function updateName(int $id, string $name): void { diff --git a/src/Http/HttpCodes.php b/src/Http/HttpCodes.php index f9d550c..c2b01df 100644 --- a/src/Http/HttpCodes.php +++ b/src/Http/HttpCodes.php @@ -7,7 +7,11 @@ namespace App\Http; */ class HttpCodes { public const OK = 200; + public const FOUND = 302; public const BAD_REQUEST = 400; + + public const FORBIDDEN = 403; + public const NOT_FOUND = 404; } diff --git a/src/Http/HttpResponse.php b/src/Http/HttpResponse.php index 5d8c3bf..d6d6a9a 100644 --- a/src/Http/HttpResponse.php +++ b/src/Http/HttpResponse.php @@ -3,21 +3,42 @@ namespace App\Http; class HttpResponse { + + /** + * @var array + */ + private array $headers; private int $code; /** * @param int $code + * @param array $headers */ - public function __construct(int $code) { + public function __construct(int $code, array $headers) { $this->code = $code; + $this->headers = $headers; } public function getCode(): int { return $this->code; } + /** + * @return array + */ + public function getHeaders(): array { + return $this->headers; + } + public static function fromCode(int $code): HttpResponse { - return new HttpResponse($code); + return new HttpResponse($code, []); + } + + public static function redirect(string $url, int $code = HttpCodes::FOUND): HttpResponse { + if ($code < 300 || $code >= 400) { + throw new \InvalidArgumentException("given code is not a redirection http code"); + } + return new HttpResponse($code, ["Location" => $url]); } } diff --git a/src/Http/JsonHttpResponse.php b/src/Http/JsonHttpResponse.php index bbd3d80..a01ffd9 100644 --- a/src/Http/JsonHttpResponse.php +++ b/src/Http/JsonHttpResponse.php @@ -12,7 +12,7 @@ class JsonHttpResponse extends HttpResponse { * @param mixed $payload */ public function __construct($payload, int $code = HttpCodes::OK) { - parent::__construct($code); + parent::__construct($code, []); $this->payload = $payload; } diff --git a/src/Http/ViewHttpResponse.php b/src/Http/ViewHttpResponse.php index 2e517d7..9db80c5 100644 --- a/src/Http/ViewHttpResponse.php +++ b/src/Http/ViewHttpResponse.php @@ -26,7 +26,7 @@ class ViewHttpResponse extends HttpResponse { * @param array $arguments */ private function __construct(int $kind, string $file, array $arguments, int $code = HttpCodes::OK) { - parent::__construct($code); + parent::__construct($code, []); $this->kind = $kind; $this->file = $file; $this->arguments = $arguments; diff --git a/src/Model/AuthModel.php b/src/Model/AuthModel.php index b786c2e..a12ce9e 100644 --- a/src/Model/AuthModel.php +++ b/src/Model/AuthModel.php @@ -2,7 +2,6 @@ namespace App\Model; -use App\Controller\AuthController; use App\Data\Account; use App\Gateway\AccountGateway; use App\Validation\FieldValidationFail; @@ -37,15 +36,15 @@ class AuthModel { $failures[] = new FieldValidationFail("email", "email already exist"); } - if (!empty($errors)) { + if (!empty($failures)) { return null; } $hash = password_hash($password, PASSWORD_DEFAULT); - $account = new Account($email, $username, $this->generateToken()); - $this->gateway->insertAccount($account, $hash); - return $account; + $token = $this->generateToken(); + $accountId = $this->gateway->insertAccount($username, $email, $token, $hash); + return new Account($email, $username, $token, $accountId); } private function generateToken(): string { @@ -60,13 +59,13 @@ class AuthModel { */ public function login(string $email, string $password, array &$failures): ?Account { if (!$this->gateway->exists($email)) { - $failures = new FieldValidationFail("email", "email doesnt exists"); + $failures[] = new FieldValidationFail("email", "email doesnt exists"); return null; } $hash = $this->gateway->getHash($email); if (!password_verify($password, $hash)) { - $failures = new FieldValidationFail("password", "invalid password"); + $failures[] = new FieldValidationFail("password", "invalid password"); return null; } diff --git a/src/Model/TacticModel.php b/src/Model/TacticModel.php index cabcdec..73cfd91 100644 --- a/src/Model/TacticModel.php +++ b/src/Model/TacticModel.php @@ -18,12 +18,12 @@ class TacticModel { $this->tactics = $tactics; } - public function makeNew(string $name): TacticInfo { - return $this->tactics->insert($name); + public function makeNew(string $name, int $ownerId): TacticInfo { + return $this->tactics->insert($name, $ownerId); } - public function makeNewDefault(): ?TacticInfo { - return $this->tactics->insert(self::TACTIC_DEFAULT_NAME); + public function makeNewDefault(int $ownerId): ?TacticInfo { + return $this->tactics->insert(self::TACTIC_DEFAULT_NAME, $ownerId); } /** diff --git a/src/Session/MutableSessionHandle.php b/src/Session/MutableSessionHandle.php index a7822a3..e142049 100644 --- a/src/Session/MutableSessionHandle.php +++ b/src/Session/MutableSessionHandle.php @@ -5,5 +5,6 @@ namespace App\Session; use App\Data\Account; interface MutableSessionHandle extends SessionHandle { + public function setAccount(Account $account): void; } \ No newline at end of file diff --git a/src/Validation/ValidationFail.php b/src/Validation/ValidationFail.php index 4f1ec22..83d6c76 100644 --- a/src/Validation/ValidationFail.php +++ b/src/Validation/ValidationFail.php @@ -34,7 +34,7 @@ class ValidationFail implements JsonSerializable { } public static function notFound(string $message): ValidationFail { - return new ValidationFail("not found", $message); + return new ValidationFail("Not found", $message); } } diff --git a/src/Validator/TacticValidator.php b/src/Validator/TacticValidator.php new file mode 100644 index 0000000..711fb72 --- /dev/null +++ b/src/Validator/TacticValidator.php @@ -0,0 +1,22 @@ +getId() . " n'existe pas"); + } + + if ($tactic->getOwnerId() != $ownerId) { + return new ValidationFail("Unauthorized", "Vous ne pouvez pas accéder à cette tactique.",); + } + + return null; + } + +} \ No newline at end of file diff --git a/src/Views/error.html.twig b/src/Views/error.html.twig index 1d1db7d..e30c2fa 100644 --- a/src/Views/error.html.twig +++ b/src/Views/error.html.twig @@ -51,7 +51,7 @@ {% endfor %} - + \ No newline at end of file From 39329c93257741db65a6f78cfe8b373dbf151523 Mon Sep 17 00:00:00 2001 From: Override-6 Date: Wed, 22 Nov 2023 03:43:03 +0100 Subject: [PATCH 6/8] integrate session in API, use tokens --- phpstan.neon | 2 + public/api/index.php | 126 ++++++++++++++++++-- public/index.php | 4 +- src/Controller/Api/APIAuthController.php | 39 ++++++ src/Controller/Api/APITacticController.php | 22 ++-- src/Controller/FrontController.php | 4 +- src/Controller/Sub/AuthController.php | 1 - src/Controller/Sub/EditorController.php | 2 +- src/Controller/Sub/VisualizerController.php | 2 +- src/Controller/VisitorController.php | 8 +- src/Gateway/AccountGateway.php | 47 +++++--- src/Gateway/TacticInfoGateway.php | 2 +- src/Http/HttpResponse.php | 1 - src/Model/AuthModel.php | 2 +- src/Model/TacticModel.php | 19 ++- src/Session/MutableSessionHandle.php | 3 +- src/Session/PhpSessionHandle.php | 5 +- src/Session/SessionHandle.php | 4 +- src/Validation/ValidationFail.php | 4 + src/Validator/TacticValidator.php | 9 +- 20 files changed, 239 insertions(+), 67 deletions(-) create mode 100644 src/Controller/Api/APIAuthController.php diff --git a/phpstan.neon b/phpstan.neon index bc6c041..715fe84 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -9,5 +9,7 @@ parameters: - sql/database.php - profiles/dev-config-profile.php - profiles/prod-config-profile.php + - public/api/index.php excludePaths: - src/react-display-file.php + - public/api/index.php diff --git a/public/api/index.php b/public/api/index.php index 3ed5caa..8cc7287 100644 --- a/public/api/index.php +++ b/public/api/index.php @@ -6,32 +6,134 @@ require "../../sql/database.php"; require "../utils.php"; use App\Connexion; +use App\Controller\Api\APIAuthController; use App\Controller\Api\APITacticController; +use App\Data\Account; +use App\Gateway\AccountGateway; use App\Gateway\TacticInfoGateway; +use App\Http\HttpResponse; use App\Http\JsonHttpResponse; use App\Http\ViewHttpResponse; +use App\Model\AuthModel; use App\Model\TacticModel; +use App\Session\SessionHandle; +use App\Validation\ValidationFail; -$con = new Connexion(get_database()); +function getTacticController(): APITacticController { + return new APITacticController(new TacticModel(new TacticInfoGateway(new Connexion(get_database())))); +} + +function getAuthController(): APIAuthController { + return new APIAuthController(new AuthModel(new AccountGateway(new Connexion(get_database())))); +} + +class Action { + /** + * @var callable(mixed[]): HttpResponse $action action to call + */ + private $action; + + private bool $isAuthRequired; + + /** + * @param callable(mixed[]): HttpResponse $action + */ + private function __construct(callable $action, bool $isAuthRequired) { + $this->action = $action; + $this->isAuthRequired = $isAuthRequired; + } + + public function isAuthRequired(): bool { + return $this->isAuthRequired; + } + + /** + * @param mixed[] $params + * @param ?Account $account + * @return HttpResponse + */ + public function run(array $params, ?Account $account): HttpResponse { + $params = array_values($params); + if ($this->isAuthRequired) { + if ($account == null) { + throw new Exception("action requires authorization."); + } + $params[] = $account; + } + + return call_user_func_array($this->action, $params); + } + + /** + * @param callable(mixed[]): HttpResponse $action + * @return Action an action that does not require to have an authorization. + */ + public static function noAuth(callable $action): Action { + return new Action($action, false); + } + + /** + * @param callable(mixed[]): HttpResponse $action + * @return Action an action that does require to have an authorization. + */ + public static function auth(callable $action): Action { + return new Action($action, true); + } +} + +/** + * @param mixed[] $match + * @return HttpResponse + * @throws Exception + */ +function handleMatch(array $match): HttpResponse { + if (!$match) { + return new JsonHttpResponse([ValidationFail::notFound("not found")]); + } + + $action = $match['target']; + if (!$action instanceof Action) { + throw new Exception("routed action is not an Action object."); + } + + $auth = null; + + if ($action->isAuthRequired()) { + $auth = tryGetAuthAccount(); + if ($auth == null) { + return new JsonHttpResponse([ValidationFail::unauthorized("Missing or invalid 'Authorization' header")]); + } + } + + return $action->run($match['params'], $auth); +} + +function tryGetAuthAccount(): ?Account { + $headers = getallheaders(); + + // If no authorization header is set, try fallback to php session. + if (!isset($headers['Authorization'])) { + $session = SessionHandle::init(); + return $session->getAccount(); + } + + $token = $headers['Authorization']; + $gateway = new AccountGateway(new Connexion(get_database())); + return $gateway->getAccountFromToken($token); +} $router = new AltoRouter(); $router->setBasePath(get_public_path() . "/api"); -$tacticEndpoint = new APITacticController(new TacticModel(new TacticInfoGateway($con))); -$router->map("POST", "/tactic/[i:id]/edit/name", fn(int $id) => $tacticEndpoint->updateName($id)); -$router->map("GET", "/tactic/[i:id]", fn(int $id) => $tacticEndpoint->getTacticInfo($id)); -$router->map("POST", "/tactic/new", fn() => $tacticEndpoint->newTactic()); +$router->map("POST", "/tactic/[i:id]/edit/name", Action::auth(fn(int $id, Account $acc) => getTacticController()->updateName($id, $acc))); +$router->map("GET", "/tactic/[i:id]", Action::auth(fn(int $id, Account $acc) => getTacticController()->getTacticInfo($id, $acc))); +$router->map("POST", "/tactic/new", Action::auth(fn(Account $acc) => getTacticController()->newTactic($acc))); +$router->map("POST", "/auth", Action::noAuth(fn() => getAuthController()->authorize())); $match = $router->match(); -if ($match == null) { - echo "404 not found"; - header($_SERVER['SERVER_PROTOCOL'] . ' 404 Not Found'); - exit(1); -} - -$response = call_user_func_array($match['target'], $match['params']); +$response = handleMatch($match); http_response_code($response->getCode()); if ($response instanceof JsonHttpResponse) { diff --git a/public/index.php b/public/index.php index ce1df33..7d42305 100644 --- a/public/index.php +++ b/public/index.php @@ -8,9 +8,9 @@ require "utils.php"; require "../src/react-display.php"; use App\Controller\FrontController; -use App\Session\PhpSessionHandle; +use App\Session\SessionHandle; $basePath = get_public_path(); $frontController = new FrontController($basePath); -$frontController->run(PhpSessionHandle::init()); +$frontController->run(SessionHandle::init()); diff --git a/src/Controller/Api/APIAuthController.php b/src/Controller/Api/APIAuthController.php new file mode 100644 index 0000000..d9e4c23 --- /dev/null +++ b/src/Controller/Api/APIAuthController.php @@ -0,0 +1,39 @@ +model = $model; + } + + + public function authorize(): HttpResponse { + return Control::runChecked([ + "email" => [Validators::regex("/^\\S+@\\S+\\.\\S+$/"), Validators::lenBetween(5, 256)], + "password" => [Validators::lenBetween(6, 256)], + ], function (HttpRequest $req) { + $failures = []; + $account = $this->model->login($req["email"], $req["password"], $failures); + + if (!empty($failures)) { + return new JsonHttpResponse($failures); + } + + return new JsonHttpResponse(["authorization" => $account->getToken()]); + }, true); + } + +} diff --git a/src/Controller/Api/APITacticController.php b/src/Controller/Api/APITacticController.php index ec0edc8..c1fcee3 100644 --- a/src/Controller/Api/APITacticController.php +++ b/src/Controller/Api/APITacticController.php @@ -3,6 +3,7 @@ namespace App\Controller\Api; use App\Controller\Control; +use App\Data\Account; use App\Http\HttpCodes; use App\Http\HttpRequest; use App\Http\HttpResponse; @@ -23,26 +24,33 @@ class APITacticController { $this->model = $model; } - public function updateName(int $tactic_id): HttpResponse { + public function updateName(int $tactic_id, Account $account): HttpResponse { return Control::runChecked([ "name" => [Validators::lenBetween(1, 50), Validators::nameWithSpaces()], - ], function (HttpRequest $request) use ($tactic_id) { - $this->model->updateName($tactic_id, $request["name"]); + ], function (HttpRequest $request) use ($tactic_id, $account) { + + $failures = $this->model->updateName($tactic_id, $request["name"], $account->getId()); + + if (!empty($failures)) { + //TODO find a system to handle Unauthorized error codes more easily from failures. + return new JsonHttpResponse($failures, HttpCodes::BAD_REQUEST); + } + return HttpResponse::fromCode(HttpCodes::OK); }, true); } - public function newTactic(): HttpResponse { + public function newTactic(Account $account): HttpResponse { return Control::runChecked([ "name" => [Validators::lenBetween(1, 50), Validators::nameWithSpaces()], - ], function (HttpRequest $request) { - $tactic = $this->model->makeNew($request["name"]); + ], function (HttpRequest $request) use ($account) { + $tactic = $this->model->makeNew($request["name"], $account->getId()); $id = $tactic->getId(); return new JsonHttpResponse(["id" => $id]); }, true); } - public function getTacticInfo(int $id): HttpResponse { + public function getTacticInfo(int $id, Account $account): HttpResponse { $tactic_info = $this->model->get($id); if ($tactic_info == null) { diff --git a/src/Controller/FrontController.php b/src/Controller/FrontController.php index 3d73d80..d1bbd6d 100644 --- a/src/Controller/FrontController.php +++ b/src/Controller/FrontController.php @@ -42,7 +42,7 @@ class FrontController { $this->handleMatch($match, $session); } else { $this->displayViewByKind(ViewHttpResponse::twig("error.html.twig", [ - 'failures' => [ValidationFail::notFound("Could not find page ${_SERVER['REQUEST_URI']}.")] + 'failures' => [ValidationFail::notFound("Could not find page ${_SERVER['REQUEST_URI']}.")], ], HttpCodes::NOT_FOUND)); } } @@ -108,7 +108,7 @@ class FrontController { return call_user_func_array([$controller, $action], $params); } else { return ViewHttpResponse::twig("error.html.twig", [ - 'failures' => [ValidationFail::notFound("Could not find page ${_SERVER['REQUEST_URI']}.")] + 'failures' => [ValidationFail::notFound("Could not find page ${_SERVER['REQUEST_URI']}.")], ], HttpCodes::NOT_FOUND); } } diff --git a/src/Controller/Sub/AuthController.php b/src/Controller/Sub/AuthController.php index fc72f67..fcf12d5 100644 --- a/src/Controller/Sub/AuthController.php +++ b/src/Controller/Sub/AuthController.php @@ -2,7 +2,6 @@ namespace App\Controller\Sub; -use App\Gateway\AccountGateway; use App\Http\HttpRequest; use App\Http\HttpResponse; use App\Http\ViewHttpResponse; diff --git a/src/Controller/Sub/EditorController.php b/src/Controller/Sub/EditorController.php index 1188bc9..dff6e8a 100644 --- a/src/Controller/Sub/EditorController.php +++ b/src/Controller/Sub/EditorController.php @@ -40,7 +40,7 @@ class EditorController { public function edit(int $id, SessionHandle $session): HttpResponse { $tactic = $this->model->get($id); - $failure = TacticValidator::validateAccess($tactic, $session->getAccount()->getId()); + $failure = TacticValidator::validateAccess($tactic, $id, $session->getAccount()->getId()); if ($failure != null) { return ViewHttpResponse::twig('error.html.twig', ['failures' => [$failure]], HttpCodes::NOT_FOUND); diff --git a/src/Controller/Sub/VisualizerController.php b/src/Controller/Sub/VisualizerController.php index f70b18e..c7e5098 100644 --- a/src/Controller/Sub/VisualizerController.php +++ b/src/Controller/Sub/VisualizerController.php @@ -25,7 +25,7 @@ class VisualizerController { public function visualize(int $id, SessionHandle $session): HttpResponse { $tactic = $this->tacticModel->get($id); - $failure = TacticValidator::validateAccess($tactic, $session->getAccount()->getId()); + $failure = TacticValidator::validateAccess($tactic, $id, $session->getAccount()->getId()); if ($failure != null) { return ViewHttpResponse::twig('error.html.twig', ['failures' => [$failure]], HttpCodes::NOT_FOUND); diff --git a/src/Controller/VisitorController.php b/src/Controller/VisitorController.php index 56e0ec2..e74934a 100644 --- a/src/Controller/VisitorController.php +++ b/src/Controller/VisitorController.php @@ -9,9 +9,7 @@ use App\Model\AuthModel; use App\Session\MutableSessionHandle; class VisitorController { - - - public final function register(MutableSessionHandle $session): HttpResponse { + final public function register(MutableSessionHandle $session): HttpResponse { $model = new AuthModel(new AccountGateway(new Connexion(get_database()))); if ($_SERVER['REQUEST_METHOD'] === 'GET') { return (new Sub\AuthController($model))->displayRegister(); @@ -19,7 +17,7 @@ class VisitorController { return (new Sub\AuthController($model))->confirmRegister($_POST, $session); } - public final function login(MutableSessionHandle $session): HttpResponse { + final public function login(MutableSessionHandle $session): HttpResponse { $model = new AuthModel(new AccountGateway(new Connexion(get_database()))); if ($_SERVER['REQUEST_METHOD'] === 'GET') { return (new Sub\AuthController($model))->displayLogin(); @@ -27,4 +25,4 @@ class VisitorController { return (new Sub\AuthController($model))->confirmLogin($_POST, $session); } -} \ No newline at end of file +} diff --git a/src/Gateway/AccountGateway.php b/src/Gateway/AccountGateway.php index 8320f33..3a57abb 100644 --- a/src/Gateway/AccountGateway.php +++ b/src/Gateway/AccountGateway.php @@ -17,39 +17,56 @@ class AccountGateway { } - public function exists(string $email): bool { - return $this->getAccount($email) != null; - } - - public function insertAccount(string $name, string $email, string $token, string $hash): int { $this->con->exec("INSERT INTO Account(username, hash, email, token) VALUES (:username,:hash,:email,:token)", [ ':username' => [$name, PDO::PARAM_STR], ':hash' => [$hash, PDO::PARAM_STR], ':email' => [$email, PDO::PARAM_STR], - ':token' => [$token, PDO::PARAM_STR] + ':token' => [$token, PDO::PARAM_STR], ]); return intval($this->con->lastInsertId()); } - public function getHash(string $email): string { - $results = $this->con->fetch("SELECT hash FROM Account WHERE email = :email", [ - ':email' => [$email, PDO::PARAM_STR] - ]); - return $results[0]['hash']; + /** + * @param string $email + * @return array|null + */ + private function getRowsFromMail(string $email): ?array { + return $this->con->fetch("SELECT * FROM Account WHERE email = :email", [':email' => [$email, PDO::PARAM_STR]])[0] ?? null; + } + + + public function getHash(string $email): ?string { + $results = $this->getRowsFromMail($email); + if ($results == null) { + return null; + } + return $results['hash']; } + public function exists(string $email): bool { + return $this->getRowsFromMail($email) != null; + } /** * @param string $email * @return Account|null */ - public function getAccount(string $email): ?Account { - $results = $this->con->fetch("SELECT * FROM Account WHERE email = :email", [':email' => [$email, PDO::PARAM_STR]]); - if (empty($results)) + public function getAccountFromMail(string $email): ?Account { + $acc = $this->getRowsFromMail($email); + if (empty($acc)) { + return null; + } + + return new Account($email, $acc["username"], $acc["token"], $acc["id"]); + } + + public function getAccountFromToken(string $token): ?Account { + $acc = $this->con->fetch("SELECT * FROM Account WHERE token = :token", [':token' => [$token, PDO::PARAM_STR]])[0] ?? null; + if (empty($acc)) { return null; + } - $acc = $results[0]; return new Account($acc["email"], $acc["username"], $acc["token"], $acc["id"]); } diff --git a/src/Gateway/TacticInfoGateway.php b/src/Gateway/TacticInfoGateway.php index 6aa2cca..efa0a7c 100644 --- a/src/Gateway/TacticInfoGateway.php +++ b/src/Gateway/TacticInfoGateway.php @@ -36,7 +36,7 @@ class TacticInfoGateway { "INSERT INTO TacticInfo(name, owner) VALUES(:name, :owner)", [ ":name" => [$name, PDO::PARAM_STR], - ":owner" => [$owner, PDO::PARAM_INT] + ":owner" => [$owner, PDO::PARAM_INT], ] ); $row = $this->con->fetch( diff --git a/src/Http/HttpResponse.php b/src/Http/HttpResponse.php index d6d6a9a..e5db3e8 100644 --- a/src/Http/HttpResponse.php +++ b/src/Http/HttpResponse.php @@ -3,7 +3,6 @@ namespace App\Http; class HttpResponse { - /** * @var array */ diff --git a/src/Model/AuthModel.php b/src/Model/AuthModel.php index a12ce9e..1739ab5 100644 --- a/src/Model/AuthModel.php +++ b/src/Model/AuthModel.php @@ -69,7 +69,7 @@ class AuthModel { return null; } - return $this->gateway->getAccount($email); + return $this->gateway->getAccountFromMail($email); } diff --git a/src/Model/TacticModel.php b/src/Model/TacticModel.php index 73cfd91..8111963 100644 --- a/src/Model/TacticModel.php +++ b/src/Model/TacticModel.php @@ -2,8 +2,10 @@ namespace App\Model; +use App\Data\Account; use App\Data\TacticInfo; use App\Gateway\TacticInfoGateway; +use App\Validation\ValidationFail; class TacticModel { public const TACTIC_DEFAULT_NAME = "Nouvelle tactique"; @@ -39,15 +41,22 @@ class TacticModel { * Update the name of a tactic * @param int $id the tactic identifier * @param string $name the new name to set - * @return bool true if the update was done successfully + * @return ValidationFail[] failures, if any */ - public function updateName(int $id, string $name): bool { - if ($this->tactics->get($id) == null) { - return false; + public function updateName(int $id, string $name, int $authId): array { + + $tactic = $this->tactics->get($id); + + if ($tactic == null) { + return [ValidationFail::notFound("Could not find tactic")]; + } + + if ($tactic->getOwnerId() != $authId) { + return [ValidationFail::unauthorized()]; } $this->tactics->updateName($id, $name); - return true; + return []; } } diff --git a/src/Session/MutableSessionHandle.php b/src/Session/MutableSessionHandle.php index e142049..2bcb0fc 100644 --- a/src/Session/MutableSessionHandle.php +++ b/src/Session/MutableSessionHandle.php @@ -5,6 +5,5 @@ namespace App\Session; use App\Data\Account; interface MutableSessionHandle extends SessionHandle { - public function setAccount(Account $account): void; -} \ No newline at end of file +} diff --git a/src/Session/PhpSessionHandle.php b/src/Session/PhpSessionHandle.php index ea0e7c2..09d862c 100644 --- a/src/Session/PhpSessionHandle.php +++ b/src/Session/PhpSessionHandle.php @@ -5,8 +5,7 @@ namespace App\Session; use App\Data\Account; class PhpSessionHandle implements MutableSessionHandle { - - public static function init(): PhpSessionHandle { + public static function init(): SessionHandle { if (session_status() !== PHP_SESSION_NONE) { throw new \Exception("A php session is already started !"); } @@ -21,4 +20,4 @@ class PhpSessionHandle implements MutableSessionHandle { public function setAccount(Account $account): void { $_SESSION["account"] = $account; } -} \ No newline at end of file +} diff --git a/src/Session/SessionHandle.php b/src/Session/SessionHandle.php index 5a65794..6ffb9c0 100644 --- a/src/Session/SessionHandle.php +++ b/src/Session/SessionHandle.php @@ -5,7 +5,5 @@ namespace App\Session; use App\Data\Account; interface SessionHandle { - public function getAccount(): ?Account; - -} \ No newline at end of file +} diff --git a/src/Validation/ValidationFail.php b/src/Validation/ValidationFail.php index 83d6c76..2229a53 100644 --- a/src/Validation/ValidationFail.php +++ b/src/Validation/ValidationFail.php @@ -37,4 +37,8 @@ class ValidationFail implements JsonSerializable { return new ValidationFail("Not found", $message); } + public static function unauthorized(string $message = "Unauthorized"): ValidationFail { + return new ValidationFail("Unauthorized", $message); + } + } diff --git a/src/Validator/TacticValidator.php b/src/Validator/TacticValidator.php index 711fb72..e39cd51 100644 --- a/src/Validator/TacticValidator.php +++ b/src/Validator/TacticValidator.php @@ -6,17 +6,16 @@ use App\Data\TacticInfo; use App\Validation\ValidationFail; class TacticValidator { - - public static function validateAccess(?TacticInfo $tactic, int $ownerId): ?ValidationFail { + public static function validateAccess(?TacticInfo $tactic, int $tacticId, int $ownerId): ?ValidationFail { if ($tactic == null) { - return ValidationFail::notFound("La tactique " . $tactic->getId() . " n'existe pas"); + return ValidationFail::notFound("La tactique $tacticId n'existe pas"); } if ($tactic->getOwnerId() != $ownerId) { - return new ValidationFail("Unauthorized", "Vous ne pouvez pas accéder à cette tactique.",); + return ValidationFail::unauthorized("Vous ne pouvez pas accéder à cette tactique."); } return null; } -} \ No newline at end of file +} From 69be9399549b2855bb01c715de63fa685c78c622 Mon Sep 17 00:00:00 2001 From: "maxime.batista" Date: Wed, 22 Nov 2023 09:38:35 +0100 Subject: [PATCH 7/8] add target url in session to bring back user to its initial target after auth --- public/api/index.php | 3 ++- public/index.php | 4 ++-- src/Controller/FrontController.php | 11 +++++++---- src/Controller/Sub/AuthController.php | 7 +++++-- src/Session/MutableSessionHandle.php | 3 +++ src/Session/PhpSessionHandle.php | 8 ++++++++ src/Session/SessionHandle.php | 2 ++ 7 files changed, 29 insertions(+), 9 deletions(-) diff --git a/public/api/index.php b/public/api/index.php index 8cc7287..580f794 100644 --- a/public/api/index.php +++ b/public/api/index.php @@ -16,6 +16,7 @@ use App\Http\JsonHttpResponse; use App\Http\ViewHttpResponse; use App\Model\AuthModel; use App\Model\TacticModel; +use App\Session\PhpSessionHandle; use App\Session\SessionHandle; use App\Validation\ValidationFail; @@ -113,7 +114,7 @@ function tryGetAuthAccount(): ?Account { // If no authorization header is set, try fallback to php session. if (!isset($headers['Authorization'])) { - $session = SessionHandle::init(); + $session = PhpSessionHandle::init(); return $session->getAccount(); } diff --git a/public/index.php b/public/index.php index 7d42305..ce1df33 100644 --- a/public/index.php +++ b/public/index.php @@ -8,9 +8,9 @@ require "utils.php"; require "../src/react-display.php"; use App\Controller\FrontController; -use App\Session\SessionHandle; +use App\Session\PhpSessionHandle; $basePath = get_public_path(); $frontController = new FrontController($basePath); -$frontController->run(SessionHandle::init()); +$frontController->run(PhpSessionHandle::init()); diff --git a/src/Controller/FrontController.php b/src/Controller/FrontController.php index d1bbd6d..4ac3047 100644 --- a/src/Controller/FrontController.php +++ b/src/Controller/FrontController.php @@ -40,11 +40,12 @@ class FrontController { $match = $this->router->match(); if ($match) { $this->handleMatch($match, $session); - } else { - $this->displayViewByKind(ViewHttpResponse::twig("error.html.twig", [ - 'failures' => [ValidationFail::notFound("Could not find page ${_SERVER['REQUEST_URI']}.")], - ], HttpCodes::NOT_FOUND)); + return; } + + $this->displayViewByKind(ViewHttpResponse::twig("error.html.twig", [ + 'failures' => [ValidationFail::notFound("Could not find page ${_SERVER['REQUEST_URI']}.")], + ], HttpCodes::NOT_FOUND)); } /** @@ -96,6 +97,8 @@ class FrontController { if ($controllerName != self::VISITOR_CONTROLLER) { $account = $session->getAccount(); if ($account == null) { + // put in the session the initial url the user wanted to get + $session->setInitialTarget($_SERVER['REQUEST_URI']); return HttpResponse::redirect($this->basePath . "/visitor/login"); } } diff --git a/src/Controller/Sub/AuthController.php b/src/Controller/Sub/AuthController.php index fcf12d5..d94da43 100644 --- a/src/Controller/Sub/AuthController.php +++ b/src/Controller/Sub/AuthController.php @@ -63,7 +63,8 @@ class AuthController { $session->setAccount($account); - return ViewHttpResponse::twig("display_auth_confirm.html.twig", ['username' => $account->getName(), 'email' => $account->getEmail()]); + $target_url = $session->getInitialTarget(); + return HttpResponse::redirect($target_url ?? "/home"); } @@ -92,7 +93,9 @@ class AuthController { $session->setAccount($account); - return ViewHttpResponse::twig("display_auth_confirm.html.twig", ['username' => $account->getName(), 'email' => $account->getEmail()]); + $target_url = $session->getInitialTarget(); + $session->setInitialTarget(null); + return HttpResponse::redirect($target_url ?? "/home"); } } diff --git a/src/Session/MutableSessionHandle.php b/src/Session/MutableSessionHandle.php index 2bcb0fc..64ee52f 100644 --- a/src/Session/MutableSessionHandle.php +++ b/src/Session/MutableSessionHandle.php @@ -5,5 +5,8 @@ namespace App\Session; use App\Data\Account; interface MutableSessionHandle extends SessionHandle { + + public function setInitialTarget(?string $url): void; + public function setAccount(Account $account): void; } diff --git a/src/Session/PhpSessionHandle.php b/src/Session/PhpSessionHandle.php index 09d862c..5568eb5 100644 --- a/src/Session/PhpSessionHandle.php +++ b/src/Session/PhpSessionHandle.php @@ -17,7 +17,15 @@ class PhpSessionHandle implements MutableSessionHandle { return $_SESSION["account"] ?? null; } + public function getInitialTarget(): ?string { + return $_SESSION["target"] ?? null; + } + public function setAccount(Account $account): void { $_SESSION["account"] = $account; } + + public function setInitialTarget(?string $url): void { + $_SESSION["target"] = $url; + } } diff --git a/src/Session/SessionHandle.php b/src/Session/SessionHandle.php index 6ffb9c0..2114877 100644 --- a/src/Session/SessionHandle.php +++ b/src/Session/SessionHandle.php @@ -5,5 +5,7 @@ namespace App\Session; use App\Data\Account; interface SessionHandle { + public function getInitialTarget(): ?string; + public function getAccount(): ?Account; } From e3875b4f15ce3b710187808420290445fe0b974c Mon Sep 17 00:00:00 2001 From: "maxime.batista" Date: Wed, 22 Nov 2023 13:02:28 +0100 Subject: [PATCH 8/8] add documentation --- Documentation/database_mcd.puml | 17 +++++++++++------ public/api/index.php | 4 +++- sql/setup-tables.sql | 10 ++-------- src/Gateway/TacticInfoGateway.php | 8 ++++---- src/Session/MutableSessionHandle.php | 10 +++++++++- src/Session/PhpSessionHandle.php | 5 ++++- src/Session/SessionHandle.php | 12 ++++++++++++ 7 files changed, 45 insertions(+), 21 deletions(-) diff --git a/Documentation/database_mcd.puml b/Documentation/database_mcd.puml index e698a69..710dfee 100644 --- a/Documentation/database_mcd.puml +++ b/Documentation/database_mcd.puml @@ -2,12 +2,10 @@ object Account { id - name - age email - phoneNumber - passwordHash - profilePicture + username + token + hash } object Team { @@ -26,7 +24,7 @@ object TacticFolder { object Tactic { id_json name - creationDate + creation_date } usecase have_team [ @@ -63,6 +61,10 @@ usecase contains_other_folder [ to contain ] +usecase owns [ + owns +] + Account "0,n" -- have_team have_team -- "1,n" Team @@ -73,6 +75,9 @@ shared_tactic_account -- "0,n" Tactic Tactic "0,n" -- shared_tactic_team shared_tactic_team -- "0,n" Team +Tactic "1,1" -- owns +owns -- Account + Team "0,n" -- shared_folder_team shared_folder_team -- "0,n"TacticFolder diff --git a/public/api/index.php b/public/api/index.php index 580f794..c5f8f2e 100644 --- a/public/api/index.php +++ b/public/api/index.php @@ -17,7 +17,6 @@ use App\Http\ViewHttpResponse; use App\Model\AuthModel; use App\Model\TacticModel; use App\Session\PhpSessionHandle; -use App\Session\SessionHandle; use App\Validation\ValidationFail; function getTacticController(): APITacticController { @@ -28,6 +27,9 @@ function getAuthController(): APIAuthController { return new APIAuthController(new AuthModel(new AccountGateway(new Connexion(get_database())))); } +/** + * A Front controller action + */ class Action { /** * @var callable(mixed[]): HttpResponse $action action to call diff --git a/sql/setup-tables.sql b/sql/setup-tables.sql index 457803c..26e58da 100644 --- a/sql/setup-tables.sql +++ b/sql/setup-tables.sql @@ -1,13 +1,7 @@ -- drop tables here -DROP TABLE IF EXISTS FormEntries; DROP TABLE IF EXISTS Account; -DROP TABLE IF EXISTS TacticInfo; +DROP TABLE IF EXISTS Tactic; -CREATE TABLE FormEntries -( - name varchar NOT NULL, - description varchar NOT NULL -); CREATE TABLE Account ( id integer PRIMARY KEY AUTOINCREMENT, @@ -17,7 +11,7 @@ CREATE TABLE Account hash varchar NOT NULL ); -CREATE TABLE TacticInfo +CREATE TABLE Tactic ( id integer PRIMARY KEY AUTOINCREMENT, name varchar NOT NULL, diff --git a/src/Gateway/TacticInfoGateway.php b/src/Gateway/TacticInfoGateway.php index efa0a7c..3075f69 100644 --- a/src/Gateway/TacticInfoGateway.php +++ b/src/Gateway/TacticInfoGateway.php @@ -18,7 +18,7 @@ class TacticInfoGateway { public function get(int $id): ?TacticInfo { $res = $this->con->fetch( - "SELECT * FROM TacticInfo WHERE id = :id", + "SELECT * FROM Tactic WHERE id = :id", [":id" => [$id, PDO::PARAM_INT]] ); @@ -33,14 +33,14 @@ class TacticInfoGateway { public function insert(string $name, int $owner): TacticInfo { $this->con->exec( - "INSERT INTO TacticInfo(name, owner) VALUES(:name, :owner)", + "INSERT INTO Tactic(name, owner) VALUES(:name, :owner)", [ ":name" => [$name, PDO::PARAM_STR], ":owner" => [$owner, PDO::PARAM_INT], ] ); $row = $this->con->fetch( - "SELECT id, creation_date, owner FROM TacticInfo WHERE :id = id", + "SELECT id, creation_date, owner FROM Tactic WHERE :id = id", [':id' => [$this->con->lastInsertId(), PDO::PARAM_INT]] )[0]; return new TacticInfo(intval($row["id"]), $name, strtotime($row["creation_date"]), $row["owner"]); @@ -48,7 +48,7 @@ class TacticInfoGateway { public function updateName(int $id, string $name): void { $this->con->exec( - "UPDATE TacticInfo SET name = :name WHERE id = :id", + "UPDATE Tactic SET name = :name WHERE id = :id", [ ":name" => [$name, PDO::PARAM_STR], ":id" => [$id, PDO::PARAM_INT], diff --git a/src/Session/MutableSessionHandle.php b/src/Session/MutableSessionHandle.php index 64ee52f..1459819 100644 --- a/src/Session/MutableSessionHandle.php +++ b/src/Session/MutableSessionHandle.php @@ -4,9 +4,17 @@ namespace App\Session; use App\Data\Account; +/** + * The mutable side of a session handle + */ interface MutableSessionHandle extends SessionHandle { - + /** + * @param string|null $url the url to redirect the user to after authentication. + */ public function setInitialTarget(?string $url): void; + /** + * @param Account $account update the session's account + */ public function setAccount(Account $account): void; } diff --git a/src/Session/PhpSessionHandle.php b/src/Session/PhpSessionHandle.php index 5568eb5..7d6b10b 100644 --- a/src/Session/PhpSessionHandle.php +++ b/src/Session/PhpSessionHandle.php @@ -4,8 +4,11 @@ namespace App\Session; use App\Data\Account; +/** + * A PHP session handle + */ class PhpSessionHandle implements MutableSessionHandle { - public static function init(): SessionHandle { + public static function init(): self { if (session_status() !== PHP_SESSION_NONE) { throw new \Exception("A php session is already started !"); } diff --git a/src/Session/SessionHandle.php b/src/Session/SessionHandle.php index 2114877..663c78c 100644 --- a/src/Session/SessionHandle.php +++ b/src/Session/SessionHandle.php @@ -4,8 +4,20 @@ namespace App\Session; use App\Data\Account; +/** + * An immutable session handle + */ interface SessionHandle { + /** + * The initial target url if the user wanted to perform an action that requires authentication + * but has been required to login first in the application. + * @return string|null Get the initial targeted URL + */ public function getInitialTarget(): ?string; + /** + * The session account if the user is logged in. + * @return Account|null + */ public function getAccount(): ?Account; }