diff --git a/misuzu.php b/misuzu.php index c538c9ac..45139bb3 100644 --- a/misuzu.php +++ b/misuzu.php @@ -6,6 +6,8 @@ date_default_timezone_set('UTC'); require_once __DIR__ . '/vendor/autoload.php'; require_once __DIR__ . '/src/changelog.php'; require_once __DIR__ . '/src/colour.php'; +require_once __DIR__ . '/src/manage.php'; +require_once __DIR__ . '/src/perms.php'; require_once __DIR__ . '/src/zalgo.php'; require_once __DIR__ . '/src/Forum/forum.php'; require_once __DIR__ . '/src/Forum/post.php'; @@ -36,16 +38,17 @@ if (PHP_SAPI !== 'cli') { ob_start('ob_gzhandler'); } + $app->startTemplating(); + $tpl = $app->getTemplating(); + if ($app->getConfig()->get('Auth', 'lockdown', 'bool', false)) { http_response_code(503); - $app->startTemplating(); - $app->getTemplating()->addPath('auth', __DIR__ . '/views/auth'); - echo $app->getTemplating()->render('lockdown'); + $tpl->addPath('auth', __DIR__ . '/views/auth'); + echo $tpl->render('lockdown'); exit; } - $app->startTemplating(); - $app->getTemplating()->addPath('mio', __DIR__ . '/views/mio'); + $tpl->addPath('mio', __DIR__ . '/views/mio'); if (isset($_COOKIE['msz_uid'], $_COOKIE['msz_sid'])) { $app->startSession((int)$_COOKIE['msz_uid'], $_COOKIE['msz_sid']); @@ -74,18 +77,22 @@ if (PHP_SAPI !== 'cli') { '); $getUserDisplayInfo->bindValue('user_id', $app->getUserId()); $userDisplayInfo = $getUserDisplayInfo->execute() ? $getUserDisplayInfo->fetch() : []; - $app->getTemplating()->var('current_user', $userDisplayInfo); + $tpl->var('current_user', $userDisplayInfo); } } - $manage_mode = starts_with($_SERVER['REQUEST_URI'], '/manage'); + $inManageMode = starts_with($_SERVER['REQUEST_URI'], '/manage'); + $hasManageAccess = perms_check(perms_get_user(MSZ_PERMS_USER, $app->getUserId()), MSZ_PERM_MANAGE); + $tpl->var('has_manage_access', $hasManageAccess); - if ($manage_mode) { - if ($app->getUserId() !== 1) { + if ($inManageMode) { + if (!$hasManageAccess) { echo render_error(403); exit; } - $app->getTemplating()->addPath('manage', __DIR__ . '/views/manage'); + $tpl = $app->getTemplating(); + $tpl->var('manage_menu', manage_get_menu($app->getUserId())); + $tpl->addPath('manage', __DIR__ . '/views/manage'); } } diff --git a/public/manage/changelog.php b/public/manage/changelog.php index 440fe394..1e201ce7 100644 --- a/public/manage/changelog.php +++ b/public/manage/changelog.php @@ -6,12 +6,18 @@ require_once __DIR__ . '/../../misuzu.php'; $db = Database::connection(); $tpl = $app->getTemplating(); +$changelogPerms = perms_get_user(MSZ_PERMS_CHANGELOG, $app->getUserId()); + $queryOffset = (int)($_GET['o'] ?? 0); switch ($_GET['v'] ?? null) { case 'changes': - $changesTake = 20; + if (!perms_check($changelogPerms, MSZ_CHANGELOG_MANAGE_CHANGES)) { + echo render_error(403); + break; + } + $changesTake = 20; $changesCount = (int)$db->query(' SELECT COUNT(`change_id`) FROM `msz_changelog_changes` @@ -61,6 +67,11 @@ switch ($_GET['v'] ?? null) { break; case 'change': + if (!perms_check($changelogPerms, MSZ_CHANGELOG_MANAGE_CHANGES)) { + echo render_error(403); + break; + } + $changeId = (int)($_GET['c'] ?? 0); if ($_SERVER['REQUEST_METHOD'] === 'POST' && tmp_csrf_verify($_POST['csrf'] ?? '')) { @@ -180,6 +191,11 @@ switch ($_GET['v'] ?? null) { break; case 'tags': + if (!perms_check($changelogPerms, MSZ_CHANGELOG_MANAGE_TAGS)) { + echo render_error(403); + break; + } + $tagsTake = 32; $tagsCount = (int)$db->query(' @@ -212,6 +228,11 @@ switch ($_GET['v'] ?? null) { break; case 'tag': + if (!perms_check($changelogPerms, MSZ_CHANGELOG_MANAGE_TAGS)) { + echo render_error(403); + break; + } + $tagId = (int)($_GET['t'] ?? 0); if ($_SERVER['REQUEST_METHOD'] === 'POST' && tmp_csrf_verify($_POST['csrf'] ?? '')) { @@ -268,6 +289,11 @@ switch ($_GET['v'] ?? null) { break; case 'actions': + if (!perms_check($changelogPerms, MSZ_CHANGELOG_MANAGE_ACTIONS)) { + echo render_error(403); + break; + } + $actionTake = 32; $actionCount = (int)$db->query(' @@ -300,6 +326,11 @@ switch ($_GET['v'] ?? null) { break; case 'action': + if (!perms_check($changelogPerms, MSZ_CHANGELOG_MANAGE_ACTIONS)) { + echo render_error(403); + break; + } + $actionId = (int)($_GET['a'] ?? 0); if ($_SERVER['REQUEST_METHOD'] === 'POST' && tmp_csrf_verify($_POST['csrf'] ?? '')) { @@ -363,8 +394,4 @@ switch ($_GET['v'] ?? null) { echo $tpl->render('@manage.changelog.action_edit'); break; - - default: - header('Location: ?v=changes'); - break; } diff --git a/public/manage/users.php b/public/manage/users.php index e2001671..b153c3f6 100644 --- a/public/manage/users.php +++ b/public/manage/users.php @@ -6,13 +6,19 @@ require_once __DIR__ . '/../../misuzu.php'; $db = Database::connection(); $templating = $app->getTemplating(); +$userPerms = perms_get_user(MSZ_PERMS_USER, $app->getUserId()); + $isPostRequest = $_SERVER['REQUEST_METHOD'] === 'POST'; $queryQffset = (int)($_GET['o'] ?? 0); switch ($_GET['v'] ?? null) { case 'listing': - $usersTake = 32; + if (!perms_check($userPerms, MSZ_PERM_MANAGE_USERS)) { + echo render_error(403); + break; + } + $usersTake = 32; $manageUsersCount = $db->query(' SELECT COUNT(`user_id`) FROM `msz_users` @@ -25,6 +31,7 @@ switch ($_GET['v'] ?? null) { FROM `msz_users` as u LEFT JOIN `msz_roles` as r ON u.`display_role` = r.`role_id` + ORDER BY `user_id` LIMIT :offset, :take '); $getManageUsers->bindValue('offset', $queryQffset); @@ -41,6 +48,11 @@ switch ($_GET['v'] ?? null) { break; case 'view': + if (!perms_check($userPerms, MSZ_PERM_MANAGE_USERS)) { + echo render_error(403); + break; + } + $userId = $_GET['u'] ?? null; if ($userId === null || ($userId = (int)$userId) < 1) { @@ -141,6 +153,11 @@ switch ($_GET['v'] ?? null) { break; case 'roles': + if (!perms_check($userPerms, MSZ_PERM_MANAGE_ROLES)) { + echo render_error(403); + break; + } + $rolesTake = 10; $manageRolesCount = $db->query(' @@ -173,6 +190,11 @@ switch ($_GET['v'] ?? null) { break; case 'role': + if (!perms_check($userPerms, MSZ_PERM_MANAGE_ROLES)) { + echo render_error(403); + break; + } + $roleId = $_GET['r'] ?? null; if ($isPostRequest) { diff --git a/public/settings.php b/public/settings.php index a09e8171..4d03407a 100644 --- a/public/settings.php +++ b/public/settings.php @@ -5,16 +5,52 @@ use Misuzu\IO\File; require_once __DIR__ . '/../misuzu.php'; $db = Database::connection(); -$templating = $app->getTemplating(); +$tpl = $app->getTemplating(); $queryOffset = (int)($_GET['o'] ?? 0); $queryTake = 15; -if (!$app->hasActiveSession()) { +$userPerms = perms_get_user(MSZ_PERMS_USER, $app->getUserId()); + +$settingsModes = [ + 'account' => [ + 'title' => 'Account', + 'allow' => perms_check($userPerms, MSZ_PERM_EDIT_PROFILE), + ], + 'avatar' => [ + 'title' => 'Avatar', + 'allow' => perms_check($userPerms, MSZ_PERM_CHANGE_AVATAR), + ], + 'sessions' => [ + 'title' => 'Sessions', + 'allow' => true, + ], + 'login-history' => [ + 'title' => 'Login History', + 'allow' => true, + ], +]; +$settingsMode = $_GET['m'] ?? null; + +$settingsNavigation = []; + +foreach ($settingsModes as $key => $value) { + if ($value['allow']) { + $settingsNavigation[$value['title']] = $key; + + if ($settingsMode === null) { + $settingsMode = $key; + } + } +} + +if (!$app->hasActiveSession() || !$settingsModes[$settingsMode]['allow']) { echo render_error(403); return; } +$tpl->var('settings_navigation', $settingsNavigation); + $csrfErrorString = "Couldn't verify you, please refresh the page and retry."; $avatarErrorStrings = [ @@ -41,23 +77,15 @@ $avatarErrorStrings = [ ], ]; -$settingsModes = [ - 'account' => 'Account', - 'avatar' => 'Avatar', - 'sessions' => 'Sessions', - 'login-history' => 'Login History', -]; -$settingsMode = $_GET['m'] ?? key($settingsModes); - -$templating->vars([ +$tpl->vars([ 'settings_mode' => $settingsMode, 'settings_modes' => $settingsModes, ]); if (!array_key_exists($settingsMode, $settingsModes)) { http_response_code(404); - $templating->var('settings_title', 'Not Found'); - echo $templating->render('settings.notfound'); + $tpl->var('settings_title', 'Not Found'); + echo $tpl->render('settings.notfound'); return; } @@ -293,8 +321,8 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') { } } -$templating->var('settings_title', $settingsModes[$settingsMode]); -$templating->var('settings_errors', $settingsErrors); +$tpl->var('settings_title', $settingsModes[$settingsMode]['title']); +$tpl->var('settings_errors', $settingsErrors); switch ($settingsMode) { case 'account': @@ -307,7 +335,7 @@ switch ($settingsMode) { $getUserFields->bindValue('user_id', $app->getUserId()); $userFields = $getUserFields->execute() ? $getUserFields->fetch() : []; - $templating->vars([ + $tpl->vars([ 'settings_profile_fields' => $profileFields, 'settings_profile_values' => $userFields, 'settings_disable_account_options' => $disableAccountOptions, @@ -316,7 +344,7 @@ switch ($settingsMode) { case 'avatar': $userHasAvatar = File::exists($app->getStore('avatars/original')->filename($avatarFileName)); - $templating->vars([ + $tpl->vars([ 'avatar_user_id' => $app->getUserId(), 'avatar_max_width' => $avatarWidthMax, 'avatar_max_height' => $avatarHeightMax, @@ -348,7 +376,7 @@ switch ($settingsMode) { $getSessions->bindValue('user_id', $app->getUserId()); $sessions = $getSessions->execute() ? $getSessions->fetchAll() : []; - $templating->vars([ + $tpl->vars([ 'active_session_id' => $app->getSessionId(), 'user_sessions' => $sessions, 'sessions_offset' => $queryOffset, @@ -380,7 +408,7 @@ switch ($settingsMode) { $getLoginAttempts->bindValue('user_id', $app->getUserId()); $loginAttempts = $getLoginAttempts->execute() ? $getLoginAttempts->fetchAll() : []; - $templating->vars([ + $tpl->vars([ 'user_login_attempts' => $loginAttempts, 'login_attempts_offset' => $queryOffset, 'login_attempts_take' => $queryTake, @@ -389,4 +417,4 @@ switch ($settingsMode) { break; } -echo $templating->render("settings.{$settingsMode}"); +echo $tpl->render("settings.{$settingsMode}"); diff --git a/src/Application.php b/src/Application.php index c10c355d..4910a62b 100644 --- a/src/Application.php +++ b/src/Application.php @@ -73,7 +73,9 @@ class Application extends ApplicationBase public function getConfig(): ConfigManager { if (is_null($this->configInstance)) { - throw new UnexpectedValueException('Internal ConfigManager instance is null, how did you even manage to do this?'); + throw new UnexpectedValueException( + 'Internal ConfigManager instance is null, how did you even manage to do this?' + ); } return $this->configInstance; @@ -242,7 +244,6 @@ class Application extends ApplicationBase $this->templatingInstance->addFilter('html_colour'); $this->templatingInstance->addFilter('url_construct'); $this->templatingInstance->addFilter('country_name', 'get_country_name'); - $this->templatingInstance->addFilter('flip', 'array_flip'); $this->templatingInstance->addFilter('first_paragraph'); $this->templatingInstance->addFilter('colour_get_css'); $this->templatingInstance->addFilter('colour_get_css_contrast'); @@ -256,6 +257,7 @@ class Application extends ApplicationBase $this->templatingInstance->addFunction('git_hash', [Application::class, 'gitCommitHash']); $this->templatingInstance->addFunction('git_branch', [Application::class, 'gitBranch']); $this->templatingInstance->addFunction('csrf_token', 'tmp_csrf_token'); + $this->templatingInstance->addFunction('perms_check'); $this->templatingInstance->var('app', $this); } diff --git a/src/Users/user.php b/src/Users/user.php index 64d991a5..b6ff5917 100644 --- a/src/Users/user.php +++ b/src/Users/user.php @@ -3,6 +3,17 @@ use Misuzu\Application; use Misuzu\Database; use Misuzu\IO\File; +define('MSZ_PERM_EDIT_PROFILE', 1); +define('MSZ_PERM_CHANGE_AVATAR', 1 << 1); + +define('MSZ_PERM_MANAGE', 1 << 20); +define('MSZ_PERM_MANAGE_USERS', 1 << 21); +define('MSZ_PERM_MANAGE_ROLES', 1 << 22); +define('MSZ_PERM_MANAGE_PERMS', 1 << 23); +define('MSZ_PERM_MANAGE_REPORTS', 1 << 24); +define('MSZ_PERM_MANAGE_RESTRICTIONS', 1 << 25); +define('MSZ_PERM_MANAGE_BLACKLISTS', 1 << 26); + define('MSZ_USERS_PASSWORD_HASH_ALGO', PASSWORD_ARGON2I); function user_create( diff --git a/src/changelog.php b/src/changelog.php index 028348bf..64a47900 100644 --- a/src/changelog.php +++ b/src/changelog.php @@ -1,6 +1,10 @@ execute() ? (int)$dbc->lastInsertId() : 0; } -define('CHANGELOG_GET_QUERY', ' +define('MSZ_CHANGELOG_GET_QUERY', ' SELECT c.`change_id`, c.`change_log`, a.`action_name`, a.`action_colour`, a.`action_class`, @@ -70,7 +74,7 @@ function changelog_get_changes(string $date, int $user, int $offset, int $take): $hasUser = $user > 0; $query = sprintf( - CHANGELOG_GET_QUERY, + MSZ_CHANGELOG_GET_QUERY, $hasDate ? 'DATE(c.`change_created`) = :date' : '1', $hasUser ? 'c.`user_id` = :user' : '1', !$hasDate ? 'LIMIT :offset, :take' : '' diff --git a/src/manage.php b/src/manage.php new file mode 100644 index 00000000..42522286 --- /dev/null +++ b/src/manage.php @@ -0,0 +1,93 @@ + '/manage/index.php?v=overview', + 'Logs' => '/manage/index.php?v=logs', + '_', + 'Emoticons' => '/manage/index.php?v=emoticons', + 'Settings' => '/manage/index.php?v=settings', + ]; + + $canUsers = perms_check($userPerms, MSZ_PERM_MANAGE_USERS); + $canRoles = perms_check($userPerms, MSZ_PERM_MANAGE_ROLES); + $canPerms = perms_check($userPerms, MSZ_PERM_MANAGE_PERMS); + $canReports = perms_check($userPerms, MSZ_PERM_MANAGE_REPORTS); + $canRestricts = perms_check($userPerms, MSZ_PERM_MANAGE_RESTRICTIONS); + $canBlacklists = perms_check($userPerms, MSZ_PERM_MANAGE_BLACKLISTS); + + if ($canUsers || $canRoles || $canPerms + || $canReports || $canRestricts || $canBlacklists) { + $menu['Users'] = []; + + if ($canUsers) { + $menu['Users']['Listing'] = '/manage/users.php?v=listing'; + } + + if ($canRoles || $canPerms) { + $menu['Users'][] = '_'; + + if ($canRoles) { + $menu['Users']['Roles'] = '/manage/users.php?v=roles'; + } + + if ($canPerms) { + $menu['Users']['Permissions'] = '/manage/users.php?v=permissions'; + } + } + + if ($canReports || $canRestricts || $canBlacklists) { + $menu['Users'][] = '_'; + + if ($canReports) { + $menu['Users']['Reports'] = '/manage/users.php?v=reports'; + } + + if ($canRestricts) { + $menu['Users']['Restrictions'] = '/manage/users.php?v=restrictions'; + } + + if ($canBlacklists) { + $menu['Users']['Blacklisting'] = '/manage/users.php?v=blacklisting'; + } + } + } + + /*$menu['Forum'] = [ + 'Listing' => '/manage/forums.php?v=listing', + 'Permisisons' => '/manage/forums.php?v=permissions', + 'Settings' => '/manage/forums.php?v=settings', + ];*/ + + $canChanges = perms_check($changelogPerms, MSZ_CHANGELOG_MANAGE_CHANGES); + $canChangeTags = perms_check($changelogPerms, MSZ_CHANGELOG_MANAGE_TAGS); + $canChangeActions = perms_check($changelogPerms, MSZ_CHANGELOG_MANAGE_ACTIONS); + + if ($canChanges || $canChangeTags || $canChangeActions) { + $menu['Changelog'] = []; + + if ($canChanges) { + $menu['Changelog']['Changes'] = '/manage/changelog.php?v=changes'; + } + + if ($canChangeTags) { + $menu['Changelog']['Tags'] = '/manage/changelog.php?v=tags'; + } + + if ($canChangeActions) { + $menu['Changelog']['Actions'] = '/manage/changelog.php?v=actions'; + } + } + + return $menu; +} diff --git a/src/perms.php b/src/perms.php new file mode 100644 index 00000000..001729dd --- /dev/null +++ b/src/perms.php @@ -0,0 +1,95 @@ +prepare(" + SELECT `{$prefix}_perms_allow` as `allow`, `{$prefix}_perms_deny` as `deny` + FROM `msz_permissions` + WHERE (`user_id` = :user_id_1 AND `role_id` IS NULL) + OR ( + `user_id` IS NULL + AND `role_id` IN ( + SELECT `role_id` + FROM `msz_user_roles` + WHERE `user_id` = :user_id_2 + ) + ) + "); + $getPerms->bindValue('user_id_1', $user); + $getPerms->bindValue('user_id_2', $user); + $perms = $getPerms->execute() ? $getPerms->fetchAll(PDO::FETCH_ASSOC) : []; + + foreach ($perms as $perm) { + $permsAllow |= $perm['allow']; + $permsDeny |= $perm['deny']; + } + + return perms_set_cache($prefix, 'user', $user, $permsAllow &~ $permsDeny); +} + +function perms_get_role(string $prefix, int $role): int +{ + if ($role < 1) { + return 0; + } + + if (perms_is_cached($prefix, 'role', $user)) { + return perms_get_cache($prefix, 'role', $user); + } + + $getPerms = Database::connection()->prepare(" + SELECT `{$prefix}_perms_allow` &~ `{$prefix}_perms_deny` + FROM `msz_permissions` + WHERE `role_id` = :role_id + AND `user_id` IS NULL + "); + $getPerms->bindValue('role_id', $role); + return perms_set_cache($prefix, 'role', $role, $getPerms->execute() ? (int)$getPerms->fetchColumn() : 0); +} + +function perms_check(int $perms, int $perm): bool +{ + return ($perms & $perm) > 0; +} diff --git a/views/manage/master.twig b/views/manage/master.twig index 165f1587..4382c624 100644 --- a/views/manage/master.twig +++ b/views/manage/master.twig @@ -1,124 +1,3 @@ -{% set menus = [ - { - 'name': 'general', - 'title': 'General', - 'sections': [ - [ - { - 'name': 'overview', - 'title': 'Overview', - 'url': '/manage/index.php?v=overview', - }, - { - 'name': 'logs', - 'title': 'Logs', - 'url': '/manage/index.php?v=logs', - }, - ], - [ - { - 'name': 'emotes', - 'title': 'Emoticons', - 'url': '/manage/index.php?v=emoticons', - }, - { - 'name': 'settings', - 'title': 'Settings', - 'url': '/manage/index.php?v=settings', - }, - ], - ], - }, - { - 'name': 'users', - 'title': 'Users', - 'sections': [ - [ - { - 'name': 'listing', - 'title': 'Listing', - 'url': '/manage/users.php?v=listing', - }, - ], - [ - { - 'name': 'roles', - 'title': 'Roles', - 'url': '/manage/users.php?v=roles', - }, - { - 'name': 'perms', - 'title': 'Permissions', - 'url': '/manage/users.php?v=permissions', - }, - ], - [ - { - 'name': 'report', - 'title': 'Reports', - 'url': '/manage/users.php?v=reports', - }, - { - 'name': 'restrictions', - 'title': 'Restrictions', - 'url': '/manage/users.php?v=restrictions', - }, - { - 'name': 'blacklist', - 'title': 'Blacklisting', - 'url': '/manage/users.php?v=blacklist', - }, - ], - ], - }, - { - 'name': 'forum', - 'title': 'Forum', - 'sections': [ - [ - { - 'name': 'forums', - 'title': 'Listing', - 'url': '/manage/forums.php?v=listing', - }, - { - 'name': 'perms', - 'title': 'Permissions', - 'url': '/manage/forums.php?v=permissions', - }, - { - 'name': 'settings', - 'title': 'Settings', - 'url': '/manage/forums.php?v=settings', - }, - ], - ], - }, - { - 'name': 'changelog', - 'title': 'Changelog', - 'sections': [ - [ - { - 'name': 'changes', - 'title': 'Changes', - 'url': '/manage/changelog.php?v=changes', - }, - { - 'name': 'tags', - 'title': 'Tags', - 'url': '/manage/changelog.php?v=tags', - }, - { - 'name': 'actions', - 'title': 'Actions', - 'url': '/manage/changelog.php?v=actions', - }, - ], - ], - }, -] %} - @@ -138,18 +17,21 @@
- {% for menu in menus %} + {% for name, menu in manage_menu %}
- - + +
- {% for section in menu.sections %} -
- {% for item in section %} - {{ item.title }} - {% endfor %} -
+
+ {% for title, link in menu %} + {% if link == '_' %} +
+
+ {% else %} + {{ title }} + {% endif %} {% endfor %} +
{% endfor %} diff --git a/views/mio/master.twig b/views/mio/master.twig index 492006cf..f5680e47 100644 --- a/views/mio/master.twig +++ b/views/mio/master.twig @@ -38,7 +38,7 @@