Add some basic user management logic.

This commit is contained in:
flash 2018-05-27 03:59:57 +02:00
parent fca5b423a4
commit d579d2c8e0
6 changed files with 321 additions and 86 deletions

View file

@ -16,8 +16,8 @@
box-shadow: 0 1px 5px 0 fade(#555, 80%); box-shadow: 0 1px 5px 0 fade(#555, 80%);
} }
&--active, &:not(&--disabled)&--active,
&:active { &:not(&--disabled)&:active {
background-color: #444; background-color: #444;
} }
@ -48,4 +48,8 @@
font-size: 2em; font-size: 2em;
} }
} }
&__option--disabled &__link {
cursor: default;
}
} }

View file

@ -47,7 +47,6 @@ switch ($_GET['v'] ?? null) {
echo 'no'; echo 'no';
break; break;
} }
$getUser = $db->prepare(' $getUser = $db->prepare('
SELECT SELECT
u.*, u.*,
@ -68,7 +67,75 @@ switch ($_GET['v'] ?? null) {
break; break;
} }
$templating->var('view_user', $manageUser); $getHasRoles = $db->prepare('
SELECT `role_id`, `role_name`
FROM `msz_roles`
WHERE `role_id` IN (
SELECT `role_id`
FROM `msz_user_roles`
WHERE `user_id` = :user_id
)
');
$getHasRoles->bindValue('user_id', $manageUser['user_id']);
$hasRoles = $getHasRoles->execute() ? $getHasRoles->fetchAll() : [];
$getAvailableRoles = $db->prepare('
SELECT `role_id`, `role_name`
FROM `msz_roles`
WHERE `role_id` NOT IN (
SELECT `role_id`
FROM `msz_user_roles`
WHERE `user_id` = :user_id
)
');
$getAvailableRoles->bindValue('user_id', $manageUser['user_id']);
$availableRoles = $getAvailableRoles->execute() ? $getAvailableRoles->fetchAll() : [];
if ($isPostRequest) {
if (!tmp_csrf_verify($_POST['csrf'] ?? '')) {
echo 'csrf err';
break;
}
if (isset($_POST['avatar'])) {
switch ($_POST['avatar']['mode'] ?? '') {
case 'delete':
user_avatar_delete($manageUser['user_id']);
break;
case 'upload':
user_avatar_set_from_path($manageUser['user_id'], $_FILES['avatar']['tmp_name']['file']);
break;
}
}
if (isset($_POST['add_role'])) {
user_role_add($manageUser['user_id'], $_POST['add_role']['role']);
}
if (isset($_POST['manage_roles'])) {
switch ($_POST['manage_roles']['mode'] ?? '') {
case 'display':
user_role_set_display($manageUser['user_id'], $_POST['manage_roles']['role']);
break;
case 'remove':
if ((int)$_POST['manage_roles']['role'] !== MSZ_ROLE_MAIN) {
user_role_remove($manageUser['user_id'], $_POST['manage_roles']['role']);
}
break;
}
}
header("Location: ?v=view&u={$manageUser['user_id']}");
break;
}
$templating->vars([
'available_roles' => $availableRoles,
'has_roles' => $hasRoles,
'view_user' => $manageUser,
]);
echo $templating->render('@manage.users.view'); echo $templating->render('@manage.users.view');
break; break;

View file

@ -17,6 +17,30 @@ if (!$app->hasActiveSession()) {
$csrfErrorString = "Couldn't verify you, please refresh the page and retry."; $csrfErrorString = "Couldn't verify you, please refresh the page and retry.";
$avatarErrorStrings = [
'upload' => [
'default' => 'Something happened? (UP:%1$d)',
UPLOAD_ERR_OK => '',
UPLOAD_ERR_NO_FILE => 'Select a file before hitting upload!',
UPLOAD_ERR_PARTIAL => 'The upload was interrupted, please try again!',
UPLOAD_ERR_INI_SIZE => 'Your avatar is not allowed to be larger in file size than %2$s!',
UPLOAD_ERR_FORM_SIZE => 'Your avatar is not allowed to be larger in file size than %2$s!',
UPLOAD_ERR_NO_TMP_DIR => 'Unable to save your avatar, contact an administator!',
UPLOAD_ERR_CANT_WRITE => 'Unable to save your avatar, contact an administator!',
],
'set' => [
'default' => 'Something happened? (SET:%1$d)',
MSZ_USER_AVATAR_NO_ERRORS => '',
MSZ_USER_AVATAR_ERROR_INVALID_IMAGE => 'The file you uploaded was not an image!',
MSZ_USER_AVATAR_ERROR_PROHIBITED_TYPE => 'This type of image is not supported, keep to PNG, JPG or GIF!',
MSZ_USER_AVATAR_ERROR_DIMENSIONS_TOO_LARGE => 'Your avatar can\'t be larger than %3$dx%4$d!',
MSZ_USER_AVATAR_ERROR_DATA_TOO_LARGE => 'Your avatar is not allowed to be larger in file size than %2$s!',
MSZ_USER_AVATAR_ERROR_TMP_FAILED => 'Unable to save your avatar, contact an administator!',
MSZ_USER_AVATAR_ERROR_STORE_FAILED => 'Unable to save your avatar, contact an administator!',
MSZ_USER_AVATAR_ERROR_FILE_NOT_FOUND => 'Unable to save your avatar, contact an administator!',
],
];
$settingsModes = [ $settingsModes = [
'account' => 'Account', 'account' => 'Account',
'avatar' => 'Avatar', 'avatar' => 'Avatar',
@ -193,16 +217,7 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
break; break;
} }
$delete_this = [ user_avatar_delete($app->getUserId());
$app->getStore('avatars/original')->filename($avatarFileName),
$app->getStore('avatars/200x200')->filename($avatarFileName),
];
foreach ($delete_this as $delete_avatar) {
if (File::exists($delete_avatar)) {
File::delete($delete_avatar);
}
}
break; break;
} }
@ -212,78 +227,29 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
break; break;
} }
switch ($_FILES['avatar']['error']) { if ($_FILES['avatar']['error'] !== UPLOAD_ERR_OK) {
case UPLOAD_ERR_OK:
break;
case UPLOAD_ERR_NO_FILE:
$settingsErrors[] = 'Select a file before hitting upload!';
break;
case UPLOAD_ERR_PARTIAL:
$settingsErrors[] = 'The upload was interrupted, please try again!';
break;
case UPLOAD_ERR_INI_SIZE:
case UPLOAD_ERR_FORM_SIZE:
$settingsErrors[] = sprintf( $settingsErrors[] = sprintf(
'Your avatar is not allowed to be larger in filesize than %s', $avatarErrorStrings['upload'][$_FILES['avatar']['error']]
byte_symbol($avatarFileSizeMax, true) ?? $avatarErrorStrings['upload']['default'],
); $_FILES['avatar']['error'],
break; byte_symbol($avatarFileSizeMax, true),
case UPLOAD_ERR_NO_TMP_DIR:
case UPLOAD_ERR_CANT_WRITE:
$settingsErrors[] = 'Unable to save your avatar, contact an administator!';
break;
case UPLOAD_ERR_EXTENSION:
default:
$settingsErrors[] = 'Something happened?';
break;
}
if (count($settingsErrors) > 0) {
break;
}
$upload_path = $_FILES['avatar']['tmp_name'];
$upload_meta = getimagesize($upload_path);
if (!$upload_meta
|| !in_array($upload_meta[2], [IMAGETYPE_GIF, IMAGETYPE_JPEG, IMAGETYPE_PNG], true)
|| $upload_meta[0] < 1
|| $upload_meta[1] < 1) {
$settingsErrors[] = 'Please provide a valid image.';
break;
}
if ($upload_meta[0] > $avatarWidthMax || $upload_meta[1] > $avatarHeightMax) {
$settingsErrors[] = sprintf(
"Your avatar can't be larger than %dx%d, yours was %dx%d",
$avatarWidthMax, $avatarWidthMax,
$avatarHeightMax, $avatarHeightMax
$upload_meta[0],
$upload_meta[1]
); );
break; break;
} }
if (filesize($upload_path) > $avatarFileSizeMax) { $setAvatar = user_avatar_set_from_path($app->getUserId(), $_FILES['avatar']['tmp_name']);
if ($setAvatar !== MSZ_USER_AVATAR_NO_ERRORS) {
$settingsErrors[] = sprintf( $settingsErrors[] = sprintf(
'Your avatar is not allowed to be larger in filesize than %s!', $avatarErrorStrings['set'][$setAvatar]
byte_symbol($avatarFileSizeMax, true) ?? $avatarErrorStrings['set']['default'],
$setAvatar,
byte_symbol($avatarFileSizeMax, true),
$avatarWidthMax,
$avatarHeightMax
); );
break;
}
$avatar_path = $app->getStore('avatars/original')->filename($avatarFileName);
move_uploaded_file($upload_path, $avatar_path);
$crop_path = $app->getStore('avatars/200x200')->filename($avatarFileName);
if (File::exists($crop_path)) {
File::delete($crop_path);
} }
break; break;
} }

View file

@ -15,3 +15,45 @@ function user_role_add(int $userId, int $roleId): bool
$addRole->bindValue('role_id', $roleId); $addRole->bindValue('role_id', $roleId);
return $addRole->execute(); return $addRole->execute();
} }
function user_role_remove(int $userId, int $roleId): bool
{
$removeRole = Database::connection()->prepare('
DELETE FROM `msz_user_roles`
WHERE `user_id` = :user_id
AND `role_id` = :role_id
');
$removeRole->bindValue('user_id', $userId);
$removeRole->bindValue('role_id', $roleId);
return $removeRole->execute();
}
function user_role_has(int $userId, int $roleId): bool
{
$hasRole = Database::connection()->prepare('
SELECT COUNT(`role_id`) > 0
FROM `msz_user_roles`
WHERE `user_id` = :user_id
AND `role_id` = :role_id
');
$hasRole->bindValue('user_id', $userId);
$hasRole->bindValue('role_id', $roleId);
return $hasRole->execute() ? (bool)$hasRole->fetchColumn() : false;
}
function user_role_set_display(int $userId, int $roleId): bool
{
if (!user_role_has($userId, $roleId)) {
return false;
}
$setDisplay = Database::connection()->prepare('
UPDATE `msz_users`
SET `display_role` = :role_id
WHERE `user_id` = :user_id
');
$setDisplay->bindValue('user_id', $userId);
$setDisplay->bindValue('role_id', $roleId);
return $setDisplay->execute();
}

View file

@ -1,5 +1,7 @@
<?php <?php
use Misuzu\Application;
use Misuzu\Database; use Misuzu\Database;
use Misuzu\IO\File;
define('MSZ_USERS_PASSWORD_HASH_ALGO', PASSWORD_ARGON2I); define('MSZ_USERS_PASSWORD_HASH_ALGO', PASSWORD_ARGON2I);
@ -53,3 +55,110 @@ function user_generate_chat_key(int $userId): string
return $setChatKey->execute() ? $chatKey : ''; return $setChatKey->execute() ? $chatKey : '';
} }
define('MSZ_USER_AVATAR_FORMAT', '%d.msz');
function user_avatar_delete(int $userId): void
{
$app = Application::getInstance();
$avatarFileName = sprintf(MSZ_USER_AVATAR_FORMAT, $userId);
$deleteThis = [
$app->getStore('avatars/original')->filename($avatarFileName),
$app->getStore('avatars/200x200')->filename($avatarFileName),
];
foreach ($deleteThis as $deleteAvatar) {
if (File::exists($deleteAvatar)) {
File::delete($deleteAvatar);
}
}
}
define('MSZ_USER_AVATAR_TYPE_PNG', IMAGETYPE_PNG);
define('MSZ_USER_AVATAR_TYPE_JPG', IMAGETYPE_JPEG);
define('MSZ_USER_AVATAR_TYPE_GIF', IMAGETYPE_GIF);
define('MSZ_USER_AVATAR_TYPES', [
MSZ_USER_AVATAR_TYPE_PNG,
MSZ_USER_AVATAR_TYPE_JPG,
MSZ_USER_AVATAR_TYPE_GIF,
]);
function user_avatar_is_allowed_type(int $type): bool
{
return in_array($type, MSZ_USER_AVATAR_TYPES, true);
}
define('MSZ_USER_AVATAR_OPTIONS', [
'max_width' => 4000,
'max_height' => 4000,
'max_size' => 1000000,
]);
define('MSZ_USER_AVATAR_NO_ERRORS', 0);
define('MSZ_USER_AVATAR_ERROR_INVALID_IMAGE', 1);
define('MSZ_USER_AVATAR_ERROR_PROHIBITED_TYPE', 2);
define('MSZ_USER_AVATAR_ERROR_DIMENSIONS_TOO_LARGE', 3);
define('MSZ_USER_AVATAR_ERROR_DATA_TOO_LARGE', 4);
define('MSZ_USER_AVATAR_ERROR_TMP_FAILED', 5);
define('MSZ_USER_AVATAR_ERROR_STORE_FAILED', 6);
define('MSZ_USER_AVATAR_ERROR_FILE_NOT_FOUND', 7);
function user_avatar_set_from_path(int $userId, string $path, array $options = []): int
{
if (!file_exists($path)) {
return MSZ_USER_AVATAR_ERROR_FILE_NOT_FOUND;
}
$options = array_merge(MSZ_USER_AVATAR_OPTIONS, $options);
// 0 => width, 1 => height, 2 => type
$imageInfo = getimagesize($path);
if ($imageInfo === false
|| count($imageInfo) < 3
|| $imageInfo[0] < 1
|| $imageInfo[1] < 1) {
return MSZ_USER_AVATAR_ERROR_INVALID_IMAGE;
}
if (!user_avatar_is_allowed_type($imageInfo[2])) {
return MSZ_USER_AVATAR_ERROR_PROHIBITED_TYPE;
}
if ($imageInfo[0] > $options['max_width']
|| $imageInfo[1] > $options['max_height']) {
return MSZ_USER_AVATAR_ERROR_DIMENSIONS_TOO_LARGE;
}
if (filesize($path) > $options['max_size']) {
return MSZ_USER_AVATAR_ERROR_DATA_TOO_LARGE;
}
user_avatar_delete($userId);
$fileName = sprintf(MSZ_USER_AVATAR_FORMAT, $userId);
$avatarPath = Application::getInstance()->getStore('avatars/original')->filename($fileName);
if (!copy($path, $avatarPath)) {
return MSZ_USER_AVATAR_ERROR_STORE_FAILED;
}
return MSZ_USER_AVATAR_NO_ERRORS;
}
function user_avatar_set_from_data(int $userId, string $data, array $options = []): int
{
$tmp = tempnam(sys_get_temp_dir(), 'msz');
if ($tmp === false || !file_exists($tmp)) {
return MSZ_USER_AVATAR_ERROR_TMP_FAILED;
}
chmod($tmp, 644);
file_put_contents($tmp, $data);
$result = user_avatar_set_from_path($userId, $tmp, $options);
unlink($tmp);
return $result;
}

View file

@ -46,23 +46,25 @@
</div> </div>
</div> </div>
<div class="container"> <form class="container" method="post" enctype="multipart/form-data" action="">
<h1 class="container__title"> <h1 class="container__title">
Avatar Avatar
</h1> </h1>
<input type="hidden" name="csrf" value="{{ csrf_token() }}">
<label class="form__label"> <label class="form__label">
<div class="form__label__text">New Avatar</div> <div class="form__label__text">New Avatar</div>
<div class="form__label__input"> <div class="form__label__input">
<input class="input input--text" type="file" name="user[avatar]"> <input class="input input--text" type="file" name="avatar[file]">
</div> </div>
</label> </label>
<div> <div>
<button class="button" name="csrf" value="{{ csrf_token() }}">Upload</button> <button class="button" name="avatar[mode]" value="upload">Upload</button>
<button class="button" name="csrf" value="{{ csrf_token() }}">Delete</button> <button class="button" name="avatar[mode]" value="delete">Delete</button>
</div>
</div> </div>
</form>
<div class="container"> <div class="container">
<h1 class="container__title"> <h1 class="container__title">
@ -168,9 +170,54 @@
</div> </div>
</div> </div>
<div class="container"> {% if available_roles|length > 0 %}
<h1 class="container__title">Roles</h1> <form class="container" method="post" action="">
<h1 class="container__title">Add Role</h1>
<label class="form__label">
<div class="form__label__text">Available Roles</div>
<div class="form__label__input">
<select name="add_role[role]" class="input input--select">
{% for role in available_roles %}
<option value="{{ role.role_id }}">
{{ role.role_name }}
</option>
{% endfor %}
</select>
</div> </div>
</label>
<div>
<button class="button" name="csrf" value="{{ csrf_token() }}">Add</button>
</div>
</form>
{% endif %}
{% if has_roles|length > 0 %}
<form class="container" method="post" action="">
<h1 class="container__title">Manage Roles</h1>
<input type="hidden" name="csrf" value="{{ csrf_token() }}">
<label class="form__label">
<div class="form__label__text">Has Roles</div>
<div class="form__label__input">
<select name="manage_roles[role]" class="input input--select">
{% for role in has_roles %}
<option value="{{ role.role_id }}"{% if role.role_id == view_user.display_role %} selected{% endif %}>
{{ role.role_name }}
</option>
{% endfor %}
</select>
</div>
</label>
<div>
<button class="button" name="manage_roles[mode]" value="display">Set Display</button>
<button class="button" name="manage_roles[mode]" value="remove">Remove</button>
</div>
</form>
{% endif %}
<div class="container"> <div class="container">
<h1 class="container__title">Permissions</h1> <h1 class="container__title">Permissions</h1>