Merge pull request 'Add Session handling' (#19) from session into salva
continuous-integration/drone/push Build is passing Details

Reviewed-on: #19
pull/18/head
Maxime BATISTA 1 year ago
commit 84a623ee6a

@ -2,12 +2,10 @@
object Account { object Account {
<u>id <u>id
name
age
email email
phoneNumber username
passwordHash token
profilePicture hash
} }
object Team { object Team {
@ -26,7 +24,7 @@ object TacticFolder {
object Tactic { object Tactic {
<u>id_json <u>id_json
name name
creationDate creation_date
} }
usecase have_team [ usecase have_team [
@ -63,6 +61,10 @@ usecase contains_other_folder [
to contain to contain
] ]
usecase owns [
owns
]
Account "0,n" -- have_team Account "0,n" -- have_team
have_team -- "1,n" Team have_team -- "1,n" Team
@ -73,6 +75,9 @@ shared_tactic_account -- "0,n" Tactic
Tactic "0,n" -- shared_tactic_team Tactic "0,n" -- shared_tactic_team
shared_tactic_team -- "0,n" Team shared_tactic_team -- "0,n" Team
Tactic "1,1" -- owns
owns -- Account
Team "0,n" -- shared_folder_team Team "0,n" -- shared_folder_team
shared_folder_team -- "0,n"TacticFolder shared_folder_team -- "0,n"TacticFolder

@ -81,7 +81,7 @@ AuthController --> "- model" AuthModel
class AuthModel{ class AuthModel{
+ register(username : string, password : string, confirmPassword : string, email : string): array + register(username : string, password : string, confirmPassword : string, email : string): array
+ getUserFields(email : string):array + getAccount(email : string):array
+ login(email : string, password : string) + login(email : string, password : string)
} }
AuthModel --> "- gateway" AuthGateway AuthModel --> "- gateway" AuthGateway
@ -89,9 +89,9 @@ AuthModel --> "- gateway" AuthGateway
class AuthGateway{ class AuthGateway{
-con : Connection -con : Connection
+ mailExist(email : string) : bool + mailExists(email : string) : bool
+ insertAccount(username : string, hash : string, email : string) + insertAccount(username : string, hash : string, email : string)
+ getUserHash(email : string):string + getHash(email : string):string
+ getUserFields (email : string): array + getAccount (email : string): array
} }
@enduml @enduml

@ -9,5 +9,7 @@ parameters:
- sql/database.php - sql/database.php
- profiles/dev-config-profile.php - profiles/dev-config-profile.php
- profiles/prod-config-profile.php - profiles/prod-config-profile.php
- public/api/index.php
excludePaths: excludePaths:
- src/react-display-file.php - src/react-display-file.php
- public/api/index.php

@ -6,32 +6,137 @@ require "../../sql/database.php";
require "../utils.php"; require "../utils.php";
use App\Connexion; use App\Connexion;
use App\Controller\Api\APIAuthController;
use App\Controller\Api\APITacticController; use App\Controller\Api\APITacticController;
use App\Data\Account;
use App\Gateway\AccountGateway;
use App\Gateway\TacticInfoGateway; use App\Gateway\TacticInfoGateway;
use App\Http\HttpResponse;
use App\Http\JsonHttpResponse; use App\Http\JsonHttpResponse;
use App\Http\ViewHttpResponse; use App\Http\ViewHttpResponse;
use App\Model\AuthModel;
use App\Model\TacticModel; use App\Model\TacticModel;
use App\Session\PhpSessionHandle;
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()))));
}
/**
* A Front controller action
*/
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 = PhpSessionHandle::init();
return $session->getAccount();
}
$token = $headers['Authorization'];
$gateway = new AccountGateway(new Connexion(get_database()));
return $gateway->getAccountFromToken($token);
}
$router = new AltoRouter(); $router = new AltoRouter();
$router->setBasePath(get_public_path() . "/api"); $router->setBasePath(get_public_path() . "/api");
$tacticEndpoint = new APITacticController(new TacticModel(new TacticInfoGateway($con))); $router->map("POST", "/tactic/[i:id]/edit/name", Action::auth(fn(int $id, Account $acc) => getTacticController()->updateName($id, $acc)));
$router->map("POST", "/tactic/[i:id]/edit/name", fn(int $id) => $tacticEndpoint->updateName($id)); $router->map("GET", "/tactic/[i:id]", Action::auth(fn(int $id, Account $acc) => getTacticController()->getTacticInfo($id, $acc)));
$router->map("GET", "/tactic/[i:id]", fn(int $id) => $tacticEndpoint->getTacticInfo($id)); $router->map("POST", "/tactic/new", Action::auth(fn(Account $acc) => getTacticController()->newTactic($acc)));
$router->map("POST", "/tactic/new", fn() => $tacticEndpoint->newTactic()); $router->map("POST", "/auth", Action::noAuth(fn() => getAuthController()->authorize()));
$match = $router->match(); $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()); http_response_code($response->getCode());
if ($response instanceof JsonHttpResponse) { if ($response instanceof JsonHttpResponse) {

@ -1,5 +1,6 @@
<?php <?php
require "../vendor/autoload.php"; require "../vendor/autoload.php";
require "../config.php"; require "../config.php";
require "../sql/database.php"; require "../sql/database.php";
@ -7,7 +8,10 @@ require "utils.php";
require "../src/react-display.php"; require "../src/react-display.php";
use App\Controller\FrontController; use App\Controller\FrontController;
use App\Session\PhpSessionHandle;
$basePath = get_public_path(); $basePath = get_public_path();
$frontController = new FrontController($basePath); $frontController = new FrontController($basePath);
$frontController->run();
$frontController->run(PhpSessionHandle::init());

@ -1,40 +1,53 @@
-- drop tables here -- drop tables here
DROP TABLE IF EXISTS FormEntries; DROP TABLE IF EXISTS Account;
DROP TABLE IF EXISTS AccountUser; DROP TABLE IF EXISTS Tactic;
DROP TABLE IF EXISTS TacticInfo;
DROP TABLE IF EXISTS Team; DROP TABLE IF EXISTS Team;
DROP TABLE IF EXISTS User; DROP TABLE IF EXISTS User;
DROP TABLE IF EXISTS Member; DROP TABLE IF EXISTS Member;
CREATE TABLE Account
(
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 FormEntries(name varchar, description varchar); CREATE TABLE Tactic
CREATE TABLE AccountUser( (
username varchar, id integer PRIMARY KEY AUTOINCREMENT,
hash varchar, name varchar NOT NULL,
email varchar unique creation_date timestamp DEFAULT CURRENT_TIMESTAMP NOT NULL,
owner integer NOT NULL,
FOREIGN KEY (owner) REFERENCES Account
); );
CREATE TABLE Team( CREATE TABLE Team
id integer PRIMARY KEY AUTOINCREMENT, (
name varchar, id integer PRIMARY KEY AUTOINCREMENT,
picture varchar, name varchar,
mainColor varchar, picture varchar,
mainColor varchar,
secondColor varchar secondColor varchar
); );
CREATE TABLE User( CREATE TABLE User
(
id integer PRIMARY KEY AUTOINCREMENT id integer PRIMARY KEY AUTOINCREMENT
); );
CREATE TABLE Member( CREATE TABLE Member
idTeam integer, (
idTeam integer,
idMember integer, idMember integer,
role char(1) CHECK (role IN ('C','P')), role char(1) CHECK (role IN ('C', 'P')),
FOREIGN KEY (idTeam) REFERENCES Team(id), FOREIGN KEY (idTeam) REFERENCES Team (id),
FOREIGN KEY (idMember) REFERENCES User(id) FOREIGN KEY (idMember) REFERENCES User (id)
); );
CREATE TABLE TacticInfo( CREATE TABLE TacticInfo
id integer PRIMARY KEY AUTOINCREMENT, (
name varchar, id integer PRIMARY KEY AUTOINCREMENT,
name varchar,
creation_date timestamp DEFAULT CURRENT_TIMESTAMP creation_date timestamp DEFAULT CURRENT_TIMESTAMP
); );

@ -0,0 +1,39 @@
<?php
namespace App\Controller\Api;
use App\Controller\Control;
use App\Http\HttpRequest;
use App\Http\HttpResponse;
use App\Http\JsonHttpResponse;
use App\Model\AuthModel;
use App\Validation\Validators;
class APIAuthController {
private AuthModel $model;
/**
* @param AuthModel $model
*/
public function __construct(AuthModel $model) {
$this->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);
}
}

@ -3,6 +3,7 @@
namespace App\Controller\Api; namespace App\Controller\Api;
use App\Controller\Control; use App\Controller\Control;
use App\Data\Account;
use App\Http\HttpCodes; use App\Http\HttpCodes;
use App\Http\HttpRequest; use App\Http\HttpRequest;
use App\Http\HttpResponse; use App\Http\HttpResponse;
@ -23,26 +24,33 @@ class APITacticController {
$this->model = $model; $this->model = $model;
} }
public function updateName(int $tactic_id): HttpResponse { public function updateName(int $tactic_id, Account $account): HttpResponse {
return Control::runChecked([ return Control::runChecked([
"name" => [Validators::lenBetween(1, 50), Validators::nameWithSpaces()], "name" => [Validators::lenBetween(1, 50), Validators::nameWithSpaces()],
], function (HttpRequest $request) use ($tactic_id) { ], function (HttpRequest $request) use ($tactic_id, $account) {
$this->model->updateName($tactic_id, $request["name"]);
$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); return HttpResponse::fromCode(HttpCodes::OK);
}, true); }, true);
} }
public function newTactic(): HttpResponse { public function newTactic(Account $account): HttpResponse {
return Control::runChecked([ return Control::runChecked([
"name" => [Validators::lenBetween(1, 50), Validators::nameWithSpaces()], "name" => [Validators::lenBetween(1, 50), Validators::nameWithSpaces()],
], function (HttpRequest $request) { ], function (HttpRequest $request) use ($account) {
$tactic = $this->model->makeNew($request["name"]); $tactic = $this->model->makeNew($request["name"], $account->getId());
$id = $tactic->getId(); $id = $tactic->getId();
return new JsonHttpResponse(["id" => $id]); return new JsonHttpResponse(["id" => $id]);
}, true); }, true);
} }
public function getTacticInfo(int $id): HttpResponse { public function getTacticInfo(int $id, Account $account): HttpResponse {
$tactic_info = $this->model->get($id); $tactic_info = $this->model->get($id);
if ($tactic_info == null) { if ($tactic_info == null) {

@ -7,6 +7,8 @@ use App\Http\HttpCodes;
use App\Http\HttpResponse; use App\Http\HttpResponse;
use App\Http\JsonHttpResponse; use App\Http\JsonHttpResponse;
use App\Http\ViewHttpResponse; use App\Http\ViewHttpResponse;
use App\Session\MutableSessionHandle;
use App\Validation\ValidationFail;
use Exception; use Exception;
use Twig\Environment; use Twig\Environment;
use Twig\Error\LoaderError; use Twig\Error\LoaderError;
@ -16,24 +18,35 @@ use Twig\Loader\FilesystemLoader;
class FrontController { class FrontController {
private AltoRouter $router; private AltoRouter $router;
private string $basePath;
private const USER_CONTROLLER = "UserController";
private const VISITOR_CONTROLLER = "VisitorController";
public function __construct(string $basePath) { public function __construct(string $basePath) {
$this->router = $this->createRouter($basePath); $this->router = $this->createRouter($basePath);
$this->initializeRouterMap(); $this->initializeRouterMap();
$this->basePath = $basePath;
} }
/** /**
* Main behavior of the FrontController * @param MutableSessionHandle $session
*
* @return void * @return void
* @throws LoaderError
* @throws RuntimeError
* @throws SyntaxError
*/ */
public function run(): void { public function run(MutableSessionHandle $session): void {
$match = $this->router->match(); $match = $this->router->match();
if ($match != null) { if ($match) {
$this->handleMatch($match); $this->handleMatch($match, $session);
} else { return;
$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));
} }
/** /**
@ -54,36 +67,53 @@ class FrontController {
* @return void * @return void
*/ */
private function initializeRouterMap(): void { private function initializeRouterMap(): void {
$this->router->map("GET", "/", "UserController"); $this->router->map("GET", "/home", self::USER_CONTROLLER);
$this->router->map("GET|POST", "/[a:action]?/[i:id]", "UserController"); $this->router->map("GET|POST", "/user/[a:action]/[i:idTactic]?", self::USER_CONTROLLER);
$this->router->map("GET|POST", "/tactic/[a:action]/[i:idTactic]?", "UserController"); $this->router->map("GET|POST", "/visitor/[a:action]", self::VISITOR_CONTROLLER);
} }
/** /**
* @param array<string, mixed> $match * @param array<string, mixed> $match
* @param MutableSessionHandle $session
* @return void * @return void
*/ */
private function handleMatch(array $match): void { private function handleMatch(array $match, MutableSessionHandle $session): void {
$tag = $match['target']; $tag = $match['target'];
$action = $this->getAction($match); $action = $this->getAction($match);
$params = $match["params"]; $params = $match["params"];
unset($params['action']); 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 $controllerName
* @param string $action * @param string $action
* @param array<int, mixed> $params * @param array<int, mixed> $params
* @param MutableSessionHandle $session
* @return HttpResponse * @return HttpResponse
*/ */
private function tryToCall(string $controller, string $action, array $params): HttpResponse { private function tryToCall(string $controllerName, string $action, array $params, MutableSessionHandle $session): HttpResponse {
$controller = $this->getController($controller); 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");
}
}
$controller = $this->getController($controllerName);
if (is_callable([$controller, $action])) { 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); return call_user_func_array([$controller, $action], $params);
} else { } else {
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);
} }
} }
@ -97,7 +127,7 @@ class FrontController {
if (isset($match["params"]["action"])) { if (isset($match["params"]["action"])) {
return $match["params"]["action"]; return $match["params"]["action"];
} }
return "default"; return "home";
} }
/** /**
@ -120,6 +150,11 @@ class FrontController {
*/ */
private function handleResponseByType(HttpResponse $response): void { private function handleResponseByType(HttpResponse $response): void {
http_response_code($response->getCode()); http_response_code($response->getCode());
foreach ($response->getHeaders() as $header => $value) {
header("$header: $value");
}
if ($response instanceof ViewHttpResponse) { if ($response instanceof ViewHttpResponse) {
$this->displayViewByKind($response); $this->displayViewByKind($response);
} elseif ($response instanceof JsonHttpResponse) { } elseif ($response instanceof JsonHttpResponse) {

@ -6,6 +6,7 @@ use App\Http\HttpRequest;
use App\Http\HttpResponse; use App\Http\HttpResponse;
use App\Http\ViewHttpResponse; use App\Http\ViewHttpResponse;
use App\Model\AuthModel; use App\Model\AuthModel;
use App\Session\MutableSessionHandle;
use App\Validation\FieldValidationFail; use App\Validation\FieldValidationFail;
use App\Validation\ValidationFail; use App\Validation\ValidationFail;
use App\Validation\Validators; use App\Validation\Validators;
@ -41,25 +42,29 @@ class AuthController {
/** /**
* @param mixed[] $request * @param mixed[] $request
* @param MutableSessionHandle $session
* @return HttpResponse * @return HttpResponse
*/ */
public function confirmRegister(array $request): HttpResponse { public function confirmRegister(array $request, MutableSessionHandle $session): HttpResponse {
$fails = []; $fails = [];
$request = HttpRequest::from($request, $fails, [ $request = HttpRequest::from($request, $fails, [
"username" => [Validators::name(), Validators::lenBetween(2, 32)], "username" => [Validators::name(), Validators::lenBetween(2, 32)],
"password" => [Validators::lenBetween(6, 256)], "password" => [Validators::lenBetween(6, 256)],
"confirmpassword" => [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)) { if (!empty($fails)) {
return $this->displayBadFields("display_register.html.twig", $fails); return $this->displayBadFields("display_register.html.twig", $fails);
} }
$fails = $this->model->register($request['username'], $request["password"], $request['confirmpassword'], $request['email']); $account = $this->model->register($request['username'], $request["password"], $request['confirmpassword'], $request['email'], $fails);
if (empty($fails)) { if (!empty($fails)) {
$results = $this->model->getUserFields($request['email']); return $this->displayBadFields("display_register.html.twig", $fails);
return ViewHttpResponse::twig("display_auth_confirm.html.twig", ['username' => $results['username'], 'email' => $results['email']]);
} }
return $this->displayBadFields("display_register.html.twig", $fails);
$session->setAccount($account);
$target_url = $session->getInitialTarget();
return HttpResponse::redirect($target_url ?? "/home");
} }
@ -71,22 +76,26 @@ class AuthController {
* @param mixed[] $request * @param mixed[] $request
* @return HttpResponse * @return HttpResponse
*/ */
public function confirmLogin(array $request): HttpResponse { public function confirmLogin(array $request, MutableSessionHandle $session): HttpResponse {
$fails = []; $fails = [];
$request = HttpRequest::from($request, $fails, [ $request = HttpRequest::from($request, $fails, [
"password" => [Validators::lenBetween(6, 256)], "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)) { if (!empty($fails)) {
return $this->displayBadFields("display_login.html.twig", $fails); return $this->displayBadFields("display_login.html.twig", $fails);
} }
$fails = $this->model->login($request['email'], $request['password']); $account = $this->model->login($request['email'], $request['password'], $fails);
if (empty($fails)) { if (!empty($fails)) {
$results = $this->model->getUserFields($request['email']); return $this->displayBadFields("display_login.html.twig", $fails);
return ViewHttpResponse::twig("display_auth_confirm.html.twig", ['username' => $results['username'], 'email' => $results['email']]);
} }
return $this->displayBadFields("display_login.html.twig", $fails);
$session->setAccount($account);
$target_url = $session->getInitialTarget();
$session->setInitialTarget(null);
return HttpResponse::redirect($target_url ?? "/home");
} }
} }

@ -3,6 +3,7 @@
namespace App\Controller\Sub; namespace App\Controller\Sub;
use App\Connexion; use App\Connexion;
use App\Controller\VisitorController;
use App\Data\TacticInfo; use App\Data\TacticInfo;
use App\Gateway\TacticInfoGateway; use App\Gateway\TacticInfoGateway;
use App\Http\HttpCodes; use App\Http\HttpCodes;
@ -10,6 +11,9 @@ use App\Http\HttpResponse;
use App\Http\JsonHttpResponse; use App\Http\JsonHttpResponse;
use App\Http\ViewHttpResponse; use App\Http\ViewHttpResponse;
use App\Model\TacticModel; use App\Model\TacticModel;
use App\Session\SessionHandle;
use App\Validation\ValidationFail;
use App\Validator\TacticValidator;
class EditorController { class EditorController {
private TacticModel $model; private TacticModel $model;
@ -22,24 +26,28 @@ class EditorController {
return ViewHttpResponse::react("views/Editor.tsx", ["name" => $tactic->getName(), "id" => $tactic->getId()]); return ViewHttpResponse::react("views/Editor.tsx", ["name" => $tactic->getName(), "id" => $tactic->getId()]);
} }
public function createNew(): HttpResponse { public function createNew(SessionHandle $session): HttpResponse {
$tactic = $this->model->makeNewDefault(); $tactic = $this->model->makeNewDefault($session->getAccount()->getId());
return $this->openEditor($tactic); return $this->openEditor($tactic);
} }
/** /**
* returns an editor view for a given tactic * returns an editor view for a given tactic
* @param int $id the targeted tactic identifier * @param int $id the targeted tactic identifier
* @param SessionHandle $session
* @return HttpResponse * @return HttpResponse
*/ */
public function edit(int $id): HttpResponse { public function edit(int $id, SessionHandle $session): HttpResponse {
$tactic = $this->model->get($id); $tactic = $this->model->get($id);
if ($tactic == null) { $failure = TacticValidator::validateAccess($tactic, $id, $session->getAccount()->getId());
return new JsonHttpResponse("la tactique " . $id . " n'existe pas", HttpCodes::NOT_FOUND);
if ($failure != null) {
return ViewHttpResponse::twig('error.html.twig', ['failures' => [$failure]], HttpCodes::NOT_FOUND);
} }
return $this->openEditor($tactic); return $this->openEditor($tactic);
} }
} }

@ -7,6 +7,9 @@ use App\Http\HttpResponse;
use App\Http\JsonHttpResponse; use App\Http\JsonHttpResponse;
use App\Http\ViewHttpResponse; use App\Http\ViewHttpResponse;
use App\Model\TacticModel; use App\Model\TacticModel;
use App\Session\SessionHandle;
use App\Validation\ValidationFail;
use App\Validator\TacticValidator;
class VisualizerController { class VisualizerController {
private TacticModel $tacticModel; private TacticModel $tacticModel;
@ -19,11 +22,13 @@ class VisualizerController {
$this->tacticModel = $tacticModel; $this->tacticModel = $tacticModel;
} }
public function visualize(int $id): HttpResponse { public function visualize(int $id, SessionHandle $session): HttpResponse {
$tactic = $this->tacticModel->get($id); $tactic = $this->tacticModel->get($id);
if ($tactic == null) { $failure = TacticValidator::validateAccess($tactic, $id, $session->getAccount()->getId());
return new JsonHttpResponse("la tactique " . $id . " n'existe pas", HttpCodes::NOT_FOUND);
if ($failure != null) {
return ViewHttpResponse::twig('error.html.twig', ['failures' => [$failure]], HttpCodes::NOT_FOUND);
} }
return ViewHttpResponse::react("views/Visualizer.tsx", ["name" => $tactic->getName()]); return ViewHttpResponse::react("views/Visualizer.tsx", ["name" => $tactic->getName()]);

@ -3,49 +3,38 @@
namespace App\Controller; namespace App\Controller;
use App\Connexion; use App\Connexion;
use App\Gateway\AuthGateway;
use App\Gateway\TacticInfoGateway; use App\Gateway\TacticInfoGateway;
use App\Gateway\TeamGateway; use App\Gateway\TeamGateway;
use App\Http\HttpResponse; use App\Http\HttpResponse;
use App\Http\ViewHttpResponse; use App\Http\ViewHttpResponse;
use App\Model\AuthModel;
use App\Model\TacticModel; use App\Model\TacticModel;
use App\Model\TeamModel; use App\Model\TeamModel;
use App\Session\SessionHandle;
class UserController extends VisitorController {
class UserController {
public function home(): HttpResponse { public function home(): HttpResponse {
return ViewHttpResponse::twig("home.twig", []); return ViewHttpResponse::twig("home.twig", []);
} }
public function register(): HttpResponse { public function view(int $id, SessionHandle $session): HttpResponse {
$model = new AuthModel(new AuthGateway(new Connexion(get_database()))); $model = new TacticModel(new TacticInfoGateway(new Connexion(get_database())));
if ($_SERVER['REQUEST_METHOD'] === 'GET') { return (new Sub\VisualizerController($model))->visualize($id, $session);
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 { public function edit(int $id, SessionHandle $session): HttpResponse {
$model = new TacticModel(new TacticInfoGateway(new Connexion(get_database()))); $model = new TacticModel(new TacticInfoGateway(new Connexion(get_database())));
return (new Sub\VisualizerController($model))->visualize($id); return (new Sub\EditorController($model))->edit($id, $session);
} }
public function edit(int $id): HttpResponse { public function create(SessionHandle $session): HttpResponse {
$model = new TacticModel(new TacticInfoGateway(new Connexion(get_database()))); $model = new TacticModel(new TacticInfoGateway(new Connexion(get_database())));
return (new Sub\EditorController($model))->edit($id); return (new Sub\EditorController($model))->createNew($session);
} }
public function create(): HttpResponse { public function open(int $id, SessionHandle $session): HttpResponse {
$model = new TacticModel(new TacticInfoGateway(new Connexion(get_database()))); $model = new TacticModel(new TacticInfoGateway(new Connexion(get_database())));
return (new Sub\EditorController($model))->createNew(); return (new Sub\VisualizerController($model))->visualize($id, $session);
} }
public function createTeam(): HttpResponse { public function createTeam(): HttpResponse {

@ -0,0 +1,28 @@
<?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 {
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();
}
return (new Sub\AuthController($model))->confirmRegister($_POST, $session);
}
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();
}
return (new Sub\AuthController($model))->confirmLogin($_POST, $session);
}
}

@ -4,8 +4,6 @@ namespace App\Data;
use http\Exception\InvalidArgumentException; use http\Exception\InvalidArgumentException;
const PHONE_NUMBER_REGEXP = "/^\\+[0-9]+$/";
/** /**
* Base class of a user account. * Base class of a user account.
* Contains the private information that we don't want * Contains the private information that we don't want
@ -16,90 +14,50 @@ class Account {
* @var string $email account's mail address * @var string $email account's mail address
*/ */
private string $email; 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; private string $name;
/** /**
* @var int account's unique identifier * @var int
*/ */
private int $id; private int $id;
/** /**
* @param string $email * @param string $email
* @param string $phoneNumber * @param string $name
* @param AccountUser $user * @param string $token
* @param Team[] $teams
* @param int $id * @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, int $id) {
$this->email = $email; $this->email = $email;
$this->phoneNumber = $phoneNumber; $this->name = $name;
$this->user = $user; $this->token = $token;
$this->teams = $teams;
$this->id = $id; $this->id = $id;
} }
/** public function getId(): int {
* @return string return $this->id;
*/
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 getEmail(): string {
* @param string $phoneNumber return $this->email;
*/
public function setPhoneNumber(string $phoneNumber): void {
if (!preg_match(PHONE_NUMBER_REGEXP, $phoneNumber)) {
throw new InvalidArgumentException("Invalid phone number");
}
$this->phoneNumber = $phoneNumber;
} }
public function getId(): int { public function getToken(): string {
return $this->id; return $this->token;
} }
/** public function getName(): string {
* @return Team[] return $this->name;
*/
public function getTeams(): array {
return $this->teams;
} }
public function getUser(): AccountUser {
return $this->user;
}
} }

@ -7,14 +7,18 @@ class TacticInfo implements \JsonSerializable {
private string $name; private string $name;
private int $creation_date; private int $creation_date;
private int $ownerId;
/** /**
* @param int $id * @param int $id
* @param string $name * @param string $name
* @param int $creation_date * @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->id = $id;
$this->name = $name; $this->name = $name;
$this->ownerId = $ownerId;
$this->creation_date = $creation_date; $this->creation_date = $creation_date;
} }
@ -26,6 +30,13 @@ class TacticInfo implements \JsonSerializable {
return $this->name; return $this->name;
} }
/**
* @return int
*/
public function getOwnerId(): int {
return $this->ownerId;
}
public function getCreationTimestamp(): int { public function getCreationTimestamp(): int {
return $this->creation_date; return $this->creation_date;
} }

@ -0,0 +1,74 @@
<?php
namespace App\Gateway;
use App\Connexion;
use App\Data\Account;
use PDO;
class AccountGateway {
private Connexion $con;
/**
* @param Connexion $con
*/
public function __construct(Connexion $con) {
$this->con = $con;
}
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],
]);
return intval($this->con->lastInsertId());
}
/**
* @param string $email
* @return array<string, mixed>|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 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;
}
return new Account($acc["email"], $acc["username"], $acc["token"], $acc["id"]);
}
}

@ -1,47 +0,0 @@
<?php
namespace App\Gateway;
use App\Connexion;
use PDO;
class AuthGateway {
private Connexion $con;
/**
* @param Connexion $con
*/
public function __construct(Connexion $con) {
$this->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<string,string>|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;
}
}

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

@ -18,7 +18,7 @@ class TacticInfoGateway {
public function get(int $id): ?TacticInfo { public function get(int $id): ?TacticInfo {
$res = $this->con->fetch( $res = $this->con->fetch(
"SELECT * FROM TacticInfo WHERE id = :id", "SELECT * FROM Tactic WHERE id = :id",
[":id" => [$id, PDO::PARAM_INT]] [":id" => [$id, PDO::PARAM_INT]]
); );
@ -28,24 +28,27 @@ class TacticInfoGateway {
$row = $res[0]; $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( $this->con->exec(
"INSERT INTO TacticInfo(name) VALUES(:name)", "INSERT INTO Tactic(name, owner) VALUES(:name, :owner)",
[":name" => [$name, PDO::PARAM_STR]] [
":name" => [$name, PDO::PARAM_STR],
":owner" => [$owner, PDO::PARAM_INT],
]
); );
$row = $this->con->fetch( $row = $this->con->fetch(
"SELECT id, creation_date FROM TacticInfo WHERE :id = id", "SELECT id, creation_date, owner FROM Tactic WHERE :id = id",
[':id' => [$this->con->lastInsertId(), PDO::PARAM_INT]] [':id' => [$this->con->lastInsertId(), PDO::PARAM_INT]]
)[0]; )[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 { public function updateName(int $id, string $name): void {
$this->con->exec( $this->con->exec(
"UPDATE TacticInfo SET name = :name WHERE id = :id", "UPDATE Tactic SET name = :name WHERE id = :id",
[ [
":name" => [$name, PDO::PARAM_STR], ":name" => [$name, PDO::PARAM_STR],
":id" => [$id, PDO::PARAM_INT], ":id" => [$id, PDO::PARAM_INT],

@ -7,7 +7,11 @@ namespace App\Http;
*/ */
class HttpCodes { class HttpCodes {
public const OK = 200; public const OK = 200;
public const FOUND = 302;
public const BAD_REQUEST = 400; public const BAD_REQUEST = 400;
public const FORBIDDEN = 403;
public const NOT_FOUND = 404; public const NOT_FOUND = 404;
} }

@ -3,21 +3,41 @@
namespace App\Http; namespace App\Http;
class HttpResponse { class HttpResponse {
/**
* @var array<string, string>
*/
private array $headers;
private int $code; private int $code;
/** /**
* @param 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->code = $code;
$this->headers = $headers;
} }
public function getCode(): int { public function getCode(): int {
return $this->code; return $this->code;
} }
/**
* @return array<string, string>
*/
public function getHeaders(): array {
return $this->headers;
}
public static function fromCode(int $code): HttpResponse { 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 * @param mixed $payload
*/ */
public function __construct($payload, int $code = HttpCodes::OK) { public function __construct($payload, int $code = HttpCodes::OK) {
parent::__construct($code); parent::__construct($code, []);
$this->payload = $payload; $this->payload = $payload;
} }

@ -26,7 +26,7 @@ class ViewHttpResponse extends HttpResponse {
* @param array<string, mixed> $arguments * @param array<string, mixed> $arguments
*/ */
private function __construct(int $kind, string $file, array $arguments, int $code = HttpCodes::OK) { private function __construct(int $kind, string $file, array $arguments, int $code = HttpCodes::OK) {
parent::__construct($code); parent::__construct($code, []);
$this->kind = $kind; $this->kind = $kind;
$this->file = $file; $this->file = $file;
$this->arguments = $arguments; $this->arguments = $arguments;

@ -2,16 +2,18 @@
namespace App\Model; namespace App\Model;
use App\Gateway\AuthGateway; use App\Data\Account;
use App\Gateway\AccountGateway;
use App\Validation\FieldValidationFail; use App\Validation\FieldValidationFail;
use App\Validation\ValidationFail; use App\Validation\ValidationFail;
class AuthModel { 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; $this->gateway = $gateway;
} }
@ -21,59 +23,54 @@ class AuthModel {
* @param string $password * @param string $password
* @param string $confirmPassword * @param string $confirmPassword
* @param string $email * @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 { public function register(string $username, string $password, string $confirmPassword, string $email, array &$failures): ?Account {
$errors = [];
if ($password != $confirmPassword) { 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)) { if ($this->gateway->exists($email)) {
$errors[] = new FieldValidationFail("email", "email already exist"); $failures[] = new FieldValidationFail("email", "email already exist");
} }
if(empty($errors)) { if (!empty($failures)) {
$hash = password_hash($password, PASSWORD_DEFAULT); return null;
$this->gateway->insertAccount($username, $hash, $email);
} }
return $errors; $hash = password_hash($password, PASSWORD_DEFAULT);
}
/** $token = $this->generateToken();
* @param string $email $accountId = $this->gateway->insertAccount($username, $email, $token, $hash);
* @return array<string,string>|null return new Account($email, $username, $token, $accountId);
*/
public function getUserFields(string $email): ?array {
return $this->gateway->getUserFields($email);
} }
private function generateToken(): string {
return base64_encode(random_bytes(64));
}
/** /**
* @param string $email * @param string $email
* @param string $password * @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 { public function login(string $email, string $password, array &$failures): ?Account {
$errors = []; if (!$this->gateway->exists($email)) {
$failures[] = new FieldValidationFail("email", "email doesnt exists");
if (!$this->gateway->mailExist($email)) { return null;
$errors[] = new FieldValidationFail("email", "email doesnt exists");
return $errors;
} }
$hash = $this->gateway->getUserHash($email); $hash = $this->gateway->getHash($email);
if (!password_verify($password, $hash)) { if (!password_verify($password, $hash)) {
$errors[] = new FieldValidationFail("password", "invalid password"); $failures[] = new FieldValidationFail("password", "invalid password");
return null;
} }
return $errors; return $this->gateway->getAccountFromMail($email);
} }
} }

@ -2,8 +2,10 @@
namespace App\Model; namespace App\Model;
use App\Data\Account;
use App\Data\TacticInfo; use App\Data\TacticInfo;
use App\Gateway\TacticInfoGateway; use App\Gateway\TacticInfoGateway;
use App\Validation\ValidationFail;
class TacticModel { class TacticModel {
public const TACTIC_DEFAULT_NAME = "Nouvelle tactique"; public const TACTIC_DEFAULT_NAME = "Nouvelle tactique";
@ -18,12 +20,12 @@ class TacticModel {
$this->tactics = $tactics; $this->tactics = $tactics;
} }
public function makeNew(string $name): TacticInfo { public function makeNew(string $name, int $ownerId): TacticInfo {
return $this->tactics->insert($name); return $this->tactics->insert($name, $ownerId);
} }
public function makeNewDefault(): ?TacticInfo { public function makeNewDefault(int $ownerId): ?TacticInfo {
return $this->tactics->insert(self::TACTIC_DEFAULT_NAME); return $this->tactics->insert(self::TACTIC_DEFAULT_NAME, $ownerId);
} }
/** /**
@ -39,15 +41,22 @@ class TacticModel {
* Update the name of a tactic * Update the name of a tactic
* @param int $id the tactic identifier * @param int $id the tactic identifier
* @param string $name the new name to set * @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 { public function updateName(int $id, string $name, int $authId): array {
if ($this->tactics->get($id) == null) {
return false; $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); $this->tactics->updateName($id, $name);
return true; return [];
} }
} }

@ -0,0 +1,20 @@
<?php
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;
}

@ -0,0 +1,34 @@
<?php
namespace App\Session;
use App\Data\Account;
/**
* A PHP session handle
*/
class PhpSessionHandle implements MutableSessionHandle {
public static function init(): self {
if (session_status() !== PHP_SESSION_NONE) {
throw new \Exception("A php session is already started !");
}
session_start();
return new PhpSessionHandle();
}
public function getAccount(): ?Account {
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;
}
}

@ -0,0 +1,23 @@
<?php
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;
}

@ -34,7 +34,11 @@ class ValidationFail implements JsonSerializable {
} }
public static function notFound(string $message): ValidationFail { public static function notFound(string $message): ValidationFail {
return new ValidationFail("not found", $message); return new ValidationFail("Not found", $message);
}
public static function unauthorized(string $message = "Unauthorized"): ValidationFail {
return new ValidationFail("Unauthorized", $message);
} }
} }

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

@ -51,7 +51,7 @@
{% endfor %} {% 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> </body>
</html> </html>
Loading…
Cancel
Save