XBackBone/app/Controllers/UserController.php
Pietro Marangon 3cb4ca790c Fix & improvements (#2)
* New .gitignore
Added violinist.io configuration
Start with CI
Started moving CSS/JS to NPM

* Fix & improvements
Security improves (SHA256 instead of MD5) and new PHP7 random function
Security fix admin check from database instead of session
Security fix user active for every page that require login
UX fix admin cannot demote himself
Added Gruntfile.js
Updated composer.json dependency
Addeded PHP >=7.1 to composer.json
Moved static file to src

* Results of .gitignore "static/"

* Fix migration for admin user_code

* Travis test for grunt (JS)

* Changed user_code generation method
Updated Travis test
2018-06-02 22:32:14 +02:00

337 lines
No EOL
8.3 KiB
PHP

<?php
namespace App\Controllers;
use App\Database\DB;
use App\Exceptions\NotFoundException;
use App\Exceptions\UnauthorizedException;
use App\Traits\SingletonController;
use App\Web\Log;
use App\Web\Session;
use Flight;
class UserController extends Controller
{
use SingletonController;
const PER_PAGE = 15;
public function index($page = 1): void
{
$this->checkAdmin();
$page = max(0, --$page);
$users = DB::query('SELECT * FROM `users` LIMIT ? OFFSET ?', [self::PER_PAGE, $page * self::PER_PAGE])->fetchAll();
$pages = DB::query('SELECT COUNT(*) AS `count` FROM `users`')->fetch()->count / self::PER_PAGE;
Flight::render('user/index.twig', [
'users' => $users,
'next' => $page < floor($pages),
'previous' => $page >= 1,
'current_page' => ++$page,
]);
}
public function create(): void
{
$this->checkAdmin();
Flight::render('user/create.twig');
}
public function store(): void
{
$this->checkAdmin();
$form = Flight::request()->data;
if (!isset($form->email) || empty($form->email)) {
Session::alert('The email is required.', 'danger');
Flight::redirectBack();
return;
}
if (!isset($form->username) || empty($form->username)) {
Session::alert('The username is required.', 'danger');
Flight::redirectBack();
return;
}
if (DB::query('SELECT COUNT(*) AS `count` FROM `users` WHERE `username` = ?', $form->username)->fetch()->count > 0) {
Session::alert('The username already taken.', 'danger');
Flight::redirectBack();
return;
}
if (!isset($form->password) || empty($form->password)) {
Session::alert('The password is required.', 'danger');
Flight::redirectBack();
return;
}
do {
$userCode = substr(bin2hex(random_bytes(3)), 5);
} while (DB::query('SELECT COUNT(*) AS `count` FROM `users` WHERE `user_code` = ?', $userCode)->fetch()->count > 0);
$token = $this->generateNewToken();
DB::query('INSERT INTO `users`(`email`, `username`, `password`, `is_admin`, `active`, `user_code`, `token`) VALUES (?, ?, ?, ?, ?, ?, ?)', [
$form->email,
$form->username,
password_hash($form->password, PASSWORD_DEFAULT),
isset($form->is_admin),
isset($form->is_active),
$userCode,
$token
]);
Session::alert("User '$form->username' created!", 'success');
Log::info('User ' . Session::get('username') . ' created a new user.', [array_diff($form->getData(), ['password'])]);
Flight::redirect('/users');
}
public function edit($id): void
{
$this->checkAdmin();
$user = DB::query('SELECT * FROM `users` WHERE `id` = ? LIMIT 1', $id)->fetch();
if (!$user) {
Flight::error(new NotFoundException());
return;
}
Flight::render('user/edit.twig', [
'user' => $user
]);
}
public function update($id): void
{
$this->checkAdmin();
$form = Flight::request()->data;
$user = DB::query('SELECT * FROM `users` WHERE `id` = ? LIMIT 1', $id)->fetch();
if (!$user) {
Flight::error(new NotFoundException());
return;
}
if (!isset($form->email) || empty($form->email)) {
Session::alert('The email is required.', 'danger');
Flight::redirectBack();
return;
}
if (!isset($form->username) || empty($form->username)) {
Session::alert('The username is required.', 'danger');
Flight::redirectBack();
return;
}
if (DB::query('SELECT COUNT(*) AS `count` FROM `users` WHERE `username` = ? AND `username` <> ?', [$form->username, $user->username])->fetch()->count > 0) {
Session::alert('The username already taken.', 'danger');
Flight::redirectBack();
return;
}
if ($user->id === Session::get('user_id') && !isset($form->is_admin)) {
Session::alert('You cannot demote yourself.', 'danger');
Flight::redirectBack();
return;
}
if (isset($form->password) && !empty($form->password)) {
DB::query('UPDATE `users` SET `email`=?, `username`=?, `password`=?, `is_admin`=?, `active`=? WHERE `id` = ?', [
$form->email,
$form->username,
password_hash($form->password, PASSWORD_DEFAULT),
isset($form->is_admin),
isset($form->is_active),
$user->id
]);
} else {
DB::query('UPDATE `users` SET `email`=?, `username`=?, `is_admin`=?, `active`=? WHERE `id` = ?', [
$form->email,
$form->username,
isset($form->is_admin),
isset($form->is_active),
$user->id
]);
}
Session::alert("User '$form->username' updated!", 'success');
Log::info('User ' . Session::get('username') . " updated $user->id.", [$user, array_diff($form->getData(), ['password'])]);
Flight::redirect('/users');
}
public function delete($id): void
{
$this->checkAdmin();
$user = DB::query('SELECT * FROM `users` WHERE `id` = ? LIMIT 1', $id)->fetch();
if (!$user) {
Flight::error(new NotFoundException());
return;
}
if ($user->id === Session::get('user_id')) {
Session::alert('You cannot delete yourself.', 'danger');
Flight::redirectBack();
return;
}
DB::query('DELETE FROM `users` WHERE `id` = ?', $user->id);
Session::alert('User deleted.', 'success');
Log::info('User ' . Session::get('username') . " deleted $user->id.");
Flight::redirect('/users');
}
public function profile(): void
{
$this->checkLogin();
$user = DB::query('SELECT * FROM `users` WHERE `id` = ? LIMIT 1', Session::get('user_id'))->fetch();
if (!$user) {
Flight::error(new NotFoundException());
return;
}
if ($user->id !== Session::get('user_id') && !Session::get('admin', false)) {
Flight::error(new UnauthorizedException());
return;
}
Flight::render('user/profile.twig', [
'user' => $user
]);
}
public function profileEdit($id): void
{
$this->checkLogin();
$form = Flight::request()->data;
$user = DB::query('SELECT * FROM `users` WHERE `id` = ? LIMIT 1', $id)->fetch();
if (!$user) {
Flight::error(new NotFoundException());
return;
}
if ($user->id !== Session::get('user_id') && !Session::get('admin', false)) {
Flight::error(new UnauthorizedException());
return;
}
if (!isset($form->email) || empty($form->email)) {
Session::alert('The email is required.', 'danger');
Flight::redirectBack();
return;
}
if (isset($form->password) && !empty($form->password)) {
DB::query('UPDATE `users` SET `email`=?, `password`=? WHERE `id` = ?', [
$form->email,
password_hash($form->password, PASSWORD_DEFAULT),
$user->id
]);
} else {
DB::query('UPDATE `users` SET `email`=? WHERE `id` = ?', [
$form->email,
$user->id
]);
}
Session::alert('Profile updated successfully!', 'success');
Log::info('User ' . Session::get('username') . " updated profile of $user->id.");
Flight::redirectBack();
}
public function refreshToken($id): void
{
$this->checkLogin();
$user = DB::query('SELECT * FROM `users` WHERE `id` = ? LIMIT 1', $id)->fetch();
if (!$user) {
Flight::halt(404);
return;
}
if ($user->id !== Session::get('user_id') && !Session::get('admin', false)) {
Flight::halt(403);
return;
}
$token = $this->generateNewToken();
DB::query('UPDATE `users` SET `token`=? WHERE `id` = ?', [
$token,
$user->id
]);
Log::info('User ' . Session::get('username') . " refreshed token of user $user->id.");
echo $token;
}
public function getShareXconfigFile($id): void
{
$this->checkLogin();
$user = DB::query('SELECT * FROM `users` WHERE `id` = ? LIMIT 1', $id)->fetch();
if (!$user) {
Flight::halt(404);
return;
}
if ($user->id !== Session::get('user_id') && !Session::get('admin', false)) {
Flight::halt(403);
return;
}
$base_url = Flight::get('config')['base_url'];
$json = [
'DestinationType' => 'ImageUploader, TextUploader, FileUploader',
'RequestURL' => "$base_url/upload",
'FileFormName' => 'upload',
'Arguments' => [
'file' => '$filename$',
'text' => '$input$',
'token' => $user->token,
],
'URL' => '$json:url$',
'ThumbnailURL' => '$json:url$/raw',
];
Flight::response()->header('Content-Type', 'application/json');
Flight::response()->header('Content-Disposition', 'attachment;filename="' . $user->username . '-ShareX.sxcu"');
Flight::response()->sendHeaders();
echo json_encode($json, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT);
}
protected function generateNewToken(): string
{
do {
$token = 'token_' . hash('sha256', microtime().random_bytes(256).uniqid('', true));
} while (DB::query('SELECT COUNT(*) AS `count` FROM `users` WHERE `token` = ?', $token)->fetch()->count > 0);
return $token;
}
}