integrate sessions for tactics

pull/19/head
Override-6 1 year ago committed by maxime.batista
parent 3d7eb7bbb1
commit 982acf5e09

@ -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
name varchar NOT NULL,
creation_date timestamp DEFAULT CURRENT_TIMESTAMP NOT NULL,
owner integer NOT NULL,
FOREIGN KEY (owner) REFERENCES Account
);

@ -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<string, mixed> $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<int, mixed> $params
* @param MutableSessionHandle $session
* @return HttpResponse
*/
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");
}
}
$controller = $this->getController($controllerName);
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);
}
} catch (Exception $e) {
return ViewHttpResponse::twig("error.html.twig", [], HttpCodes::NOT_FOUND);
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) {

@ -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()]);
}

@ -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);
}
}

@ -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()]);

@ -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);
}
}

@ -0,0 +1,30 @@
<?php
namespace App\Controller;
use App\Connexion;
use App\Gateway\AccountGateway;
use App\Http\HttpResponse;
use App\Model\AuthModel;
use App\Session\MutableSessionHandle;
class VisitorController {
public final 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();
}
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);
}
}

@ -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 {

@ -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;
}

@ -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"]);
}

@ -1,35 +0,0 @@
<?php
namespace App\Gateway;
use PDO;
use App\Connexion;
/**
* A sample gateway, that stores the sample form's result.
*/
class FormResultGateway {
private Connexion $con;
public function __construct(Connexion $con) {
$this->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<string, mixed>
*/
public function listResults(): array {
return $this->con->fetch("SELECT * FROM FormEntries", []);
}
}

@ -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 {

@ -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;
}

@ -3,21 +3,42 @@
namespace App\Http;
class HttpResponse {
/**
* @var array<string, string>
*/
private array $headers;
private int $code;
/**
* @param int $code
* @param array<string, string> $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<string, string>
*/
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]);
}
}

@ -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;
}

@ -26,7 +26,7 @@ class ViewHttpResponse extends HttpResponse {
* @param array<string, mixed> $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;

@ -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;
}

@ -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);
}
/**

@ -5,5 +5,6 @@ namespace App\Session;
use App\Data\Account;
interface MutableSessionHandle extends SessionHandle {
public function setAccount(Account $account): void;
}

@ -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);
}
}

@ -0,0 +1,22 @@
<?php
namespace App\Validator;
use App\Data\TacticInfo;
use App\Validation\ValidationFail;
class TacticValidator {
public static function validateAccess(?TacticInfo $tactic, int $ownerId): ?ValidationFail {
if ($tactic == null) {
return ValidationFail::notFound("La tactique " . $tactic->getId() . " n'existe pas");
}
if ($tactic->getOwnerId() != $ownerId) {
return new ValidationFail("Unauthorized", "Vous ne pouvez pas accéder à cette tactique.",);
}
return null;
}
}

@ -51,7 +51,7 @@
{% endfor %}
<button class="button" onclick="location.href='/'" type="button">Retour à la page d'accueil</button>
<button class="button" onclick="location.href='/home'" type="button">Retour à la page d'accueil</button>
</body>
</html>
Loading…
Cancel
Save