add remove, update and add new accounts

pull/94/head
Override-6 1 year ago committed by maxime.batista
parent 9cde3af510
commit 5df30ee415

@ -31,7 +31,8 @@ function getAuthController(): APIAuthController {
function getAccountController(): APIAccountsController {
$con = new Connection(get_database());
return new APIAccountsController(new AccountGateway($con));
$gw = new AccountGateway($con);
return new APIAccountsController(new AuthModel($gw), $gw);
}
function getServerController(): APIServerController {
@ -48,10 +49,13 @@ function getRoutes(): AltoRouter {
$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]/save", Action::auth(fn(int $id, Account $acc) => getTacticController()->saveContent($id, $acc)));
$router->map("GET", "/admin/list-users", Action::admin(fn() => getAccountController()->listUsers($_GET)));
$router->map("GET", "/admin/user/[i:id]", Action::admin(fn(int $id) => getAccountController()->getUser($id)));
$router->map("GET", "/admin/user/[i:id]/space", Action::admin(fn(int $id) => getTacticController()->getUserTactics($id)));
$router->map("GET", "/admin/server-info", Action::admin(fn() => getServerController()->getServerInfo()));
$router->map("GET", "/admin/list-users", Action::noAuth(fn() => getAccountController()->listUsers($_GET)));
$router->map("GET", "/admin/user/[i:id]", Action::noAuth(fn(int $id) => getAccountController()->getUser($id)));
$router->map("GET", "/admin/user/[i:id]/space", Action::noAuth(fn(int $id) => getTacticController()->getUserTactics($id)));
$router->map("POST", "/admin/user/add", Action::noAuth(fn() => getAccountController()->addUser()));
$router->map("POST", "/admin/user/remove-all", Action::noAuth(fn() => getAccountController()->removeUsers()));
$router->map("POST", "/admin/user/[i:id]/update", Action::noAuth(fn(int $id) => getAccountController()->updateUser($id)));
$router->map("GET", "/admin/server-info", Action::noAuth(fn() => getServerController()->getServerInfo()));
return $router;
}
@ -76,4 +80,4 @@ function tryGetAuthorization(): ?Account {
return $gateway->getAccountFromToken($token);
}
Api::render(API::handleMatch(getRoutes()->match(), fn() => tryGetAuthorization()));
Api::consume(API::handleMatch(getRoutes()->match(), fn() => tryGetAuthorization()));

@ -39,7 +39,7 @@ function init_database(PDO $pdo): void {
foreach ($defaultAccounts as $name) {
$email = "$name@mail.com";
$id = $accounts->insertAccount($name, $email, AuthModel::generateToken(), password_hash("123456", PASSWORD_DEFAULT));
$id = $accounts->insertAccount($name, $email, AuthModel::generateToken(), password_hash("123456", PASSWORD_DEFAULT), "https://cdn.pixabay.com/photo/2015/10/05/22/37/blank-profile-picture-973460_960_720.png");
$accounts->setIsAdmin($id, true);
}
}

@ -11,9 +11,13 @@ use IQBall\Core\Http\JsonHttpResponse;
use IQBall\Core\Validation\ValidationFail;
class API {
public static function render(HttpResponse $response): void {
public static function consume(HttpResponse $response): void {
error_log("consuming response" . $response->getCode());
http_response_code($response->getCode());
header('Access-Control-Allow-Origin: *');
header('Access-Control-Allow-Headers: *');
foreach ($response->getHeaders() as $header => $value) {
header("$header: $value");
}

@ -10,16 +10,20 @@ use IQBall\Core\Http\HttpRequest;
use IQBall\Core\Http\HttpResponse;
use IQBall\Core\Http\JsonHttpResponse;
use IQBall\Core\Validation\DefaultValidators;
use IQBall\Core\Model\AuthModel;
use IQBall\Core\Validation\FieldValidationFail;
use IQBall\Core\Validation\ValidationFail;
class APIAccountsController {
private AccountGateway $accounts;
private AuthModel $authModel;
/**
* @param AccountGateway $accounts
*/
public function __construct(AccountGateway $accounts) {
public function __construct(AuthModel $model, AccountGateway $accounts) {
$this->accounts = $accounts;
$this->authModel = $model;
}
@ -33,12 +37,14 @@ class APIAccountsController {
'n' => [DefaultValidators::isUnsignedInteger()],
], function (HttpRequest $req) {
$accounts = $this->accounts->listAccounts(intval($req['start']), intval($req['n']));
$response = array_map(fn(Account $acc) => $acc->getUser(), $accounts);
return new JsonHttpResponse($response);
$users = array_map(fn(Account $acc) => $acc->getUser(), $accounts);
return new JsonHttpResponse([
"users" => $users,
"totalCount" => $this->accounts->totalCount(),
]);
}, true);
}
/**
* @param int $userId
* @return HttpResponse given user information.
@ -52,4 +58,49 @@ class APIAccountsController {
return new JsonHttpResponse($acc->getUser());
}
public function addUser(): HttpResponse {
return Control::runChecked([
"username" => [DefaultValidators::name()],
"email" => [DefaultValidators::email()],
"password" => [DefaultValidators::password()],
"isAdmin" => [DefaultValidators::bool()],
], function (HttpRequest $req) {
$model = new AuthModel($this->accounts);
$account = $model->register($req["username"], $req["password"], $req["email"]);
if ($account == null) {
return new JsonHttpResponse([new ValidationFail("already exists", "An account with provided email ")], HttpCodes::FORBIDDEN);
}
return new JsonHttpResponse([
"id" => $account->getUser()->getId(),
]);
}, true);
}
public function removeUsers(): HttpResponse {
return Control::runChecked([
"identifiers" => [DefaultValidators::array(), DefaultValidators::forall(DefaultValidators::isUnsignedInteger())],
], function (HttpRequest $req) {
$this->accounts->removeAccounts($req["identifiers"]);
return HttpResponse::fromCode(HttpCodes::OK);
}, true);
}
public function updateUser(int $id): HttpResponse {
return Control::runChecked([
"email" => [DefaultValidators::email()],
"username" => [DefaultValidators::name()],
"isAdmin" => [DefaultValidators::bool()],
], function (HttpRequest $req) use ($id) {
$mailAccount = $this->accounts->getAccountFromMail($req["email"]);
if ($mailAccount->getUser()->getId() != $id) {
return new JsonHttpResponse([new ValidationFail("email exists", "The provided mail address already exists for another account.")], HttpCodes::FORBIDDEN);
}
$this->authModel->update($id, $req["email"], $req["username"], $req["isAdmin"]);
return HttpResponse::fromCode(HttpCodes::OK);
}, true);
}
}

@ -28,7 +28,7 @@ class APIAuthController {
public function authorize(): HttpResponse {
return Control::runChecked([
"email" => [DefaultValidators::email(), DefaultValidators::lenBetween(5, 256)],
"password" => [DefaultValidators::lenBetween(6, 256)],
"password" => [DefaultValidators::password()],
], function (HttpRequest $req) {
$failures = [];
$account = $this->model->login($req["email"], $req["password"], $failures);

@ -25,7 +25,7 @@ class Control {
$fail = new ValidationFail("bad-payload", "request body is not a valid json object");
if ($errorInJson) {
return new JsonHttpResponse([$fail]);
return new JsonHttpResponse([$fail], HttpCodes::BAD_REQUEST);
}
return ViewHttpResponse::twig("error.html.twig", ["failures" => [$fail]], HttpCodes::BAD_REQUEST);
@ -49,7 +49,7 @@ class Control {
if (!empty($fails)) {
if ($errorInJson) {
return new JsonHttpResponse($fails);
return new JsonHttpResponse($fails, HttpCodes::BAD_REQUEST);
}
return ViewHttpResponse::twig("error.html.twig", ['failures' => $fails], HttpCodes::BAD_REQUEST);
}

@ -7,7 +7,9 @@ use IQBall\App\ViewHttpResponse;
use IQBall\Core\Http\HttpRequest;
use IQBall\Core\Http\HttpResponse;
use IQBall\Core\Model\AuthModel;
use IQBall\Core\Validation\DefaultValidators;
use IQBall\Core\Validation\FieldValidationFail;
class AuthController {
private AuthModel $model;
@ -31,10 +33,10 @@ class AuthController {
*/
public function register(array $request, MutableSessionHandle $session): HttpResponse {
$fails = [];
HttpRequest::from($request, $fails, [
$request = HttpRequest::from($request, $fails, [
"username" => [DefaultValidators::name(), DefaultValidators::lenBetween(2, 32)],
"password" => [DefaultValidators::lenBetween(6, 256)],
"confirmpassword" => [DefaultValidators::lenBetween(6, 256)],
"password" => [DefaultValidators::password()],
"confirmpassword" => [DefaultValidators::password()],
"email" => [DefaultValidators::email(), DefaultValidators::lenBetween(5, 256)],
]);
@ -44,7 +46,16 @@ class AuthController {
}
}
$account = $this->model->register($request['username'], $request["password"], $request['confirmpassword'], $request['email'], $fails);
if ($request["password"] != $request['confirmpassword']) {
$fails[] = new FieldValidationFail("confirmpassword", "Le mot de passe et la confirmation ne sont pas les mêmes.");
}
$account = $this->model->register($request['username'], $request["password"], $request['email']);
if (!$account) {
$fails[] = new FieldValidationFail("email", "L'email existe déjà");
}
if (!empty($fails)) {
return ViewHttpResponse::twig("display_register.html.twig", ['fails' => $fails]);
}
@ -52,7 +63,7 @@ class AuthController {
$target_url = $session->getInitialTarget();
if ($target_url != null) {
return HttpResponse::redirect_absolute($target_url);
return HttpResponse::redirectAbsolute($target_url);
}
return HttpResponse::redirect("/home");
@ -81,7 +92,7 @@ class AuthController {
$target_url = $session->getInitialTarget();
$session->setInitialTarget(null);
if ($target_url != null) {
return HttpResponse::redirect_absolute($target_url);
return HttpResponse::redirectAbsolute($target_url);
}
return HttpResponse::redirect("/home");

@ -2,7 +2,6 @@
namespace IQBall\Core\Gateway;
use Cassandra\PreparedStatement;
use IQBall\Core\Connection;
use IQBall\Core\Data\Account;
use IQBall\Core\Data\User;
@ -19,16 +18,26 @@ class AccountGateway {
}
public function insertAccount(string $name, string $email, string $token, string $hash, string $profilePicture): int {
$this->con->exec("INSERT INTO Account(username, hash, email, token,profile_picture) VALUES (:username,:hash,:email,:token,:profilePic)", [
$this->con->exec("INSERT INTO Account(username, hash, email, token,profile_picture) VALUES (:username,:hash,:email,:token,:profile_pic)", [
':username' => [$name, PDO::PARAM_STR],
':hash' => [$hash, PDO::PARAM_STR],
':email' => [$email, PDO::PARAM_STR],
':token' => [$token, PDO::PARAM_STR],
':profilePic' => [$profilePicture, PDO::PARAM_STR],
':profile_pic' => [$profilePicture, PDO::PARAM_STR],
]);
return intval($this->con->lastInsertId());
}
public function updateAccount(int $id, string $name, string $email, string $token, bool $isAdmin): void {
$this->con->exec("UPDATE Account SET username = :username, email = :email, token = :token, is_admin = :is_admin WHERE id = :id", [
':username' => [$name, PDO::PARAM_STR],
':email' => [$email, PDO::PARAM_STR],
':token' => [$token, PDO::PARAM_STR],
':id' => [$id, PDO::PARAM_INT],
':is_admin' => [$isAdmin, PDO::PARAM_BOOL],
]);
}
/**
* promote or demote a user to server administrator
@ -122,7 +131,7 @@ class AccountGateway {
*
* @param integer $n the number of accounts to retrieve
* @param int $start starting index of the list content
* @return Account[]
* @return Account[]|null
*/
public function listAccounts(int $start, int $n): ?array {
$res = $this->con->fetch(
@ -132,7 +141,27 @@ class AccountGateway {
":n" => [$n, PDO::PARAM_INT],
]
);
return array_map(fn(array $acc) => new Account($acc["email"], new User($acc["username"], $acc["token"], $acc["id"], $acc["profile_picture"], $acc["is_admin"])), $res);
return array_map(fn(array $acc) => new Account($acc["token"], new User($acc["email"], $acc["username"], $acc["id"], $acc["profile_picture"], $acc["is_admin"])), $res);
}
/**
* returns the total amount of accounts in the database
* @return int
*/
public function totalCount(): int {
return $this->con->fetch("SELECT count(*) FROM Account", [])[0]['count(*)'];
}
/**
* remove a bunch of account identifiers
* @param int[] $accountIds
*/
public function removeAccounts(array $accountIds): void {
foreach ($accountIds as $accountId) {
$this->con->fetch("DELETE FROM Account WHERE id = :accountId", [
":accountId" => [$accountId, PDO::PARAM_INT],
]);
}
}
}

@ -45,7 +45,7 @@ class HttpResponse {
public static function redirect(string $url, int $code = HttpCodes::FOUND): HttpResponse {
global $basePath;
return self::redirect_absolute($basePath . $url, $code);
return self::redirectAbsolute($basePath . $url, $code);
}
/**
@ -54,7 +54,7 @@ class HttpResponse {
* @return HttpResponse a response that will redirect client to given url
*/
public static function redirect_absolute(string $url, int $code = HttpCodes::FOUND): HttpResponse {
public static function redirectAbsolute(string $url, int $code = HttpCodes::FOUND): HttpResponse {
if ($code < 300 || $code >= 400) {
throw new \InvalidArgumentException("given code is not a redirection http code");
}

@ -6,6 +6,8 @@ use Exception;
use IQBall\Core\Data\Account;
use IQBall\Core\Data\User;
use IQBall\Core\Gateway\AccountGateway;
use IQBall\Core\Http\HttpCodes;
use IQBall\Core\Http\JsonHttpResponse;
use IQBall\Core\Validation\FieldValidationFail;
use IQBall\Core\Validation\ValidationFail;
@ -23,34 +25,19 @@ class AuthModel {
/**
* @param string $username
* @param string $password
* @param string $confirmPassword
* @param string $email
* @param ValidationFail[] $failures
* @return Account|null the registered account or null if failures occurred
* @throws Exception
* @return Account|null the registered account or null if the account already exists for the given email address
*/
public function register(
string $username,
string $password,
string $confirmPassword,
string $email,
array &$failures
string $email
): ?Account {
if ($password != $confirmPassword) {
$failures[] = new FieldValidationFail("confirmpassword", "Le mot de passe et la confirmation ne sont pas les mêmes.");
}
if ($this->gateway->exists($email)) {
$failures[] = new FieldValidationFail("email", "L'email existe déjà");
}
if (!empty($failures)) {
return null;
}
$hash = password_hash($password, PASSWORD_DEFAULT);
$token = $this->generateToken();
$accountId = $this->gateway->insertAccount($username, $email, $token, $hash, self::DEFAULT_PROFILE_PICTURE);
return new Account($token, new User($email, $username, $accountId, self::DEFAULT_PROFILE_PICTURE, false));
@ -59,10 +46,13 @@ class AuthModel {
/**
* Generate a random base 64 string
* @return string
* @throws Exception
*/
public static function generateToken(): string {
try {
return base64_encode(random_bytes(64));
} catch (Exception $e) {
throw new \RuntimeException($e);
}
}
/**
@ -80,4 +70,9 @@ class AuthModel {
return $this->gateway->getAccountFromMail($email);
}
public function update(int $id, string $email, string $username, bool $isAdmin): void {
$token = $this->generateToken();
$this->gateway->updateAccount($id, $username, $email, $token, $isAdmin);
}
}

@ -38,6 +38,10 @@ class DefaultValidators {
return self::regex("/^[0-9a-zA-Zà-üÀ-Ü _-]*$/");
}
public static function password(): Validator {
return self::lenBetween(6, 256);
}
/**
* Validate string if its length is between given range
* @param int $min minimum accepted length, inclusive
@ -82,10 +86,63 @@ class DefaultValidators {
);
}
/**
* @param mixed[] $values
* @return Validator
*/
public static function oneOf(array $values): Validator {
return new SimpleFunctionValidator(
fn(string $val) => in_array($val, $values),
fn(string $name) => [new FieldValidationFail($name, "The value must be one of '" . join(", ", $values) . "'")]
);
}
public static function bool(): Validator {
return self::oneOf([true, false]);
}
public static function isURL(): Validator {
return new SimpleFunctionValidator(
fn($val) => filter_var($val, FILTER_VALIDATE_URL),
fn(string $name) => [new FieldValidationFail($name, "The value is not an URL")]
);
}
/**
* @return Validator
*/
public static function array(): Validator {
return new SimpleFunctionValidator(
fn($val) => is_array($val),
fn(string $name) => [new FieldValidationFail($name, "The value is not an array")]
);
}
/**
* @param Validator $validator
* @return Validator
*/
public static function forall(Validator $validator): Validator {
return new class ($validator) extends Validator {
private Validator $validator;
/**
* @param Validator $validator
*/
public function __construct(Validator $validator) {
$this->validator = $validator;
}
public function validate(string $name, $val): array {
$failures = [];
$idx = 0;
foreach ($val as $item) {
$failures = array_merge($failures, $this->validator->validate($name . "[$idx]", $item));
$idx += 1;
}
return $failures;
}
};
}
}

Loading…
Cancel
Save