Rewrote permissions system.

This commit is contained in:
flash 2023-08-30 22:37:21 +00:00
parent ca23822e40
commit 07a2868159
63 changed files with 1286 additions and 989 deletions

View File

@ -2,6 +2,6 @@
> Misuzu can and will steal your lunch money.
## Requirements
- PHP 8.2
- PHP 8.2 (64-bit)
- MariaDB 10.6
- [Composer](https://getcomposer.org/)

View File

@ -0,0 +1,125 @@
<?php
use Index\Data\IDbConnection;
use Index\Data\Migration\IDbMigration;
final class NewPermissionsSystem_20230830_213930 implements IDbMigration {
public function migrate(IDbConnection $conn): void {
// make sure cron doesn't fuck us over
$conn->execute('DELETE FROM msz_config WHERE config_name = "perms.needsRecalc"');
$conn->execute('
CREATE TABLE msz_perms (
user_id INT(10) UNSIGNED NULL DEFAULT NULL,
role_id INT(10) UNSIGNED NULL DEFAULT NULL,
forum_id INT(10) UNSIGNED NULL DEFAULT NULL,
perms_category VARBINARY(64) NOT NULL,
perms_allow BIGINT(20) UNSIGNED NOT NULL,
perms_deny BIGINT(20) UNSIGNED NOT NULL,
UNIQUE KEY perms_unique (user_id, role_id, forum_id, perms_category),
KEY perms_user_foreign (user_id),
KEY perms_role_foreign (role_id),
KEY perms_forum_foreign (forum_id),
KEY perms_category_index (perms_category),
CONSTRAINT perms_user_foreign
FOREIGN KEY (user_id)
REFERENCES msz_users (user_id)
ON UPDATE CASCADE
ON DELETE CASCADE,
CONSTRAINT perms_role_foreign
FOREIGN KEY (role_id)
REFERENCES msz_roles (role_id)
ON UPDATE CASCADE
ON DELETE CASCADE,
CONSTRAINT perms_forum_foreign
FOREIGN KEY (forum_id)
REFERENCES msz_forum_categories (forum_id)
ON UPDATE CASCADE
ON DELETE CASCADE
) ENGINE=InnoDB COLLATE=utf8mb4_bin
');
$conn->execute('
ALTER TABLE msz_perms
ADD CONSTRAINT perms_53bit
CHECK (perms_allow >= 0 AND perms_deny >= 0 AND perms_allow <= 9007199254740991 AND perms_deny <= 9007199254740991),
ADD CONSTRAINT perms_only_user_or_role
CHECK ((user_id IS NULL AND role_id IS NULL) OR (user_id IS NULL AND role_id IS NOT NULL) OR (user_id IS NOT NULL AND role_id IS NULL))
');
$conn->execute('
CREATE TABLE msz_perms_calculated (
user_id INT(10) UNSIGNED NULL DEFAULT NULL,
forum_id INT(10) UNSIGNED NULL DEFAULT NULL,
perms_category VARBINARY(64) NOT NULL,
perms_calculated BIGINT(20) UNSIGNED NOT NULL,
UNIQUE KEY perms_calculated_unique (user_id, forum_id, perms_category),
KEY perms_calculated_user_foreign (user_id),
KEY perms_calculated_forum_foreign (forum_id),
KEY perms_calculated_category_index (perms_category),
CONSTRAINT perms_calculated_user_foreign
FOREIGN KEY (user_id)
REFERENCES msz_users (user_id)
ON UPDATE CASCADE
ON DELETE CASCADE,
CONSTRAINT perms_calculated_forum_foreign
FOREIGN KEY (forum_id)
REFERENCES msz_forum_categories (forum_id)
ON UPDATE CASCADE
ON DELETE CASCADE
) ENGINE=InnoDB COLLATE=utf8mb4_bin
');
$conn->execute('
ALTER TABLE msz_perms_calculated
ADD CONSTRAINT perms_calculated_53bit
CHECK (perms_calculated >= 0 AND perms_calculated <= 9007199254740991)
');
$insert = $conn->prepare('INSERT INTO msz_perms (user_id, role_id, forum_id, perms_category, perms_allow, perms_deny) VALUES (?, ?, ?, ?, ?, ?)');
$result = $conn->query('SELECT user_id, role_id, general_perms_allow, general_perms_deny, user_perms_allow, user_perms_deny, changelog_perms_allow, changelog_perms_deny, news_perms_allow, news_perms_deny, forum_perms_allow, forum_perms_deny, comments_perms_allow, comments_perms_deny FROM msz_permissions');
while($result->next()) {
$insert->addParameter(1, $result->isNull(0) ? null : $result->getString(0));
$insert->addParameter(2, $result->isNull(1) ? null : $result->getString(1));
$insert->addParameter(3, null);
$insert->addParameter(4, 'user');
$insert->addParameter(5, $result->getInteger(4));
$insert->addParameter(6, $result->getInteger(5));
$insert->execute();
$allow = $result->getInteger(2);
$allow |= $result->getInteger(6) << 8;
$allow |= $result->getInteger(8) << 16;
$allow |= $result->getInteger(10) << 24;
$allow |= $result->getInteger(12) << 32;
$deny = $result->getInteger(3);
$deny |= $result->getInteger(7) << 8;
$deny |= $result->getInteger(9) << 16;
$deny |= $result->getInteger(11) << 24;
$deny |= $result->getInteger(13) << 32;
$insert->addParameter(4, 'global');
$insert->addParameter(5, $allow);
$insert->addParameter(6, $deny);
$insert->execute();
}
$result = $conn->query('SELECT user_id, role_id, forum_id, forum_perms_allow, forum_perms_deny FROM msz_forum_permissions');
while($result->next()) {
$insert->addParameter(1, $result->isNull(0) ? null : $result->getString(0));
$insert->addParameter(2, $result->isNull(1) ? null : $result->getString(1));
$insert->addParameter(3, $result->getString(2));
$insert->addParameter(4, 'forum');
$insert->addParameter(5, $result->getInteger(3));
$insert->addParameter(6, $result->getInteger(4));
$insert->execute();
}
$conn->execute('DROP TABLE msz_forum_permissions');
$conn->execute('DROP TABLE msz_permissions');
// schedule recalc
$conn->execute('INSERT INTO msz_config (config_name, config_value) VALUES ("perms.needsRecalc", "b:1;")');
}
}

View File

@ -23,7 +23,6 @@ mb_internal_encoding('utf-8');
date_default_timezone_set('utc');
require_once MSZ_ROOT . '/utility.php';
require_once MSZ_SOURCE . '/perms.php';
require_once MSZ_SOURCE . '/url.php';
$dbConfig = parse_ini_file(MSZ_CONFIG . '/config.ini', true, INI_SCANNER_TYPED);

View File

@ -113,7 +113,7 @@ while(!empty($_POST['login']) && is_array($_POST['login'])) {
if($userInfo->passwordNeedsRehash())
$users->updateUser($userInfo, password: $_POST['login']['password']);
if(!empty($loginPermCat) && $loginPermVal > 0 && !perms_check_user($loginPermCat, $userInfo->getId(), $loginPermVal)) {
if(!empty($loginPermCat) && $loginPermVal > 0 && !$msz->getPerms()->checkPermissions($loginPermCat, $loginPermVal, $userInfo)) {
$notices[] = "Login succeeded, but you're not allowed to browse the site right now.";
$loginAttempts->recordAttempt(true, $ipAddress, $countryCode, $userAgent, $clientInfo, $userInfo);
break;

View File

@ -30,7 +30,7 @@ if($msz->hasActiveBan()) {
$currentUserInfo = $msz->getActiveUser();
$comments = $msz->getComments();
$commentPerms = perms_for_comments($currentUserInfo->getId());
$perms = $msz->getAuthInfo()->getPerms('global');
$commentId = (string)filter_input(INPUT_GET, 'c', FILTER_SANITIZE_NUMBER_INT);
$commentMode = (string)filter_input(INPUT_GET, 'm');
@ -55,7 +55,7 @@ if($commentMode !== 'create' && empty($commentInfo)) {
switch($commentMode) {
case 'pin':
case 'unpin':
if(!$commentPerms['can_pin'] && !$categoryInfo->isOwner($currentUserInfo)) {
if(!$perms->check(Perm::G_COMMENTS_PIN) && !$categoryInfo->isOwner($currentUserInfo)) {
echo render_info("You're not allowed to pin comments.", 403);
break;
}
@ -92,7 +92,7 @@ switch($commentMode) {
break;
case 'vote':
if(!$commentPerms['can_vote'] && !$categoryInfo->isOwner($currentUserInfo)) {
if(!$perms->check(Perm::G_COMMENTS_VOTE) && !$categoryInfo->isOwner($currentUserInfo)) {
echo render_info("You're not allowed to vote on comments.", 403);
break;
}
@ -113,21 +113,23 @@ switch($commentMode) {
break;
case 'delete':
if(!$commentPerms['can_delete'] && !$categoryInfo->isOwner($currentUserInfo)) {
$canDelete = $perms->check(Perm::G_COMMENTS_DELETE_OWN | Perm::G_COMMENTS_DELETE_ANY);
if(!$canDelete && !$categoryInfo->isOwner($currentUserInfo)) {
echo render_info("You're not allowed to delete comments.", 403);
break;
}
$canDeleteAny = $perms->check(Perm::G_COMMENTS_DELETE_ANY);
if($commentInfo->isDeleted()) {
echo render_info(
$commentPerms['can_delete_any'] ? 'This comment is already marked for deletion.' : "This comment doesn't exist.",
$canDeleteAny ? 'This comment is already marked for deletion.' : "This comment doesn't exist.",
400
);
break;
}
$isOwnComment = $commentInfo->getUserId() === $currentUserInfo->getId();
$isModAction = $commentPerms['can_delete_any'] && !$isOwnComment;
$isModAction = $canDeleteAny && !$isOwnComment;
if(!$isModAction && !$isOwnComment) {
echo render_info("You're not allowed to delete comments made by others.", 403);
@ -150,7 +152,7 @@ switch($commentMode) {
break;
case 'restore':
if(!$commentPerms['can_delete_any']) {
if(!$perms->check(Perm::G_COMMENTS_DELETE_ANY)) {
echo render_info("You're not allowed to restore deleted comments.", 403);
break;
}
@ -172,7 +174,7 @@ switch($commentMode) {
break;
case 'create':
if(!$commentPerms['can_comment'] && !$categoryInfo->isOwner($currentUserInfo)) {
if(!$perms->check(Perm::G_COMMENTS_CREATE) && !$categoryInfo->isOwner($currentUserInfo)) {
echo render_info("You're not allowed to post comments.", 403);
break;
}
@ -192,15 +194,16 @@ switch($commentMode) {
break;
}
if($categoryInfo->isLocked() && !$commentPerms['can_lock']) {
$canLock = $perms->check(Perm::G_COMMENTS_LOCK);
if($categoryInfo->isLocked() && !$canLock) {
echo render_info('This comment category has been locked.', 403);
break;
}
$commentText = !empty($_POST['comment']['text']) && is_string($_POST['comment']['text']) ? $_POST['comment']['text'] : '';
$commentReply = (string)(!empty($_POST['comment']['reply']) && is_string($_POST['comment']['reply']) ? (int)$_POST['comment']['reply'] : 0);
$commentLock = !empty($_POST['comment']['lock']) && $commentPerms['can_lock'];
$commentPin = !empty($_POST['comment']['pin']) && $commentPerms['can_pin'];
$commentLock = !empty($_POST['comment']['lock']) && $canLock;
$commentPin = !empty($_POST['comment']['pin']) && $perms->check(Perm::G_COMMENTS_PIN);
if($commentLock) {
if($categoryInfo->isLocked())
@ -212,7 +215,7 @@ switch($commentMode) {
if(strlen($commentText) > 0) {
$commentText = preg_replace("/[\r\n]{2,}/", "\n", $commentText);
} else {
if($commentPerms['can_lock']) {
if($canLock) {
echo render_info('The action has been processed.', 400);
} else {
echo render_info('Your comment is too short.', 400);

View File

@ -3,6 +3,7 @@ namespace Misuzu;
use stdClass;
use RuntimeException;
use Index\XArray;
$forum = $msz->getForum();
$users = $msz->getUsers();
@ -16,18 +17,18 @@ try {
return;
}
$perms = $msz->getAuthInfo()->getPerms('forum', $categoryInfo);
$currentUser = $msz->getActiveUser();
$currentUserId = $currentUser === null ? '0' : $currentUser->getId();
$perms = forum_perms_get_user($categoryInfo->getId(), $currentUserId)[MSZ_FORUM_PERMS_GENERAL];
if(!perms_check($perms, MSZ_FORUM_PERM_VIEW_FORUM)) {
if(!$perms->check(Perm::F_CATEGORY_VIEW)) {
echo render_error(403);
return;
}
if(isset($currentUser) && $msz->hasActiveBan($currentUser))
$perms &= MSZ_FORUM_PERM_LIST_FORUM | MSZ_FORUM_PERM_VIEW_FORUM;
if($msz->hasActiveBan())
$perms = $perms->apply(fn($calc) => $calc & (Perm::F_CATEGORY_LIST | Perm::F_CATEGORY_VIEW));
if($categoryInfo->isLink()) {
if($categoryInfo->hasLinkTarget()) {
@ -40,7 +41,7 @@ if($categoryInfo->isLink()) {
$forumPagination = new Pagination($forum->countTopics(
categoryInfo: $categoryInfo,
global: true,
deleted: perms_check($perms, MSZ_FORUM_PERM_DELETE_ANY_POST) ? null : false
deleted: $perms->check(Perm::F_POST_DELETE_ANY) ? null : false
), 20);
if(!$forumPagination->hasValidOffset()) {
@ -56,9 +57,9 @@ $topics = [];
if($categoryInfo->mayHaveChildren()) {
$children = $forum->getCategoryChildren($categoryInfo, hidden: false, asTree: true);
foreach($children as $child) {
$childPerms = forum_perms_get_user($child->info->getId(), (int)$currentUserId)[MSZ_FORUM_PERMS_GENERAL];
if(!perms_check($childPerms, MSZ_FORUM_PERM_LIST_FORUM)) {
foreach($children as $childId => $child) {
$childPerms = $msz->getAuthInfo()->getPerms('forum', $child->info);
if(!$childPerms->check(Perm::F_CATEGORY_LIST)) {
unset($category->children[$childId]);
continue;
}
@ -67,8 +68,8 @@ if($categoryInfo->mayHaveChildren()) {
if($child->info->mayHaveChildren()) {
foreach($child->children as $grandChildId => $grandChild) {
$grandChildPerms = forum_perms_get_user($grandChild->info->getId(), (int)$currentUserId)[MSZ_FORUM_PERMS_GENERAL];
if(!perms_check($grandChildPerms, MSZ_FORUM_PERM_LIST_FORUM)) {
$grandChildPerms = $msz->getAuthInfo()->getPerms('forum', $grandChild->info);
if(!$grandChildPerms->check(Perm::F_CATEGORY_LIST)) {
unset($child->children[$grandChildId]);
continue;
}
@ -78,8 +79,8 @@ if($categoryInfo->mayHaveChildren()) {
if($grandChild->info->mayHaveTopics()) {
$catIds = [$grandChild->info->getId()];
foreach($grandChild->childIds as $greatGrandChildId) {
$greatGrandChildPerms = forum_perms_get_user($greatGrandChildId, (int)$currentUserId)[MSZ_FORUM_PERMS_GENERAL];
if(perms_check($greatGrandChildPerms, MSZ_FORUM_PERM_LIST_FORUM))
$greatGrandChildPerms = $msz->getAuthInfo()->getPerms('forum', $greatGrandChildId);
if(!$greatGrandChildPerms->check(Perm::F_CATEGORY_LIST))
$catIds[] = $greatGrandChildId;
}
@ -96,8 +97,8 @@ if($categoryInfo->mayHaveChildren()) {
if($child->info->mayHaveChildren() || $child->info->mayHaveTopics()) {
$catIds = [$child->info->getId()];
foreach($child->childIds as $grandChildId) {
$grandChildPerms = forum_perms_get_user($grandChildId, (int)$currentUserId)[MSZ_FORUM_PERMS_GENERAL];
if(perms_check($grandChildPerms, MSZ_FORUM_PERM_LIST_FORUM))
$grandChildPerms = $msz->getAuthInfo()->getPerms('forum', $grandChildId);
if($grandChildPerms->check(Perm::F_CATEGORY_LIST))
$catIds[] = $grandChildId;
}
@ -138,7 +139,7 @@ if($categoryInfo->mayHaveTopics()) {
$topicInfos = $forum->getTopics(
categoryInfo: $categoryInfo,
global: true,
deleted: perms_check($perms, MSZ_FORUM_PERM_DELETE_ANY_POST) ? null : false,
deleted: $perms->check(Perm::F_POST_DELETE_ANY) ? null : false,
pagination: $forumPagination,
);
@ -183,8 +184,8 @@ if($categoryInfo->mayHaveTopics()) {
}
}
$perms = perms_check_bulk($perms, [
'can_create_topic' => MSZ_FORUM_PERM_CREATE_TOPIC,
$perms = $perms->checkMany([
'can_create_topic' => Perm::F_TOPIC_CREATE,
]);
Template::render('forum.forum', [

View File

@ -25,8 +25,8 @@ if($mode === 'mark') {
: $forum->getCategoryChildren(parentInfo: $categoryId, includeSelf: true);
foreach($categoryInfos as $categoryInfo) {
$perms = forum_perms_get_user($categoryInfo->getId(), (int)$currentUserId)[MSZ_FORUM_PERMS_GENERAL];
if(perms_check($perms, MSZ_FORUM_PERM_LIST_FORUM))
$perms = $msz->getAuthInfo()->getPerms('forum', $categoryInfo);
if($perms->check(Perm::F_CATEGORY_LIST))
$forum->updateUserReadCategory($userInfo, $categoryInfo);
}
@ -55,8 +55,8 @@ $userColours = [];
$categories = $forum->getCategories(hidden: false, asTree: true);
foreach($categories as $categoryId => $category) {
$perms = forum_perms_get_user($category->info->getId(), (int)$currentUserId)[MSZ_FORUM_PERMS_GENERAL];
if(!perms_check($perms, MSZ_FORUM_PERM_LIST_FORUM)) {
$perms = $msz->getAuthInfo()->getPerms('forum', $category->info);
if(!$perms->check(Perm::F_CATEGORY_LIST)) {
unset($categories[$categoryId]);
continue;
}
@ -65,8 +65,8 @@ foreach($categories as $categoryId => $category) {
if($category->info->mayHaveChildren())
foreach($category->children as $childId => $child) {
$childPerms = forum_perms_get_user($child->info->getId(), (int)$currentUserId)[MSZ_FORUM_PERMS_GENERAL];
if(!perms_check($childPerms, MSZ_FORUM_PERM_LIST_FORUM)) {
$childPerms = $msz->getAuthInfo()->getPerms('forum', $child->info);
if(!$childPerms->check(Perm::F_CATEGORY_LIST)) {
unset($category->children[$childId]);
continue;
}
@ -76,8 +76,8 @@ foreach($categories as $categoryId => $category) {
if($category->info->isListing()) {
if($child->info->mayHaveChildren()) {
foreach($child->children as $grandChildId => $grandChild) {
$grandChildPerms = forum_perms_get_user($grandChild->info->getId(), (int)$currentUserId)[MSZ_FORUM_PERMS_GENERAL];
if(!perms_check($grandChildPerms, MSZ_FORUM_PERM_LIST_FORUM)) {
$grandChildPerms = $msz->getAuthInfo()->getPerms('forum', $grandChild->info);
if(!$grandChildPerms->check(Perm::F_CATEGORY_LIST)) {
unset($child->children[$grandChildId]);
continue;
}
@ -87,8 +87,8 @@ foreach($categories as $categoryId => $category) {
if($grandChild->info->mayHaveTopics()) {
$catIds = [$grandChild->info->getId()];
foreach($grandChild->childIds as $greatGrandChildId) {
$greatGrandChildPerms = forum_perms_get_user($greatGrandChildId, (int)$currentUserId)[MSZ_FORUM_PERMS_GENERAL];
if(perms_check($greatGrandChildPerms, MSZ_FORUM_PERM_LIST_FORUM))
$greatGrandChildPerms = $msz->getAuthInfo()->getPerms('forum', $greatGrandChildId);
if($greatGrandChildPerms->check(Perm::F_CATEGORY_LIST))
$catIds[] = $greatGrandChildId;
}
@ -105,8 +105,8 @@ foreach($categories as $categoryId => $category) {
if($child->info->mayHaveChildren() || $child->info->mayHaveTopics()) {
$catIds = [$child->info->getId()];
foreach($child->childIds as $grandChildId) {
$grandChildPerms = forum_perms_get_user($grandChildId, (int)$currentUserId)[MSZ_FORUM_PERMS_GENERAL];
if(perms_check($grandChildPerms, MSZ_FORUM_PERM_LIST_FORUM))
$grandChildPerms = $msz->getAuthInfo()->getPerms('forum', $grandChildId);
if($grandChildPerms->check(Perm::F_CATEGORY_LIST))
$catIds[] = $grandChildId;
}
@ -165,8 +165,8 @@ foreach($categories as $categoryId => $category) {
if($category->info->mayHaveChildren() || $category->info->mayHaveTopics()) {
$catIds = [$category->info->getId()];
foreach($category->childIds as $childId) {
$childPerms = forum_perms_get_user($childId, (int)$currentUserId)[MSZ_FORUM_PERMS_GENERAL];
if(perms_check($childPerms, MSZ_FORUM_PERM_LIST_FORUM))
$childPerms = $msz->getAuthInfo()->getPerms('forum', $childId);
if($childPerms->check(Perm::F_CATEGORY_LIST))
$catIds[] = $childId;
}

View File

@ -3,7 +3,7 @@ namespace Misuzu;
use RuntimeException;
if(!$msz->isLoggedIn() || !perms_check_user(MSZ_PERMS_FORUM, $msz->getActiveUser()->getId(), MSZ_PERM_FORUM_VIEW_LEADERBOARD)) {
if(!$msz->getAuthInfo()->getPerms('global')->check(Perm::G_FORUM_LEADERBOARD_VIEW)) {
echo render_error(403);
return;
}

View File

@ -31,14 +31,14 @@ try {
return;
}
$perms = forum_perms_get_user($postInfo->getCategoryId(), $currentUserId)[MSZ_FORUM_PERMS_GENERAL];
$perms = $msz->getAuthInfo()->getPerms('forum', $postInfo->getCategoryId());
if(!perms_check($perms, MSZ_FORUM_PERM_VIEW_FORUM)) {
if(!$perms->check(Perm::F_CATEGORY_VIEW)) {
echo render_error(403);
return;
}
$canDeleteAny = perms_check($perms, MSZ_FORUM_PERM_DELETE_ANY_POST);
$canDeleteAny = $perms->check(Perm::F_POST_DELETE_ANY);
switch($postMode) {
case 'delete':
@ -53,7 +53,7 @@ switch($postMode) {
return;
}
if(!perms_check($perms, MSZ_FORUM_PERM_DELETE_POST)) {
if(!$perms->check(Perm::F_POST_DELETE_OWN)) {
echo render_info('You are not allowed to delete posts.', 403);
return;
}

View File

@ -121,12 +121,13 @@ if(empty($forumId)) {
$hasCategoryInfo = true;
}
$perms = forum_perms_get_user($categoryInfo->getId(), $currentUserId)[MSZ_FORUM_PERMS_GENERAL];
$perms = $msz->getAuthInfo()->getPerms('forum', $categoryInfo);
if($categoryInfo->isArchived()
|| (isset($topicInfo) && $topicInfo->isLocked() && !perms_check($perms, MSZ_FORUM_PERM_LOCK_TOPIC))
|| !perms_check($perms, MSZ_FORUM_PERM_VIEW_FORUM | MSZ_FORUM_PERM_CREATE_POST)
|| (!isset($topicInfo) && !perms_check($perms, MSZ_FORUM_PERM_CREATE_TOPIC))) {
|| (isset($topicInfo) && $topicInfo->isLocked() && !$perms->check(Perm::F_TOPIC_LOCK))
|| !$perms->check(Perm::F_CATEGORY_VIEW)
|| !$perms->check(Perm::F_POST_CREATE)
|| (!isset($topicInfo) && !$perms->check(Perm::F_TOPIC_CREATE))) {
echo render_error(403);
return;
}
@ -141,16 +142,16 @@ $topicTypes = [];
if($mode === 'create' || $mode === 'edit') {
$topicTypes['discussion'] = 'Normal discussion';
if(perms_check($perms, MSZ_FORUM_PERM_STICKY_TOPIC))
if($perms->check(Perm::F_TOPIC_STICKY))
$topicTypes['sticky'] = 'Sticky topic';
if(perms_check($perms, MSZ_FORUM_PERM_ANNOUNCE_TOPIC))
if($perms->check(Perm::F_TOPIC_ANNOUNCE_LOCAL))
$topicTypes['announce'] = 'Announcement';
if(perms_check($perms, MSZ_FORUM_PERM_GLOBAL_ANNOUNCE_TOPIC))
if($perms->check(Perm::F_TOPIC_ANNOUNCE_GLOBAL))
$topicTypes['global'] = 'Global Announcement';
}
// edit mode stuff
if($mode === 'edit' && !perms_check($perms, $postInfo->getUserId() === $currentUserId ? MSZ_FORUM_PERM_EDIT_POST : MSZ_FORUM_PERM_EDIT_ANY_POST)) {
if($mode === 'edit' && !$perms->check($postInfo->getUserId() === $currentUserId ? Perm::F_POST_EDIT_OWN : Perm::F_POST_EDIT_ANY)) {
echo render_error(403);
return;
}

View File

@ -25,8 +25,8 @@ if($topicId < 1 && $postId > 0) {
}
$categoryId = $postInfo->getCategoryId();
$perms = forum_perms_get_user($categoryId, $currentUserId)[MSZ_FORUM_PERMS_GENERAL];
$canDeleteAny = !perms_check($perms, MSZ_FORUM_PERM_DELETE_ANY_POST);
$perms = $msz->getAuthInfo()->getPerms('forum', $postInfo->getCategoryId());
$canDeleteAny = $perms->check(Perm::F_POST_DELETE_ANY);
if($postInfo->isDeleted() && !$canDeleteAny) {
echo render_error(404);
@ -53,13 +53,13 @@ if(!$topicIsNuked) {
if($categoryId !== (int)$topicInfo->getCategoryId()) {
$categoryId = (int)$topicInfo->getCategoryId();
$perms = forum_perms_get_user($categoryId, $currentUserId)[MSZ_FORUM_PERMS_GENERAL];
$perms = $msz->getAuthInfo()->getPerms('forum', $topicInfo->getCategoryId());
}
if(isset($currentUser) && $msz->hasActiveBan($currentUser))
$perms &= MSZ_FORUM_PERM_LIST_FORUM | MSZ_FORUM_PERM_VIEW_FORUM;
if($msz->hasActiveBan())
$perms = $perms->apply(fn($calc) => $calc & (Perm::F_CATEGORY_LIST | Perm::F_CATEGORY_VIEW));
$canDeleteAny = perms_check($perms, MSZ_FORUM_PERM_DELETE_ANY_POST);
$canDeleteAny = $perms->check(Perm::F_POST_DELETE_ANY);
}
if(($topicIsNuked || $topicIsDeleted) && $forum->hasTopicRedirect($topicId)) {
@ -75,7 +75,7 @@ if(($topicIsNuked || $topicIsDeleted) && $forum->hasTopicRedirect($topicId)) {
}
}
if(!perms_check($perms, MSZ_FORUM_PERM_VIEW_FORUM)) {
if(!$perms->check(Perm::F_CATEGORY_VIEW)) {
echo render_error(403);
return;
}
@ -89,9 +89,9 @@ $topicIsLocked = $topicInfo->isLocked();
$topicIsArchived = $categoryInfo->isArchived();
$topicPostsTotal = $topicInfo->getTotalPostsCount();
$topicIsFrozen = $topicIsArchived || $topicIsDeleted;
$canDeleteOwn = !$topicIsFrozen && !$topicIsLocked && perms_check($perms, MSZ_FORUM_PERM_DELETE_POST);
$canBumpTopic = !$topicIsFrozen && perms_check($perms, MSZ_FORUM_PERM_BUMP_TOPIC);
$canLockTopic = !$topicIsFrozen && perms_check($perms, MSZ_FORUM_PERM_LOCK_TOPIC);
$canDeleteOwn = !$topicIsFrozen && !$topicIsLocked && $perms->check(Perm::F_POST_DELETE_OWN);
$canBumpTopic = !$topicIsFrozen && $perms->check(Perm::F_TOPIC_BUMP);
$canLockTopic = !$topicIsFrozen && $perms->check(Perm::F_TOPIC_LOCK);
$canNukeOrRestore = $canDeleteAny && $topicIsDeleted;
$canDelete = !$topicIsDeleted && (
$canDeleteAny || (
@ -304,7 +304,7 @@ if(!$topicPagination->hasValidOffset()) {
$postInfos = $forum->getPosts(
topicInfo: $topicInfo,
deleted: perms_check($perms, MSZ_FORUM_PERM_DELETE_ANY_POST) ? null : false,
deleted: $perms->check(Perm::F_POST_DELETE_ANY) ? null : false,
pagination: $topicPagination,
);
@ -343,19 +343,19 @@ foreach($postInfos as $postInfo) {
&& $originalPostInfo->getUserId() === $postInfo->getUserId();
}
$canReply = !$topicIsArchived && !$topicIsLocked && !$topicIsDeleted && perms_check($perms, MSZ_FORUM_PERM_CREATE_POST);
$canReply = !$topicIsArchived && !$topicIsLocked && !$topicIsDeleted && $perms->check(Perm::F_POST_CREATE);
if(!$forum->checkUserHasReadTopic($userInfo, $topicInfo))
$forum->incrementTopicView($topicInfo);
$forum->updateUserReadTopic($currentUser, $topicInfo);
$perms = perms_check_bulk($perms, [
'can_create_post' => MSZ_FORUM_PERM_CREATE_POST,
'can_edit_post' => MSZ_FORUM_PERM_EDIT_POST,
'can_edit_any_post' => MSZ_FORUM_PERM_EDIT_ANY_POST,
'can_delete_post' => MSZ_FORUM_PERM_DELETE_POST,
'can_delete_any_post' => MSZ_FORUM_PERM_DELETE_ANY_POST,
$perms = $perms->checkMany([
'can_create_post' => Perm::F_POST_CREATE,
'can_edit_post' => Perm::F_POST_EDIT_OWN,
'can_edit_any_post' => Perm::F_POST_EDIT_ANY,
'can_delete_post' => Perm::F_POST_DELETE_OWN,
'can_delete_any_post' => Perm::F_POST_DELETE_ANY,
]);
Template::render('forum.topic', [

View File

@ -7,7 +7,7 @@ use Index\DateTime;
use Index\XArray;
use Misuzu\Changelog\Changelog;
if(!$msz->isLoggedIn() || !perms_check_user(MSZ_PERMS_CHANGELOG, $msz->getActiveUser()->getId(), MSZ_PERM_CHANGELOG_MANAGE_CHANGES)) {
if(!$msz->getAuthInfo()->getPerms('global')->check(Perm::G_CL_CHANGES_MANAGE)) {
echo render_error(403);
return;
}

View File

@ -3,7 +3,7 @@ namespace Misuzu;
use RuntimeException;
if(!$msz->isLoggedIn() || !perms_check_user(MSZ_PERMS_CHANGELOG, $msz->getActiveUser()->getId(), MSZ_PERM_CHANGELOG_MANAGE_CHANGES)) {
if(!$msz->getAuthInfo()->getPerms('global')->check(Perm::G_CL_CHANGES_MANAGE)) {
echo render_error(403);
return;
}

View File

@ -3,7 +3,7 @@ namespace Misuzu;
use RuntimeException;
if(!$msz->isLoggedIn() || !perms_check_user(MSZ_PERMS_CHANGELOG, $msz->getActiveUser()->getId(), MSZ_PERM_CHANGELOG_MANAGE_TAGS)) {
if(!$msz->getAuthInfo()->getPerms('global')->check(Perm::G_CL_TAGS_MANAGE)) {
echo render_error(403);
return;
}

View File

@ -1,7 +1,7 @@
<?php
namespace Misuzu;
if(!$msz->isLoggedIn() || !perms_check_user(MSZ_PERMS_CHANGELOG, $msz->getActiveUser()->getId(), MSZ_PERM_CHANGELOG_MANAGE_TAGS)) {
if(!$msz->getAuthInfo()->getPerms('global')->check(Perm::G_CL_TAGS_MANAGE)) {
echo render_error(403);
return;
}

View File

@ -1,18 +1,24 @@
<?php
namespace Misuzu;
if(!$msz->isLoggedIn() || !perms_check_user(MSZ_PERMS_GENERAL, $msz->getActiveUser()->getId(), MSZ_PERM_FORUM_MANAGE_FORUMS)) {
use Misuzu\Perm;
if(!$msz->getAuthInfo()->getPerms('global')->check(Perm::G_FORUM_CATEGORIES_MANAGE)) {
echo render_error(403);
return;
}
$rawPerms = perms_create(MSZ_FORUM_PERM_MODES);
$perms = manage_forum_perms_list($rawPerms);
$perms = $msz->getPerms();
$permsInfos = $perms->getPermissionInfo(categoryNames: Perm::INFO_FOR_FORUM_CATEGORY);
$permsLists = Perm::createList(Perm::LISTS_FOR_FORUM_CATEGORY);
if(!empty($_POST['perms']) && is_array($_POST['perms'])) {
$finalPerms = manage_perms_apply($perms, $_POST['perms'], $rawPerms);
$perms = manage_forum_perms_list($finalPerms);
Template::set('calculated_perms', $finalPerms);
}
if(filter_has_var(INPUT_POST, 'perms'))
Template::set('calculated_perms', Perm::convertSubmission(
filter_input(INPUT_POST, 'perms', FILTER_DEFAULT, FILTER_REQUIRE_ARRAY),
Perm::INFO_FOR_FORUM_CATEGORY
));
Template::render('manage.forum.listing', compact('perms'));
Template::render('manage.forum.listing', [
'perms_lists' => $permsLists,
'perms_infos' => $permsInfos,
]);

View File

@ -1,7 +1,7 @@
<?php
namespace Misuzu;
if(!$msz->isLoggedIn() || !perms_check_user(MSZ_PERMS_GENERAL, $msz->getActiveUser()->getId(), MSZ_PERM_FORUM_TOPIC_REDIRS)) {
if(!$msz->getAuthInfo()->getPerms('global')->check(Perm::G_FORUM_TOPIC_REDIRS_MANAGE)) {
echo render_error(403);
return;
}

View File

@ -4,7 +4,7 @@ namespace Misuzu;
use RuntimeException;
use Index\XArray;
if(!$msz->isLoggedIn() || !perms_check_user(MSZ_PERMS_GENERAL, $msz->getActiveUser()->getId(), MSZ_PERM_GENERAL_MANAGE_EMOTES)) {
if(!$msz->getAuthInfo()->getPerms('global')->check(Perm::G_EMOTES_MANAGE)) {
echo render_error(403);
return;
}

View File

@ -3,7 +3,7 @@ namespace Misuzu;
use RuntimeException;
if(!$msz->isLoggedIn() || !perms_check_user(MSZ_PERMS_GENERAL, $msz->getActiveUser()->getId(), MSZ_PERM_GENERAL_MANAGE_EMOTES)) {
if(!$msz->getAuthInfo()->getPerms('global')->check(Perm::G_EMOTES_MANAGE)) {
echo render_error(403);
return;
}

View File

@ -3,7 +3,7 @@ namespace Misuzu;
use Misuzu\Pagination;
if(!$msz->isLoggedIn() || !perms_check_user(MSZ_PERMS_GENERAL, $msz->getActiveUser()->getId(), MSZ_PERM_GENERAL_VIEW_LOGS)) {
if(!$msz->getAuthInfo()->getPerms('global')->check(Perm::G_LOGS_VIEW)) {
echo render_error(403);
return;
}

View File

@ -3,7 +3,7 @@ namespace Misuzu;
use Misuzu\Config\CfgTools;
if(!$msz->isLoggedIn() || !perms_check_user(MSZ_PERMS_GENERAL, $msz->getActiveUser()->getId(), MSZ_PERM_GENERAL_MANAGE_CONFIG)) {
if(!$msz->getAuthInfo()->getPerms('global')->check(Perm::G_CONFIG_MANAGE)) {
echo render_error(403);
return;
}

View File

@ -3,7 +3,7 @@ namespace Misuzu;
use Misuzu\Config\DbConfig;
if(!$msz->isLoggedIn() || !perms_check_user(MSZ_PERMS_GENERAL, $msz->getActiveUser()->getId(), MSZ_PERM_GENERAL_MANAGE_CONFIG)) {
if(!$msz->getAuthInfo()->getPerms('global')->check(Perm::G_CONFIG_MANAGE)) {
echo render_error(403);
return;
}

View File

@ -1,7 +1,7 @@
<?php
namespace Misuzu;
if(!$msz->isLoggedIn() || !perms_check_user(MSZ_PERMS_GENERAL, $msz->getActiveUser()->getId(), MSZ_PERM_GENERAL_MANAGE_CONFIG)) {
if(!$msz->getAuthInfo()->getPerms('global')->check(Perm::G_CONFIG_MANAGE)) {
echo render_error(403);
return;
}

View File

@ -1,7 +1,7 @@
<?php
namespace Misuzu;
if(!$msz->isLoggedIn() || !perms_check_user(MSZ_PERMS_NEWS, $msz->getActiveUser()->getId(), MSZ_PERM_NEWS_MANAGE_CATEGORIES)) {
if(!$msz->getAuthInfo()->getPerms('global')->check(Perm::G_NEWS_CATEGORIES_MANAGE)) {
echo render_error(403);
return;
}

View File

@ -3,7 +3,7 @@ namespace Misuzu;
use RuntimeException;
if(!$msz->isLoggedIn() || !perms_check_user(MSZ_PERMS_NEWS, $msz->getActiveUser()->getId(), MSZ_PERM_NEWS_MANAGE_CATEGORIES)) {
if(!$msz->getAuthInfo()->getPerms('global')->check(Perm::G_NEWS_CATEGORIES_MANAGE)) {
echo render_error(403);
return;
}

View File

@ -3,7 +3,7 @@ namespace Misuzu;
use RuntimeException;
if(!$msz->isLoggedIn() || !perms_check_user(MSZ_PERMS_NEWS, $msz->getActiveUser()->getId(), MSZ_PERM_NEWS_MANAGE_POSTS)) {
if(!$msz->getAuthInfo()->getPerms('global')->check(Perm::G_NEWS_POSTS_MANAGE)) {
echo render_error(403);
return;
}

View File

@ -1,7 +1,7 @@
<?php
namespace Misuzu;
if(!$msz->isLoggedIn() || !perms_check_user(MSZ_PERMS_NEWS, $msz->getActiveUser()->getId(), MSZ_PERM_NEWS_MANAGE_POSTS)) {
if(!$msz->getAuthInfo()->getPerms('global')->check(Perm::G_NEWS_POSTS_MANAGE)) {
echo render_error(403);
return;
}

View File

@ -5,7 +5,7 @@ use DateTimeInterface;
use RuntimeException;
use Index\DateTime;
if(!$msz->isLoggedIn() || !perms_check_user(MSZ_PERMS_USER, $msz->getActiveUser()->getId(), MSZ_PERM_USER_MANAGE_BANS)) {
if(!$msz->getAuthInfo()->getPerms('user')->check(Perm::U_BANS_MANAGE)) {
echo render_error(403);
return;
}

View File

@ -3,7 +3,7 @@ namespace Misuzu;
use RuntimeException;
if(!$msz->isLoggedIn() || !perms_check_user(MSZ_PERMS_USER, $msz->getActiveUser()->getId(), MSZ_PERM_USER_MANAGE_BANS)) {
if(!$msz->getAuthInfo()->getPerms('user')->check(Perm::U_BANS_MANAGE)) {
echo render_error(403);
return;
}

View File

@ -1,7 +1,7 @@
<?php
namespace Misuzu;
if(!$msz->isLoggedIn() || !perms_check_user(MSZ_PERMS_USER, $msz->getActiveUser()->getId(), MSZ_PERM_USER_MANAGE_USERS)) {
if(!$msz->getAuthInfo()->getPerms('user')->check(Perm::U_USERS_MANAGE)) {
echo render_error(403);
return;
}

View File

@ -3,7 +3,7 @@ namespace Misuzu;
use RuntimeException;
if(!$msz->isLoggedIn() || !perms_check_user(MSZ_PERMS_USER, $msz->getActiveUser()->getId(), MSZ_PERM_USER_MANAGE_NOTES)) {
if(!$msz->getAuthInfo()->getPerms('user')->check(Perm::U_NOTES_MANAGE)) {
echo render_error(403);
return;
}

View File

@ -3,7 +3,7 @@ namespace Misuzu;
use RuntimeException;
if(!$msz->isLoggedIn() || !perms_check_user(MSZ_PERMS_USER, $msz->getActiveUser()->getId(), MSZ_PERM_USER_MANAGE_NOTES)) {
if(!$msz->getAuthInfo()->getPerms('user')->check(Perm::U_NOTES_MANAGE)) {
echo render_error(403);
return;
}

View File

@ -4,14 +4,17 @@ namespace Misuzu;
use RuntimeException;
use Index\Colour\Colour;
use Index\Colour\ColourRGB;
use Misuzu\Perm;
if(!$msz->isLoggedIn() || !perms_check_user(MSZ_PERMS_USER, $msz->getActiveUser()->getId(), MSZ_PERM_USER_MANAGE_ROLES)) {
$viewerPerms = $msz->getAuthInfo()->getPerms('user');
if(!$viewerPerms->check(Perm::U_ROLES_MANAGE)) {
echo render_error(403);
return;
}
$users = $msz->getUsers();
$roles = $msz->getRoles();
$perms = $msz->getPerms();
if(filter_has_var(INPUT_GET, 'r')) {
$roleId = (string)filter_input(INPUT_GET, 'r', FILTER_SANITIZE_NUMBER_INT);
@ -26,10 +29,10 @@ if(filter_has_var(INPUT_GET, 'r')) {
} else $isNew = true;
$currentUser = $msz->getActiveUser();
$canEditPerms = perms_check_user(MSZ_PERMS_USER, $currentUser->getId(), MSZ_PERM_USER_MANAGE_PERMS);
$canEditPerms = $viewerPerms->check(Perm::U_PERMS_MANAGE);
if($canEditPerms)
$permissions = manage_perms_list(perms_get_role_raw($roleId ?? 0));
$permsInfos = $perms->getPermissionInfo(roleInfo: $roleInfo, categoryNames: Perm::INFO_FOR_ROLE);
$permsLists = Perm::createList(Perm::LISTS_FOR_ROLE);
while($_SERVER['REQUEST_METHOD'] === 'POST' && CSRF::validateRequest()) {
$userRank = $users->getUserRank($currentUser);
@ -120,27 +123,16 @@ while($_SERVER['REQUEST_METHOD'] === 'POST' && CSRF::validateRequest()) {
[$roleInfo->getId()]
);
if(!empty($permissions) && !empty($_POST['perms']) && is_array($_POST['perms'])) {
$perms = manage_perms_apply($permissions, $_POST['perms']);
if($canEditPerms && filter_has_var(INPUT_POST, 'perms')) {
$permsApply = Perm::convertSubmission(
filter_input(INPUT_POST, 'perms', FILTER_DEFAULT, FILTER_REQUIRE_ARRAY),
Perm::INFO_FOR_ROLE
);
if($perms !== null) {
$permKeys = array_keys($perms);
$setPermissions = DB::prepare('
REPLACE INTO `msz_permissions` (`role_id`, `user_id`, `' . implode('`, `', $permKeys) . '`)
VALUES (:role_id, NULL, :' . implode(', :', $permKeys) . ')
');
$setPermissions->bind('role_id', $roleInfo->getId());
foreach($permsApply as $categoryName => $values)
$perms->setPermissions($categoryName, $values['allow'], $values['deny'], roleInfo: $roleInfo);
foreach($perms as $key => $value) {
$setPermissions->bind($key, $value);
}
$setPermissions->execute();
} else {
$deletePermissions = DB::prepare('DELETE FROM `msz_permissions` WHERE `role_id` = :role_id AND `user_id` IS NULL');
$deletePermissions->bind('role_id', $roleInfo->getId());
$deletePermissions->execute();
}
$msz->getConfig()->setBoolean('perms.needsRecalc', true);
}
url_redirect('manage-role', ['role' => $roleInfo->getId()]);
@ -150,6 +142,7 @@ while($_SERVER['REQUEST_METHOD'] === 'POST' && CSRF::validateRequest()) {
Template::render('manage.users.role', [
'role_new' => $isNew,
'role_info' => $roleInfo ?? null,
'can_manage_perms' => $canEditPerms,
'permissions' => $permissions ?? [],
'can_edit_perms' => $canEditPerms,
'perms_lists' => $permsLists,
'perms_infos' => $permsInfos,
]);

View File

@ -1,7 +1,7 @@
<?php
namespace Misuzu;
if(!$msz->isLoggedIn() || !perms_check_user(MSZ_PERMS_USER, $msz->getActiveUser()->getId(), MSZ_PERM_USER_MANAGE_ROLES)) {
if(!$msz->getAuthInfo()->getPerms('user')->check(Perm::U_ROLES_MANAGE)) {
echo render_error(403);
return;
}

View File

@ -3,9 +3,11 @@ namespace Misuzu;
use RuntimeException;
use Index\Colour\Colour;
use Misuzu\Perm;
use Misuzu\Auth\AuthTokenCookie;
use Misuzu\Users\User;
$viewerPerms = $msz->getAuthInfo()->getPerms('user');
if(!$msz->isLoggedIn()) {
echo render_error(403);
return;
@ -13,15 +15,16 @@ if(!$msz->isLoggedIn()) {
$users = $msz->getUsers();
$roles = $msz->getRoles();
$perms = $msz->getPerms();
$currentUser = $msz->getActiveUser();
$canManageUsers = perms_check_user(MSZ_PERMS_USER, $currentUser->getId(), MSZ_PERM_USER_MANAGE_USERS);
$canManagePerms = perms_check_user(MSZ_PERMS_USER, $currentUser->getId(), MSZ_PERM_USER_MANAGE_PERMS);
$canManageNotes = perms_check_user(MSZ_PERMS_USER, $currentUser->getId(), MSZ_PERM_USER_MANAGE_NOTES);
$canManageWarnings = perms_check_user(MSZ_PERMS_USER, $currentUser->getId(), MSZ_PERM_USER_MANAGE_WARNINGS);
$canManageBans = perms_check_user(MSZ_PERMS_USER, $currentUser->getId(), MSZ_PERM_USER_MANAGE_BANS);
$canImpersonate = perms_check_user(MSZ_PERMS_USER, $currentUser->getId(), MSZ_PERM_USER_IMPERSONATE);
$canManageUsers = $viewerPerms->check(Perm::U_USERS_MANAGE);
$canManagePerms = $viewerPerms->check(Perm::U_PERMS_MANAGE);
$canManageNotes = $viewerPerms->check(Perm::U_NOTES_MANAGE);
$canManageWarnings = $viewerPerms->check(Perm::U_WARNINGS_MANAGE);
$canManageBans = $viewerPerms->check(Perm::U_BANS_MANAGE);
$canImpersonate = $viewerPerms->check(Perm::U_CAN_IMPERSONATE);
$canSendTestMail = $currentUser->isSuperUser();
$hasAccess = $canManageUsers || $canManageNotes || $canManageWarnings || $canManageBans;
@ -45,7 +48,9 @@ $userRank = $users->getUserRank($userInfo);
$canEdit = $canManageUsers && ($currentUser->isSuperUser() || (string)$currentUser->getId() === $userInfo->getId() || $currentUserRank > $userRank);
$canEditPerms = $canEdit && $canManagePerms;
$permissions = $canEditPerms ? manage_perms_list(perms_get_user_raw($userId)) : [];
$permsInfos = $perms->getPermissionInfo(userInfo: $userInfo, categoryNames: Perm::INFO_FOR_USER);
$permsLists = Perm::createList(Perm::LISTS_FOR_USER);
if(CSRF::validateRequest() && $canEdit) {
if(!empty($_POST['impersonate_user'])) {
@ -136,11 +141,14 @@ if(CSRF::validateRequest() && $canEdit) {
if(!empty($addRoles))
$users->addRoles($userInfo, $addRoles);
if(!empty($addRoles) || !empty($removeRoles))
$msz->getConfig()->setBoolean('perms.needsRecalc', true);
}
if(!empty($_POST['user']) && is_array($_POST['user'])) {
$setCountry = (string)($_POST['user']['country'] ?? '');
$setTitle = (string)($_POST['user']['title'] ?? '');
$setCountry = (string)($_POST['user']['country'] ?? '');
$setTitle = (string)($_POST['user']['title'] ?? '');
$displayRole = (string)($_POST['user']['display_role'] ?? 0);
if(!$users->hasRole($userInfo, $displayRole))
@ -193,19 +201,16 @@ if(CSRF::validateRequest() && $canEdit) {
}
}
if($canEditPerms && !empty($_POST['perms']) && is_array($_POST['perms'])) {
$perms = manage_perms_apply($permissions, $_POST['perms']);
if($canEditPerms && filter_has_var(INPUT_POST, 'perms')) {
$permsApply = Perm::convertSubmission(
filter_input(INPUT_POST, 'perms', FILTER_DEFAULT, FILTER_REQUIRE_ARRAY),
Perm::INFO_FOR_USER
);
if($perms !== null) {
if(!perms_set_user_raw($userId, $perms))
$notices[] = 'Failed to update permissions.';
} else {
if(!perms_delete_user($userId))
$notices[] = 'Failed to remove permissions.';
}
foreach($permsApply as $categoryName => $values)
$perms->setPermissions($categoryName, $values['allow'], $values['deny'], userInfo: $userInfo);
// this smells, make it refresh/apply in a non-retarded way
$permissions = manage_perms_list(perms_get_user_raw($userId));
$msz->getConfig()->setBoolean('perms.needsRecalc', true);
}
url_redirect('manage-user', ['user' => $userInfo->getId()]);
@ -227,5 +232,6 @@ Template::render('manage.users.user', [
'can_manage_bans' => $canManageBans,
'can_impersonate' => $canImpersonate,
'can_send_test_mail' => $canSendTestMail,
'permissions' => $permissions ?? [],
'perms_lists' => $permsLists,
'perms_infos' => $permsInfos,
]);

View File

@ -3,7 +3,7 @@ namespace Misuzu;
use RuntimeException;
if(!$msz->isLoggedIn() || !perms_check_user(MSZ_PERMS_USER, $msz->getActiveUser()->getId(), MSZ_PERM_USER_MANAGE_WARNINGS)) {
if(!$msz->getAuthInfo()->getPerms('user')->check(Perm::U_WARNINGS_MANAGE)) {
echo render_error(403);
return;
}

View File

@ -3,7 +3,7 @@ namespace Misuzu;
use RuntimeException;
if(!$msz->isLoggedIn() || !perms_check_user(MSZ_PERMS_USER, $msz->getActiveUser()->getId(), MSZ_PERM_USER_MANAGE_WARNINGS)) {
if(!$msz->getAuthInfo()->getPerms('user')->check(Perm::U_WARNINGS_MANAGE)) {
echo render_error(403);
return;
}

View File

@ -67,8 +67,6 @@ if(empty($orderDir)) {
return;
}
$canManageUsers = perms_check_user(MSZ_PERMS_USER, $msz->getActiveUser()->getId(), MSZ_PERM_USER_MANAGE_USERS);
if($roleId === null) {
$roleInfo = $roles->getDefaultRole();
} else {
@ -80,6 +78,7 @@ if($roleId === null) {
}
}
$canManageUsers = $msz->getAuthInfo()->getPerms('user')->check(Perm::U_USERS_MANAGE);
$deleted = $canManageUsers ? null : false;
$rolesAll = $roles->getRoles(hidden: false);

View File

@ -65,15 +65,15 @@ $notices = [];
$userRank = $users->getUserRank($userInfo);
$viewerRank = $viewingAsGuest ? 0 : $users->getUserRank($viewerInfo);
$viewerPerms = $msz->getAuthInfo()->getPerms('user');
$activeBanInfo = $msz->tryGetActiveBan($userInfo);
$isBanned = $activeBanInfo !== null;
$profileFields = $msz->getProfileFields();
$viewingOwnProfile = (string)$viewerId === $userInfo->getId();
$userPerms = perms_get_user($viewerId)[MSZ_PERMS_USER];
$canManageWarnings = perms_check($userPerms, MSZ_PERM_USER_MANAGE_WARNINGS);
$canManageWarnings = $viewerPerms->check(Perm::U_WARNINGS_MANAGE);
$canEdit = !$viewingAsGuest && ((!$isBanned && $viewingOwnProfile) || $viewerInfo->isSuperUser() || (
perms_check($userPerms, MSZ_PERM_USER_MANAGE_USERS)
&& ($viewingOwnProfile || $viewerRank > $userRank)
$viewerPerms->check(Perm::U_USERS_MANAGE) && ($viewingOwnProfile || $viewerRank > $userRank)
));
$avatarInfo = new UserAvatarAsset($userInfo);
$backgroundInfo = new UserBackgroundAsset($userInfo);
@ -84,13 +84,13 @@ if($isEditing) {
return;
}
$perms = perms_check_bulk($userPerms, [
'edit_profile' => MSZ_PERM_USER_EDIT_PROFILE,
'edit_avatar' => MSZ_PERM_USER_CHANGE_AVATAR,
'edit_background' => MSZ_PERM_USER_CHANGE_BACKGROUND,
'edit_about' => MSZ_PERM_USER_EDIT_ABOUT,
'edit_birthdate' => MSZ_PERM_USER_EDIT_BIRTHDATE,
'edit_signature' => MSZ_PERM_USER_EDIT_SIGNATURE,
$perms = $viewerPerms->checkMany([
'edit_profile' => Perm::U_PROFILE_EDIT,
'edit_avatar' => Perm::U_AVATAR_CHANGE,
'edit_background' => PERM::U_PROFILE_BACKGROUND_CHANGE,
'edit_about' => Perm::U_PROFILE_ABOUT_EDIT,
'edit_birthdate' => Perm::U_PROFILE_BIRTHDATE_EDIT,
'edit_signature' => Perm::U_FORUM_SIGNATURE_EDIT,
]);
Template::set([
@ -105,7 +105,7 @@ if($isEditing) {
$profileFieldsSubmit = filter_input(INPUT_POST, 'profile', FILTER_DEFAULT, FILTER_REQUIRE_ARRAY);
if(!empty($profileFieldsSubmit)) {
if(!$perms['edit_profile']) {
if(!$perms->edit_profile) {
$notices[] = 'You\'re not allowed to edit your profile';
} else {
$profileFieldInfos = $profileFields->getFields();
@ -139,7 +139,7 @@ if($isEditing) {
}
if(!empty($_POST['about']) && is_array($_POST['about'])) {
if(!$perms['edit_about']) {
if(!$perms->edit_about) {
$notices[] = 'You\'re not allowed to edit your about page.';
} else {
$aboutText = (string)($_POST['about']['text'] ?? '');
@ -163,7 +163,7 @@ if($isEditing) {
}
if(!empty($_POST['signature']) && is_array($_POST['signature'])) {
if(!$perms['edit_signature']) {
if(!$perms->edit_signature) {
$notices[] = 'You\'re not allowed to edit your forum signature.';
} else {
$sigText = (string)($_POST['signature']['text'] ?? '');
@ -187,7 +187,7 @@ if($isEditing) {
}
if(!empty($_POST['birthdate']) && is_array($_POST['birthdate'])) {
if(!$perms['edit_birthdate']) {
if(!$perms->edit_birthdate) {
$notices[] = "You aren't allow to change your birthdate.";
} else {
$birthYear = (int)($_POST['birthdate']['year'] ?? 0);
@ -215,7 +215,7 @@ if($isEditing) {
if(!empty($_POST['avatar']['delete'])) {
$avatarInfo->delete();
} else {
if(!$perms['edit_avatar']) {
if(!$perms->edit_avatar) {
$notices[] = 'You aren\'t allow to change your avatar.';
} elseif(!empty($_FILES['avatar'])
&& is_array($_FILES['avatar'])
@ -260,7 +260,7 @@ if($isEditing) {
if((int)($_POST['background']['attach'] ?? -1) === 0) {
$backgroundInfo->delete();
} else {
if(!$perms['edit_background']) {
if(!$perms->edit_background) {
$notices[] = 'You aren\'t allow to change your background.';
} elseif(!empty($_FILES['background']) && is_array($_FILES['background'])) {
if(!empty($_FILES['background']['name']['file'])) {

View File

@ -83,7 +83,7 @@ if(!empty($searchQuery)) {
$forumCategoryIds = XArray::where(
$forum->getCategories(hidden: false),
fn($categoryInfo) => $categoryInfo->mayHaveTopics() && forum_perms_check_user(MSZ_FORUM_PERMS_GENERAL, $categoryInfo->getId(), $currentUserId, MSZ_FORUM_PERM_VIEW_FORUM)
fn($categoryInfo) => $categoryInfo->mayHaveTopics() && $msz->getAuthInfo()->getPerms('forum', $categoryInfo)->check(Perm::F_CATEGORY_VIEW)
);
$forumTopicInfos = $forum->getTopics(categoryInfo: $forumCategoryIds, deleted: false, searchQuery: $searchQueryEvaluated);

View File

@ -35,9 +35,10 @@ if(!$isRestricted && $isVerifiedRequest && !empty($_POST['role'])) {
break;
case 'leave':
if($roleInfo->isLeavable())
if($roleInfo->isLeavable()) {
$users->removeRoles($userInfo, $roleInfo);
else
$msz->getConfig()->setBoolean('perms.needsRecalc', true);
} else
$errors[] = "You're not allow to leave this role, an administrator has to remove it for you.";
break;
}

View File

@ -125,14 +125,14 @@ if(isset($_POST['action']) && is_string($_POST['action'])) {
$tmpFiles[] = db_to_zip($archive, $userInfo, 'comments_categories', ['category_id:s', 'category_name:s', 'owner_id:s:n', 'category_created:t', 'category_locked:t:n'], 'owner_id');
$tmpFiles[] = db_to_zip($archive, $userInfo, 'comments_posts', ['comment_id:s', 'category_id:s', 'user_id:s:n', 'comment_reply_to:s:n', 'comment_text:s', 'comment_created:t', 'comment_pinned:t:n', 'comment_edited:t:n', 'comment_deleted:t:n']);
$tmpFiles[] = db_to_zip($archive, $userInfo, 'comments_votes', ['comment_id:s', 'user_id:s', 'comment_vote:i']);
$tmpFiles[] = db_to_zip($archive, $userInfo, 'forum_permissions', ['user_id:s:n', 'role_id:s:n', 'forum_id:s', 'forum_perms_allow:i', 'forum_perms_deny:i']);
$tmpFiles[] = db_to_zip($archive, $userInfo, 'forum_posts', ['post_id:s', 'topic_id:s', 'forum_id:s', 'user_id:s:n', 'post_ip:a', 'post_text:s', 'post_parse:i', 'post_display_signature:b', 'post_created:t', 'post_edited:t:n', 'post_deleted:t:n']);
$tmpFiles[] = db_to_zip($archive, $userInfo, 'forum_topics', ['topic_id:s', 'forum_id:s', 'user_id:s:n', 'topic_type:i', 'topic_title:s', 'topic_count_views:i', 'topic_created:t', 'topic_bumped:t', 'topic_deleted:t:n', 'topic_locked:t:n']);
$tmpFiles[] = db_to_zip($archive, $userInfo, 'forum_topics_redirects', ['topic_id:s', 'user_id:s:n', 'topic_redir_url:s', 'topic_redir_created:t']);
$tmpFiles[] = db_to_zip($archive, $userInfo, 'forum_topics_track', ['user_id:s', 'topic_id:s', 'forum_id:s', 'track_last_read:t']);
$tmpFiles[] = db_to_zip($archive, $userInfo, 'login_attempts', ['user_id:s:n', 'attempt_success:b', 'attempt_ip:a', 'attempt_country:s', 'attempt_created:t', 'attempt_user_agent:s']);
$tmpFiles[] = db_to_zip($archive, $userInfo, 'news_posts', ['post_id:s', 'category_id:s', 'user_id:s:n', 'comment_section_id:s:n', 'post_is_featured:b', 'post_title:s', 'post_text:s', 'post_scheduled:t', 'post_created:t', 'post_updated:t', 'post_deleted:t:n']);
$tmpFiles[] = db_to_zip($archive, $userInfo, 'permissions', ['user_id:s:n', 'role_id:s:n', 'general_perms_allow:i', 'general_perms_deny:i', 'user_perms_allow:i', 'user_perms_deny:i', 'changelog_perms_allow:i', 'changelog_perms_deny:i', 'news_perms_allow:i', 'news_perms_deny:i', 'forum_perms_allow:i', 'forum_perms_deny:i', 'comments_perms_allow:i', 'comments_perms_deny:i']);
$tmpFiles[] = db_to_zip($archive, $userInfo, 'perms', ['user_id:s:n', 'role_id:s:n', 'forum_id:s:n', 'perms_category:s', 'perms_allow:i', 'perms_deny:i']);
$tmpFiles[] = db_to_zip($archive, $userInfo, 'perms_calculated', ['user_id:s:n', 'forum_id:s:n', 'perms_category:s', 'perms_calculated:i']);
$tmpFiles[] = db_to_zip($archive, $userInfo, 'profile_fields_values', ['field_id:s', 'user_id:s', 'format_id:s', 'field_value:s']);
$tmpFiles[] = db_to_zip($archive, $userInfo, 'sessions', ['session_id:s', 'user_id:s', 'session_key:n', 'session_ip:a', 'session_ip_last:a:n', 'session_user_agent:s', 'session_country:s', 'session_expires:t', 'session_expires_bump:b', 'session_created:t', 'session_active:t:n']);
$tmpFiles[] = db_to_zip($archive, $userInfo, 'users', ['user_id:s', 'username:s', 'password:n', 'email:s', 'register_ip:a', 'last_ip:a', 'user_super:b', 'user_country:s', 'user_colour:i:n', 'user_created:t', 'user_active:t:n', 'user_deleted:t:n', 'display_role:s:n', 'user_totp_key:n', 'user_about_content:s:n', 'user_about_parser:i', 'user_signature_content:s:n', 'user_signature_parser:i', 'user_birthdate:s:n', 'user_background_settings:i:n', 'user_title:s:n']);

View File

@ -188,8 +188,9 @@ if($inManageMode) {
if($msz->isLoggedIn() && !$msz->hasActiveBan()) {
$manageUser = $msz->getActiveUser();
$manageUserId = $manageUser->getId();
$manageGlobalPerms = $msz->getAuthInfo()->getPerms('global');
if(perms_check_user(MSZ_PERMS_GENERAL, $manageUserId, MSZ_PERM_GENERAL_CAN_MANAGE)) {
if($manageGlobalPerms->check(Perm::G_IS_JANITOR)) {
$hasManageAccess = true;
$manageMenu = [
'General' => [
@ -197,37 +198,38 @@ if($inManageMode) {
],
];
if(perms_check_user(MSZ_PERMS_GENERAL, $manageUserId, MSZ_PERM_GENERAL_VIEW_LOGS))
if($manageGlobalPerms->check(Perm::G_LOGS_VIEW))
$manageMenu['General']['Logs'] = url('manage-general-logs');
if(perms_check_user(MSZ_PERMS_GENERAL, $manageUserId, MSZ_PERM_GENERAL_MANAGE_EMOTES))
if($manageGlobalPerms->check(Perm::G_EMOTES_MANAGE))
$manageMenu['General']['Emoticons'] = url('manage-general-emoticons');
if(perms_check_user(MSZ_PERMS_GENERAL, $manageUserId, MSZ_PERM_GENERAL_MANAGE_CONFIG))
if($manageGlobalPerms->check(Perm::G_CONFIG_MANAGE))
$manageMenu['General']['Settings'] = url('manage-general-settings');
if(perms_check_user(MSZ_PERMS_USER, $manageUserId, MSZ_PERM_USER_MANAGE_USERS))
$manageUserPerms = $msz->getAuthInfo()->getPerms('user');
if($manageUserPerms->check(Perm::U_USERS_MANAGE))
$manageMenu['Users & Roles']['Users'] = url('manage-users');
if(perms_check_user(MSZ_PERMS_USER, $manageUserId, MSZ_PERM_USER_MANAGE_ROLES))
if($manageUserPerms->check(Perm::U_ROLES_MANAGE))
$manageMenu['Users & Roles']['Roles'] = url('manage-roles');
if(perms_check_user(MSZ_PERMS_USER, $manageUserId, MSZ_PERM_USER_MANAGE_NOTES))
if($manageUserPerms->check(Perm::U_NOTES_MANAGE))
$manageMenu['Users & Roles']['Notes'] = url('manage-users-notes');
if(perms_check_user(MSZ_PERMS_USER, $manageUserId, MSZ_PERM_USER_MANAGE_WARNINGS))
if($manageUserPerms->check(Perm::U_WARNINGS_MANAGE))
$manageMenu['Users & Roles']['Warnings'] = url('manage-users-warnings');
if(perms_check_user(MSZ_PERMS_USER, $manageUserId, MSZ_PERM_USER_MANAGE_BANS))
if($manageUserPerms->check(Perm::U_BANS_MANAGE))
$manageMenu['Users & Roles']['Bans'] = url('manage-users-bans');
if(perms_check_user(MSZ_PERMS_NEWS, $manageUserId, MSZ_PERM_NEWS_MANAGE_POSTS))
if($manageGlobalPerms->check(Perm::G_NEWS_POSTS_MANAGE))
$manageMenu['News']['Posts'] = url('manage-news-posts');
if(perms_check_user(MSZ_PERMS_NEWS, $manageUserId, MSZ_PERM_NEWS_MANAGE_CATEGORIES))
if($manageGlobalPerms->check(Perm::G_NEWS_CATEGORIES_MANAGE))
$manageMenu['News']['Categories'] = url('manage-news-categories');
if(perms_check_user(MSZ_PERMS_FORUM, $manageUserId, MSZ_PERM_FORUM_MANAGE_FORUMS))
if($manageGlobalPerms->check(Perm::G_FORUM_CATEGORIES_MANAGE))
$manageMenu['Forum']['Permission Calculator'] = url('manage-forum-categories');
if(perms_check_user(MSZ_PERMS_FORUM, $manageUserId, MSZ_PERM_FORUM_TOPIC_REDIRS))
if($manageGlobalPerms->check(Perm::G_FORUM_TOPIC_REDIRS_MANAGE))
$manageMenu['Forum']['Topic Redirects'] = url('manage-forum-topic-redirs');
if(perms_check_user(MSZ_PERMS_CHANGELOG, $manageUserId, MSZ_PERM_CHANGELOG_MANAGE_CHANGES))
if($manageGlobalPerms->check(Perm::G_CL_CHANGES_MANAGE))
$manageMenu['Changelog']['Changes'] = url('manage-changelog-changes');
if(perms_check_user(MSZ_PERMS_CHANGELOG, $manageUserId, MSZ_PERM_CHANGELOG_MANAGE_TAGS))
if($manageGlobalPerms->check(Perm::G_CL_TAGS_MANAGE))
$manageMenu['Changelog']['Tags'] = url('manage-changelog-tags');
Template::set('manage_menu', $manageMenu);

View File

@ -1,27 +1,24 @@
<?php
namespace Misuzu\Auth;
use Index\XArray;
use Misuzu\Auth\SessionInfo;
use Misuzu\Forum\ForumCategoryInfo;
use Misuzu\Perms\IPermissionResult;
use Misuzu\Perms\Permissions;
use Misuzu\Users\UserInfo;
class AuthInfo {
private Permissions $permissions;
private AuthTokenInfo $tokenInfo;
private ?UserInfo $userInfo;
private ?SessionInfo $sessionInfo;
private ?UserInfo $realUserInfo;
private array $perms;
public function __construct(
?AuthTokenInfo $tokenInfo = null,
?UserInfo $userInfo = null,
?SessionInfo $sessionInfo = null,
?UserInfo $realUserInfo = null
) {
$this->setInfo(
$tokenInfo ?? AuthTokenInfo::empty(),
$userInfo,
$sessionInfo,
$realUserInfo
);
public function __construct(Permissions $permissions) {
$this->permissions = $permissions;
$this->setInfo(AuthTokenInfo::empty());
}
public function setInfo(
@ -34,6 +31,7 @@ class AuthInfo {
$this->userInfo = $userInfo;
$this->sessionInfo = $sessionInfo;
$this->realUserInfo = $realUserInfo;
$this->perms = [];
}
public function removeInfo(): void {
@ -76,15 +74,17 @@ class AuthInfo {
return $this->realUserInfo;
}
private static AuthInfo $empty;
public function getPerms(
string $category,
ForumCategoryInfo|string|null $forumCategoryInfo = null
): IPermissionResult {
$cacheKey = $category;
if($forumCategoryInfo !== null)
$cacheKey .= '|' . ($forumCategoryInfo instanceof ForumCategoryInfo ? $forumCategoryInfo->getId() : $forumCategoryInfo);
public static function init(): void {
self::$empty = new AuthInfo(AuthTokenInfo::empty());
}
if(array_key_exists($cacheKey, $this->perms))
return $this->perms[$cacheKey];
public static function empty(): self {
return self::$empty;
return $this->perms[$cacheKey] = $this->permissions->getPermissions($category, $this->userInfo, $forumCategoryInfo);
}
}
AuthInfo::init();

View File

@ -4,6 +4,7 @@ namespace Misuzu\Comments;
use stdClass;
use RuntimeException;
use Misuzu\MisuzuContext;
use Misuzu\Perm;
use Misuzu\Auth\AuthInfo;
use Misuzu\Users\Users;
@ -24,7 +25,14 @@ class CommentsEx {
$hasUser = $this->authInfo->isLoggedIn();
$info->user = $hasUser ? $this->authInfo->getUserInfo() : null;
$info->colour = $hasUser ? $this->users->getUserColour($info->user) : null;
$info->perms = $hasUser ? perms_for_comments($info->user->getId()) : [];
$info->perms = $this->authInfo->getPerms('global')->checkMany([
'can_post' => Perm::G_COMMENTS_CREATE,
'can_delete' => Perm::G_COMMENTS_DELETE_OWN | Perm::G_COMMENTS_DELETE_ANY,
'can_delete_any' => Perm::G_COMMENTS_DELETE_ANY,
'can_pin' => Perm::G_COMMENTS_PIN,
'can_lock' => Perm::G_COMMENTS_LOCK,
'can_vote' => Perm::G_COMMENTS_VOTE,
]);
$info->category = $category;
$info->posts = [];

View File

@ -27,6 +27,7 @@ use Misuzu\Home\HomeRoutes;
use Misuzu\Info\InfoRoutes;
use Misuzu\News\News;
use Misuzu\News\NewsRoutes;
use Misuzu\Perms\Permissions;
use Misuzu\Profile\ProfileFields;
use Misuzu\Satori\SatoriRoutes;
use Misuzu\SharpChat\SharpChatRoutes;
@ -66,13 +67,15 @@ class MisuzuContext {
private Counters $counters;
private ProfileFields $profileFields;
private Forum $forum;
private Permissions $perms;
private AuthInfo $authInfo;
public function __construct(IDbConnection $dbConn, IConfig $config) {
$this->dbConn = $dbConn;
$this->config = $config;
$this->perms = new Permissions($this->dbConn);
$this->authInfo = new AuthInfo($this->perms);
$this->auditLog = new AuditLog($this->dbConn);
$this->authInfo = new AuthInfo;
$this->bans = new Bans($this->dbConn);
$this->changelog = new Changelog($this->dbConn);
$this->comments = new Comments($this->dbConn);
@ -184,6 +187,10 @@ class MisuzuContext {
return $this->forum;
}
public function getPerms(): Permissions {
return $this->perms;
}
public function createAuthTokenPacker(): AuthTokenPacker {
return new AuthTokenPacker($this->config->getString('auth.secret', 'meow'));
}
@ -276,7 +283,7 @@ class MisuzuContext {
'menu' => [],
];
if($hasUserInfo && perms_check_user(MSZ_PERMS_GENERAL, $userInfo->getId(), MSZ_PERM_FORUM_VIEW_LEADERBOARD))
if($this->authInfo->getPerms('global')->check(Perm::G_FORUM_LEADERBOARD_VIEW))
$forum['menu'][] = [
'title' => 'Leaderboard',
'url' => url('forum-leaderboard'),
@ -325,7 +332,7 @@ class MisuzuContext {
'icon' => 'fas fa-search fa-fw',
];
if(!$this->hasActiveBan($userInfo) && perms_check_user(MSZ_PERMS_GENERAL, $userInfo->getId(), MSZ_PERM_GENERAL_CAN_MANAGE)) {
if(!$this->hasActiveBan($userInfo) && $this->authInfo->getPerms('global')->check(Perm::G_IS_JANITOR)) {
// restore behaviour where clicking this button switches between
// site version and broom version
if($inBroomCloset)
@ -408,7 +415,7 @@ class MisuzuContext {
new SharpChatRoutes(
$this->router, $this->config->scopeTo('sockChat'),
$this->bans, $this->emotes, $this->users,
$this->sessions, $this->authInfo,
$this->sessions, $this->perms, $this->authInfo,
$this->createAuthTokenPacker(...)
);

355
src/Perm.php Normal file
View File

@ -0,0 +1,355 @@
<?php
namespace Misuzu;
use stdClass;
// All permissions should be defined in this class
// To avoid future conflicts, unused/deprecated permissions should remain defined for any given category
final class Perm {
// GLOBAL ALLOCATION:
// 0bXXXXX_XXXXXXXX_CCCCCCCC_FFFFFFFF_NNNNNNNN_LLLLLLLL_GGGGGGGG
// G -> General global permissions
// L -> Changelog permissions
// N -> News permissions
// F -> Global Forum perms
// C -> Global Comments perms
// X -> unallocated
public const G_IS_JANITOR = 0b00000_00000000_00000000_00000000_00000000_00000000_00000001;
public const G_LOGS_VIEW = 0b00000_00000000_00000000_00000000_00000000_00000000_00000010;
public const G_EMOTES_MANAGE = 0b00000_00000000_00000000_00000000_00000000_00000000_00000100;
public const G_CONFIG_MANAGE = 0b00000_00000000_00000000_00000000_00000000_00000000_00001000;
//public const G_IS_TESTER = 0b00000_00000000_00000000_00000000_00000000_00000000_00010000; // deprecated: tester status went unused
public const G_BLACKLIST_MANAGE = 0b00000_00000000_00000000_00000000_00000000_00000000_00100000; // unused: blacklist is currently removed to reduce overhead and it seemed like it was broken
//public const G_TWITTER_MANAGE = 0b00000_00000000_00000000_00000000_00000000_00000000_01000000; // deprecated: twitter integration has been removed
public const G_CL_CHANGES_MANAGE = 0b00000_00000000_00000000_00000000_00000000_00000001_00000000;
public const G_CL_TAGS_MANAGE = 0b00000_00000000_00000000_00000000_00000000_00000010_00000000;
//public const G_CL_ACTIONS_MANAGE = 0b00000_00000000_00000000_00000000_00000000_00000100_00000000; // deprecated: actions are hardcoded now
public const G_NEWS_POSTS_MANAGE = 0b00000_00000000_00000000_00000000_00000001_00000000_00000000;
public const G_NEWS_CATEGORIES_MANAGE = 0b00000_00000000_00000000_00000000_00000010_00000000_00000000;
public const G_FORUM_CATEGORIES_MANAGE = 0b00000_00000000_00000000_00000001_00000000_00000000_00000000;
public const G_FORUM_LEADERBOARD_VIEW = 0b00000_00000000_00000000_00000010_00000000_00000000_00000000;
public const G_FORUM_TOPIC_REDIRS_MANAGE = 0b00000_00000000_00000000_00000100_00000000_00000000_00000000;
public const G_COMMENTS_CREATE = 0b00000_00000000_00000001_00000000_00000000_00000000_00000000;
public const G_COMMENTS_EDIT_OWN = 0b00000_00000000_00000010_00000000_00000000_00000000_00000000; // unused: editing not implemented
public const G_COMMENTS_EDIT_ANY = 0b00000_00000000_00000100_00000000_00000000_00000000_00000000; // unused: editing not implemented
public const G_COMMENTS_DELETE_OWN = 0b00000_00000000_00001000_00000000_00000000_00000000_00000000;
public const G_COMMENTS_DELETE_ANY = 0b00000_00000000_00010000_00000000_00000000_00000000_00000000;
public const G_COMMENTS_PIN = 0b00000_00000000_00100000_00000000_00000000_00000000_00000000;
public const G_COMMENTS_LOCK = 0b00000_00000000_01000000_00000000_00000000_00000000_00000000;
public const G_COMMENTS_VOTE = 0b00000_00000000_10000000_00000000_00000000_00000000_00000000;
// USER ALLOCATION:
// There's no rules here, manage perms started abouts halfway through the 31-bit integer
// Maybe formally define the octets regardless later?
public const U_PROFILE_EDIT = 0b00000_00000000_00000000_00000000_00000000_00000000_00000001;
public const U_AVATAR_CHANGE = 0b00000_00000000_00000000_00000000_00000000_00000000_00000010;
public const U_PROFILE_BACKGROUND_CHANGE = 0b00000_00000000_00000000_00000000_00000000_00000000_00000100;
public const U_PROFILE_ABOUT_EDIT = 0b00000_00000000_00000000_00000000_00000000_00000000_00001000;
public const U_PROFILE_BIRTHDATE_EDIT = 0b00000_00000000_00000000_00000000_00000000_00000000_00010000;
public const U_FORUM_SIGNATURE_EDIT = 0b00000_00000000_00000000_00000000_00000000_00000000_00100000;
public const U_USERS_MANAGE = 0b00000_00000000_00000000_00000000_00010000_00000000_00000000;
public const U_ROLES_MANAGE = 0b00000_00000000_00000000_00000000_00100000_00000000_00000000;
public const U_PERMS_MANAGE = 0b00000_00000000_00000000_00000000_01000000_00000000_00000000;
public const U_REPORTS_MANAGE = 0b00000_00000000_00000000_00000000_10000000_00000000_00000000; // unused: reports are not implemented
public const U_WARNINGS_MANAGE = 0b00000_00000000_00000000_00000001_00000000_00000000_00000000;
//public const U_BLACKLISTS_MANAGE = 0b00000_00000000_00000000_00000010_00000000_00000000_00000000; // deprecated: replaced with GLOBAL_BLACKLIST_MANAGE
public const U_NOTES_MANAGE = 0b00000_00000000_00000000_00000100_00000000_00000000_00000000;
public const U_BANS_MANAGE = 0b00000_00000000_00000000_00001000_00000000_00000000_00000000;
public const U_CAN_IMPERSONATE = 0b00000_00000000_00000000_00010000_00000000_00000000_00000000;
// FORUM ALLOCATION:
// 0bXXXXX_XXXXXXXX_XXXXXXXX_PPPPPPPP_PPPPTTTT_TTTTTTTT_CCCCCCCC
// C -> Category related
// T -> Topic related
// N -> Post related
// X -> unallocated
public const F_CATEGORY_LIST = 0b00000_00000000_00000000_00000000_00000000_00000000_00000001;
public const F_CATEGORY_VIEW = 0b00000_00000000_00000000_00000000_00000000_00000000_00000010;
public const F_TOPIC_CREATE = 0b00000_00000000_00000000_00000000_00000000_00000100_00000000;
//public const F_TOPIC_DELETE = 0b00000_00000000_00000000_00000000_00000000_00001000_00000000; // deprecated: use F_POST_DELETE_ANY instead
public const F_TOPIC_MOVE = 0b00000_00000000_00000000_00000000_00000000_00010000_00000000; // unused: topic moving not implemented
public const F_TOPIC_LOCK = 0b00000_00000000_00000000_00000000_00000000_00100000_00000000;
public const F_TOPIC_STICKY = 0b00000_00000000_00000000_00000000_00000000_01000000_00000000;
public const F_TOPIC_ANNOUNCE_LOCAL = 0b00000_00000000_00000000_00000000_00000000_10000000_00000000;
public const F_TOPIC_ANNOUNCE_GLOBAL = 0b00000_00000000_00000000_00000000_00000001_00000000_00000000;
public const F_TOPIC_BUMP = 0b00000_00000000_00000000_00000000_00000010_00000000_00000000;
public const F_TOPIC_PRIORITY_VOTE = 0b00000_00000000_00000000_00000000_00000100_00000000_00000000; // unused: feature postponed, reuse if it makes sense otherwise deprecate
public const F_POST_CREATE = 0b00000_00000000_00000000_00000000_00010000_00000000_00000000;
public const F_POST_EDIT_OWN = 0b00000_00000000_00000000_00000000_00100000_00000000_00000000;
public const F_POST_EDIT_ANY = 0b00000_00000000_00000000_00000000_01000000_00000000_00000000;
public const F_POST_DELETE_OWN = 0b00000_00000000_00000000_00000000_10000000_00000000_00000000;
public const F_POST_DELETE_ANY = 0b00000_00000000_00000000_00000001_00000000_00000000_00000000;
public const INFO_FOR_USER = ['global', 'user'];
public const INFO_FOR_ROLE = self::INFO_FOR_USER; // just alias for now, no clue if this will ever desync
public const INFO_FOR_FORUM_CATEGORY = ['forum'];
public const LISTS_FOR_USER = ['global:general', 'global:changelog', 'global:news', 'global:forum', 'global:comments', 'user:personal', 'user:manage'];
public const LISTS_FOR_ROLE = self::LISTS_FOR_USER; // idem
public const LISTS_FOR_FORUM_CATEGORY = ['forum:category', 'forum:topic', 'forum:post'];
public const LISTS = [
'global:general' => [
'title' => 'Global Permissions',
'perms' => [
'global',
self::G_IS_JANITOR,
self::G_LOGS_VIEW,
self::G_EMOTES_MANAGE,
self::G_CONFIG_MANAGE,
self::G_BLACKLIST_MANAGE,
],
],
'global:changelog' => [
'title' => 'Changelog Permissions',
'perms' => [
'global',
self::G_CL_CHANGES_MANAGE,
self::G_CL_TAGS_MANAGE,
],
],
'global:news' => [
'title' => 'News Permissions',
'perms' => [
'global',
self::G_NEWS_POSTS_MANAGE,
self::G_NEWS_CATEGORIES_MANAGE,
],
],
'global:forum' => [
'title' => 'Global Forum Permissions',
'perms' => [
'global',
self::G_FORUM_CATEGORIES_MANAGE,
self::G_FORUM_LEADERBOARD_VIEW,
self::G_FORUM_TOPIC_REDIRS_MANAGE,
],
],
'global:comments' => [
'title' => 'Comments Permissions',
'perms' => [
'global',
self::G_COMMENTS_CREATE,
self::G_COMMENTS_EDIT_OWN,
self::G_COMMENTS_EDIT_ANY,
self::G_COMMENTS_DELETE_OWN,
self::G_COMMENTS_DELETE_ANY,
self::G_COMMENTS_PIN,
self::G_COMMENTS_LOCK,
self::G_COMMENTS_VOTE,
],
],
'user:personal' => [
'title' => 'User Permissions',
'perms' => [
'user',
self::U_PROFILE_EDIT,
self::U_AVATAR_CHANGE,
self::U_PROFILE_BACKGROUND_CHANGE,
self::U_PROFILE_ABOUT_EDIT,
self::U_FORUM_SIGNATURE_EDIT,
self::U_PROFILE_BIRTHDATE_EDIT,
],
],
'user:manage' => [
'title' => 'User Management Permissions',
'perms' => [
'user',
self::U_REPORTS_MANAGE,
self::U_NOTES_MANAGE,
self::U_WARNINGS_MANAGE,
self::U_BANS_MANAGE,
self::U_USERS_MANAGE,
self::U_ROLES_MANAGE,
self::U_PERMS_MANAGE,
self::U_CAN_IMPERSONATE,
],
],
'forum:category' => [
'title' => 'Forum Category Permissions',
'perms' => [
'forum',
self::F_CATEGORY_LIST,
self::F_CATEGORY_VIEW,
],
],
'forum:topic' => [
'title' => 'Forum Topic Permissions',
'perms' => [
'forum',
self::F_TOPIC_CREATE,
self::F_TOPIC_MOVE,
self::F_TOPIC_LOCK,
self::F_TOPIC_STICKY,
self::F_TOPIC_ANNOUNCE_LOCAL,
self::F_TOPIC_ANNOUNCE_GLOBAL,
self::F_TOPIC_BUMP,
self::F_TOPIC_PRIORITY_VOTE,
],
],
'forum:post' => [
'title' => 'Forum Topic Permissions',
'perms' => [
'forum',
self::F_POST_CREATE,
self::F_POST_EDIT_OWN,
self::F_POST_EDIT_ANY,
self::F_POST_DELETE_OWN,
self::F_POST_DELETE_ANY,
],
],
];
public const LABELS = [
'global' => [
self::G_IS_JANITOR => 'Can access the Broom closet.',
self::G_LOGS_VIEW => 'Can view global audit logs.',
self::G_EMOTES_MANAGE => 'Can manage emoticons.',
self::G_CONFIG_MANAGE => 'Can manage global configuration.',
//self::G_IS_TESTER => 'Can test experimental features.',
self::G_BLACKLIST_MANAGE => 'Can manage registration IP address blacklist.',
//self::G_TWITTER_MANAGE => 'Can manage Twitter integration settings.',
self::G_CL_CHANGES_MANAGE => 'Can manage changelog entries.',
self::G_CL_TAGS_MANAGE => 'Can manage changelog tags.',
//self::G_CL_ACTIONS_MANAGE => 'Can manage changelog action types.',
self::G_NEWS_POSTS_MANAGE => 'Can manage news posts.',
self::G_NEWS_CATEGORIES_MANAGE => 'Can manage news categories.',
self::G_FORUM_CATEGORIES_MANAGE => 'Can manage forum categories.',
self::G_FORUM_LEADERBOARD_VIEW => 'Can view forum leaderboard.',
self::G_FORUM_TOPIC_REDIRS_MANAGE => 'Can create redirects for deleted forum topics.',
self::G_COMMENTS_CREATE => 'Can post comments.',
self::G_COMMENTS_EDIT_OWN => 'Can edit own comments.',
self::G_COMMENTS_EDIT_ANY => 'Can edit ANY comment.',
self::G_COMMENTS_DELETE_OWN => 'Can delete own comments.',
self::G_COMMENTS_DELETE_ANY => 'Can delete ANY comment.',
self::G_COMMENTS_PIN => 'Can pin commments.',
self::G_COMMENTS_LOCK => 'Can lock comment categories.',
self::G_COMMENTS_VOTE => 'Can vote (like or dislike) on comments.',
],
'user' => [
self::U_PROFILE_EDIT => 'Can edit own profile.',
self::U_AVATAR_CHANGE => 'Can change own avatar.',
self::U_PROFILE_BACKGROUND_CHANGE => 'Can change own profile background.',
self::U_PROFILE_ABOUT_EDIT => 'Can edit own profile about section.',
self::U_PROFILE_BIRTHDATE_EDIT => 'Can edit own profile birthdate.',
self::U_FORUM_SIGNATURE_EDIT => 'Can edit own forum signature.',
self::U_USERS_MANAGE => 'Can manage other users.',
self::U_ROLES_MANAGE => 'Can manage user roles.',
self::U_PERMS_MANAGE => 'Can manage permissions.',
self::U_REPORTS_MANAGE => 'Can handle reports.',
self::U_WARNINGS_MANAGE => 'Can manage user warnings.',
//self::U_BLACKLISTS_MANAGE => 'Can manage registration IP address blacklist.',
self::U_NOTES_MANAGE => 'Can manage user notes.',
self::U_BANS_MANAGE => 'Can manage user bans.',
self::U_CAN_IMPERSONATE => 'Can impersonate select other users. Requires whitelisting in the configuration or super user status.',
],
'forum' => [
self::F_CATEGORY_LIST => 'Can see the forum category listed but cannot access it.',
self::F_CATEGORY_VIEW => 'Can access the forum category.',
self::F_TOPIC_CREATE => 'Can create forum topics.',
//self::F_TOPIC_DELETE => 'Can delete forum topics.',
self::F_TOPIC_MOVE => 'Can move forum topics to other categories.',
self::F_TOPIC_LOCK => 'Can lock forum topics.',
self::F_TOPIC_STICKY => 'Can make sticky topics.',
self::F_TOPIC_ANNOUNCE_LOCAL => 'Can make local announcement topics.',
self::F_TOPIC_ANNOUNCE_GLOBAL => 'Can make global announcement topics.',
self::F_TOPIC_BUMP => 'Can bump topics without posting a reply.',
self::F_TOPIC_PRIORITY_VOTE => 'Can use the priority voting system.',
self::F_POST_CREATE => 'Can create forum posts (replies only, if not allowed to create topics).',
self::F_POST_EDIT_OWN => 'Can edit own forum posts.',
self::F_POST_EDIT_ANY => 'Can edit ANY forum post.',
self::F_POST_DELETE_OWN => 'Can delete own forum posts.',
self::F_POST_DELETE_ANY => 'Can delete ANY forum post.',
],
];
public static function label(string $category, int $permission): string {
return array_key_exists($category, self::LABELS)
&& array_key_exists($permission, self::LABELS[$category])
? self::LABELS[$category][$permission] : '';
}
public static function createList(array $sections): array {
$list = [];
foreach($sections as $sectionName) {
if(!array_key_exists($sectionName, self::LISTS))
continue;
$currentCategoryName = '';
$sectionInfo = self::LISTS[$sectionName];
$list[] = $item = new stdClass;
$item->name = $sectionName;
$item->title = $sectionInfo['title'];
$item->perms = [];
foreach($sectionInfo['perms'] as $permInfo) {
if(is_string($permInfo)) {
$currentCategoryName = $permInfo;
continue;
}
$categoryName = $currentCategoryName;
$perm = 0;
if(is_array($permInfo))
[$categoryName, $perm] = $permInfo;
elseif(is_int($permInfo))
$perm = $permInfo;
$item->perms[] = $permItem = new stdClass;
$permItem->category = $categoryName;
$permItem->name = sprintf('perms[%s:%d]', $categoryName, $perm);
$permItem->title = self::label($categoryName, $perm);
$permItem->value = $perm;
}
}
return $list;
}
public static function convertSubmission(array $raw, array $categories): array {
$apply = [];
foreach($raw as $name => $mode) {
$nameParts = explode(':', $name, 2);
if(count($nameParts) !== 2 || !ctype_alpha($nameParts[0]) || !ctype_digit($nameParts[1]))
continue;
[$category, $value] = $nameParts;
if(!in_array($category, $categories))
continue;
if($mode === 'yes' || $mode === 'never') {
if(!array_key_exists($category, $apply))
$apply[$category] = ['allow' => 0, 'deny' => 0];
$apply[$category][$mode === 'yes' ? 'allow' : 'deny'] |= (int)$value;
}
}
return $apply;
}
}

View File

@ -0,0 +1,9 @@
<?php
namespace Misuzu\Perms;
interface IPermissionResult {
public function getCalculated(): int;
public function check(int $perm): bool;
public function checkMany(array $perms): object;
public function apply(callable $callable): IPermissionResult;
}

View File

@ -0,0 +1,83 @@
<?php
namespace Misuzu\Perms;
use Index\Data\IDbResult;
class PermissionInfo implements IPermissionResult {
use PermissionResultShared;
private ?string $userId;
private ?string $roleId;
private ?string $forumCategoryId;
private string $category;
private int $allow;
private int $deny;
private int $calculated;
public function __construct(IDbResult $result) {
$this->userId = $result->isNull(0) ? null : $result->getInteger(0);
$this->roleId = $result->isNull(1) ? null : $result->getInteger(1);
$this->forumCategoryId = $result->isNull(2) ? null : $result->getInteger(2);
$this->category = $result->getString(3);
$this->allow = $result->getInteger(4);
$this->deny = $result->getInteger(5);
$this->calculated = $this->allow & ~$this->deny;
}
public function hasUserId(): bool {
return $this->userId !== null;
}
public function getUserId(): ?string {
return $this->userId;
}
public function hasRoleId(): bool {
return $this->roleId !== null;
}
public function getRoleId(): ?string {
return $this->roleId;
}
public function hasForumCategoryId(): bool {
return $this->forumCategoryId !== null;
}
public function getForumCategoryId(): ?string {
return $this->forumCategoryId;
}
public function getCategory(): string {
return $this->category;
}
public function getAllow(): int {
return $this->allow;
}
public function getDeny(): int {
return $this->deny;
}
public function getCalculated(): int {
return $this->calculated;
}
public function check(int $perm): bool {
return ($this->calculated & $perm) > 0;
}
public function checkAllow(int $perm): bool {
return ($this->allow & $perm) > 0;
}
public function checkDeny(int $perm): bool {
return ($this->deny & $perm) > 0;
}
public function checkNeutral(int $perm): bool {
return ($this->allow & $perm) === 0
&& ($this->deny & $perm) === 0;
}
}

View File

@ -0,0 +1,16 @@
<?php
namespace Misuzu\Perms;
class PermissionResult implements IPermissionResult {
use PermissionResultShared;
public function __construct(private int $calculated) {}
public function getCalculated(): int {
return $this->calculated;
}
public function check(int $perm): bool {
return ($this->calculated & $perm) > 0;
}
}

View File

@ -0,0 +1,24 @@
<?php
namespace Misuzu\Perms;
use stdClass;
use InvalidArgumentException;
trait PermissionResultShared {
public function checkMany(array $perms): object {
if(empty($perms))
throw new InvalidArgumentException('$perms must not be empty.');
$result = new stdClass;
$calculated = $this->getCalculated();
foreach($perms as $name => $perm)
$result->{$name} = ($calculated & $perm) > 0;
return $result;
}
public function apply(callable $callable): IPermissionResult {
return new PermissionResult($callable($this->getCalculated()));
}
}

363
src/Perms/Permissions.php Normal file
View File

@ -0,0 +1,363 @@
<?php
namespace Misuzu\Perms;
use stdClass;
use InvalidArgumentException;
use RuntimeException;
use Index\DateTime;
use Index\Data\DbStatementCache;
use Index\Data\DbTools;
use Index\Data\IDbConnection;
use Index\Data\IDbStatement;
use Misuzu\Forum\Forum;
use Misuzu\Forum\ForumCategoryInfo;
use Misuzu\Users\RoleInfo;
use Misuzu\Users\UserInfo;
class Permissions {
// limiting this to 53-bit in case it ever has to be sent to javascript or any other implicit float language
// For More Information: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/MAX_SAFE_INTEGER
// it's still a ways up from the 31-bit of the old permission system which only existed because i developed on a 32-bit laptop for a bit
public const PERMS_MIN = 0;
public const PERMS_MAX = 9007199254740991;
private IDbConnection $dbConn;
private DbStatementCache $cache;
public function __construct(IDbConnection $dbConn) {
$this->dbConn = $dbConn;
$this->cache = new DbStatementCache($dbConn);
}
// this method is purely intended for getting the permission data for a single entity
// it should not be used to do actual permission checks
public function getPermissionInfo(
UserInfo|string|null $userInfo = null,
RoleInfo|string|null $roleInfo = null,
ForumCategoryInfo|string|null $forumCategoryInfo = null,
array|string|null $categoryNames = null,
): PermissionInfo|array|null {
$hasUserInfo = $userInfo !== null;
$hasRoleInfo = $roleInfo !== null;
if($hasUserInfo && $hasRoleInfo)
throw new InvalidArgumentException('$userInfo and $roleInfo may not be set at the same time.');
$hasForumCategoryInfo = $forumCategoryInfo !== null;
$hasCategoryName = $categoryNames !== null;
$categoryNamesIsArray = $hasCategoryName && is_array($categoryNames);
if($categoryNamesIsArray && empty($categoryNames))
throw new InvalidArgumentException('$categoryNames may not be empty if it is an array.');
$query = 'SELECT user_id, role_id, forum_id, perms_category, perms_allow, perms_deny FROM msz_perms';
$query .= sprintf(' WHERE user_id %s', $hasUserInfo ? '= ?' : 'IS NULL');
$query .= sprintf(' AND role_id %s', $hasRoleInfo ? '= ?' : 'IS NULL');
$query .= sprintf(' AND forum_id %s', $hasForumCategoryInfo ? '= ?' : 'IS NULL');
if($hasCategoryName)
$query .= ' AND perms_category ' . ($categoryNamesIsArray ? sprintf('IN (%s)', DbTools::prepareListString($categoryNames)) : '= ?');
$args = 0;
$stmt = $this->cache->get($query);
if($hasUserInfo)
$stmt->addParameter(++$args, $userInfo instanceof UserInfo ? $userInfo->getId() : $userInfo);
if($hasRoleInfo)
$stmt->addParameter(++$args, $roleInfo instanceof RoleInfo ? $roleInfo->getId() : $roleInfo);
if($hasForumCategoryInfo)
$stmt->addParameter(++$args, $forumCategoryInfo instanceof ForumCategoryInfo ? $forumCategoryInfo->getId() : $forumCategoryInfo);
if($hasCategoryName) {
if($categoryNamesIsArray) {
foreach($categoryNames as $name)
$stmt->addParameter(++$args, $name);
} else
$stmt->addParameter(++$args, $categoryNames);
}
$stmt->execute();
$result = $stmt->getResult();
if(is_string($categoryNames))
return $result->next() ? new PermissionInfo($result) : null;
$perms = [];
while($result->next())
$perms[$result->getString(3)] = new PermissionInfo($result);
return $perms;
}
public function setPermissions(
string $categoryName,
int $allow,
int $deny,
UserInfo|string|null $userInfo = null,
RoleInfo|string|null $roleInfo = null,
ForumCategoryInfo|string|null $forumCategoryInfo = null
): void {
if($allow < self::PERMS_MIN || $allow > self::PERMS_MAX)
throw new InvalidArgumentException('$allow must be an positive 53-bit integer.');
if($deny < self::PERMS_MIN || $deny > self::PERMS_MAX)
throw new InvalidArgumentException('$allow must be an positive 53-bit integer.');
if($userInfo !== null && $roleInfo !== null)
throw new InvalidArgumentException('$userInfo and $roleInfo may not be set at the same time.');
// because of funny technical reasons we have to delete separately
$this->removePermissions($categoryName, $userInfo, $roleInfo, $forumCategoryInfo);
// don't insert zeroes
if($allow === 0 && $deny === 0)
return;
$stmt = $this->cache->get('INSERT INTO msz_perms (user_id, role_id, forum_id, perms_category, perms_allow, perms_deny) VALUES (?, ?, ?, ?, ?, ?)');
$stmt->addParameter(1, $userInfo instanceof UserInfo ? $userInfo->getId() : $userInfo);
$stmt->addParameter(2, $roleInfo instanceof RoleInfo ? $roleInfo->getId() : $roleInfo);
$stmt->addParameter(3, $forumCategoryInfo instanceof ForumCategoryInfo ? $forumCategoryInfo->getId() : $forumCategoryInfo);
$stmt->addParameter(4, $categoryName);
$stmt->addParameter(5, $allow);
$stmt->addParameter(6, $deny);
$stmt->execute();
}
public function removePermissions(
array|string|null $categoryNames,
UserInfo|string|null $userInfo = null,
RoleInfo|string|null $roleInfo = null,
ForumCategoryInfo|string|null $forumCategoryInfo = null
): void {
$hasUserInfo = $userInfo !== null;
$hasRoleInfo = $roleInfo !== null;
$hasForumCategoryInfo = $forumCategoryInfo !== null;
$hasCategoryNames = $categoryNames !== null;
$categoryNamesIsArray = $hasCategoryNames && is_array($categoryNames);
$query = 'DELETE FROM msz_perms';
$query .= sprintf(' WHERE user_id %s', $hasUserInfo ? '= ?' : 'IS NULL');
$query .= sprintf(' AND role_id %s', $hasRoleInfo ? '= ?' : 'IS NULL');
$query .= sprintf(' AND forum_id %s', $hasForumCategoryInfo ? '= ?' : 'IS NULL');
if($hasCategoryNames)
$query .= ' AND perms_category ' . ($categoryNamesIsArray ? sprintf('IN (%s)', DbTools::prepareListString($categoryNames)) : '= ?');
$args = 0;
$stmt = $this->cache->get($query);
if($hasUserInfo)
$stmt->addParameter(++$args, $userInfo instanceof UserInfo ? $userInfo->getId() : $userInfo);
if($hasRoleInfo)
$stmt->addParameter(++$args, $roleInfo instanceof RoleInfo ? $roleInfo->getId() : $roleInfo);
if($hasForumCategoryInfo)
$stmt->addParameter(++$args, $forumCategoryInfo instanceof ForumCategoryInfo ? $forumCategoryInfo->getId() : $forumCategoryInfo);
if($categoryNamesIsArray) {
foreach($categoryNames as $name)
$stmt->addParameter(++$args, $name);
} else
$stmt->addParameter(++$args, $categoryNames);
$stmt->execute();
}
public function checkPermissions(
string $categoryName,
int $perms,
UserInfo|string|null $userInfo = null,
ForumCategoryInfo|string|null $forumCategoryInfo = null
): int {
$hasUserInfo = $userInfo !== null;
$hasForumCategoryInfo = $forumCategoryInfo !== null;
$query = 'SELECT perms_calculated & ? FROM msz_perms_calculated WHERE perms_category = ?';
$query .= sprintf(' AND forum_id %s', $hasForumCategoryInfo ? '= ?' : 'IS NULL');
$query .= sprintf(' AND user_id %s', $hasUserInfo ? '= ?' : 'IS NULL');
$args = 0;
$stmt = $this->cache->get($query);
$stmt->addParameter(++$args, $perms);
$stmt->addParameter(++$args, $categoryName);
if($hasForumCategoryInfo)
$stmt->addParameter(++$args, $forumCategoryInfo instanceof ForumCategoryInfo ? $forumCategoryInfo->getId() : $forumCategoryInfo);
if($hasUserInfo)
$stmt->addParameter(++$args, $userInfo instanceof UserInfo ? $userInfo->getId() : $userInfo);
$stmt->execute();
$result = $stmt->getResult();
return $result->next() ? $result->getInteger(0) : 0;
}
public function getPermissions(
string|array $categoryNames,
UserInfo|string|null $userInfo = null,
ForumCategoryInfo|string|null $forumCategoryInfo = null
): PermissionResult|stdClass {
$categoryNamesIsArray = is_array($categoryNames);
if($categoryNamesIsArray && empty($categoryNames))
throw new InvalidArgumentException('$categoryNames may not be an empty array.');
$hasUserInfo = $userInfo !== null;
$hasForumCategoryInfo = $forumCategoryInfo !== null;
$query = 'SELECT perms_category, perms_calculated FROM msz_perms_calculated';
$query .= ' WHERE perms_category ' . ($categoryNamesIsArray ? sprintf('IN (%s)', DbTools::prepareListString($categoryNames)) : '= ?');
$query .= sprintf(' AND forum_id %s', $hasForumCategoryInfo ? '= ?' : 'IS NULL');
$query .= sprintf(' AND user_id %s', $hasUserInfo ? '= ?' : 'IS NULL');
$query .= ' GROUP BY perms_category';
$args = 0;
$stmt = $this->cache->get($query);
if($categoryNamesIsArray) {
foreach($categoryNames as $name)
$stmt->addParameter(++$args, $name);
} else
$stmt->addParameter(++$args, $categoryNames);
if($hasForumCategoryInfo)
$stmt->addParameter(++$args, $forumCategoryInfo instanceof ForumCategoryInfo ? $forumCategoryInfo->getId() : $forumCategoryInfo);
if($hasUserInfo)
$stmt->addParameter(++$args, $userInfo instanceof UserInfo ? $userInfo->getId() : $userInfo);
$stmt->execute();
$result = $stmt->getResult();
if(!$categoryNamesIsArray)
return new PermissionResult($result->next() ? $result->getInteger(1) : 0);
$results = [];
while($result->next())
$results[$result->getString(0)] = $result->getInteger(1);
$sets = new stdClass;
foreach($categoryNames as $categoryName)
$sets->{$categoryName} = new PermissionResult($results[$categoryName] ?? 0);
return $sets;
}
// precalculates all permissions for fast lookups, don't run this from the browser lol
// TODO: only recalc a subset of users (e.g. personal permission changes/role add/remove)
public function precalculatePermissions(Forum $forum): void {
self::precalculatePermissionsLog('Loading list of user IDs...');
$userIds = [];
$result = $this->dbConn->query('SELECT user_id FROM msz_users');
while($result->next())
$userIds[] = $result->getString(0);
self::precalculatePermissionsLog('Clearing existing precalculations...');
$this->dbConn->execute('TRUNCATE msz_perms_calculated');
self::precalculatePermissionsLog('Creating inserter statement...');
$insert = $this->cache->get('INSERT INTO msz_perms_calculated (user_id, forum_id, perms_category, perms_calculated) VALUES (?, ?, ?, ?)');
self::precalculatePermissionsLog('Calculating guest permissions...');
$result = $this->dbConn->query('SELECT perms_category, BIT_OR(perms_allow) & ~BIT_OR(perms_deny) FROM msz_perms WHERE forum_id IS NULL AND user_id IS NULL AND role_id IS NULL GROUP BY perms_category');
$insert->addParameter(1, null);
$insert->addParameter(2, null);
while($result->next()) {
$category = $result->getString(0);
$perms = $result->getInteger(1);
if($perms === 0)
continue;
self::precalculatePermissionsLog('Inserting guest permissions for category %s with value %x...', $category, $perms);
$insert->addParameter(3, $category);
$insert->addParameter(4, $perms);
$insert->execute();
}
self::precalculatePermissionsLog('Calculating user permissions...');
$stmt = $this->cache->get('SELECT perms_category, BIT_OR(perms_allow) & ~BIT_OR(perms_deny) FROM msz_perms WHERE forum_id IS NULL AND (user_id = ? OR role_id IN (SELECT role_id FROM msz_users_roles WHERE user_id = ?)) GROUP BY perms_category');
foreach($userIds as $userId) {
$insert->reset();
$insert->addParameter(1, $userId);
$insert->addParameter(2, null);
$stmt->reset();
$stmt->addParameter(1, $userId);
$stmt->addParameter(2, $userId);
$stmt->execute();
$result = $stmt->getResult();
while($result->next()) {
$category = $result->getString(0);
$perms = $result->getInteger(1);
if($perms === 0)
continue;
self::precalculatePermissionsLog('Inserting user #%s permissions for category %s with value %x...', $userId, $category, $perms);
$insert->addParameter(3, $category);
$insert->addParameter(4, $perms);
$insert->execute();
}
}
self::precalculatePermissionsLog('Loading list of forum categories...');
$forumCats = $forum->getCategories(asTree: true);
foreach($forumCats as $forumCat)
$this->precalculatePermissionsForForumCategory($insert, $userIds, $forumCat);
self::precalculatePermissionsLog('Finished permission precalculations!');
}
private function precalculatePermissionsForForumCategory(IDbStatement $insert, array $userIds, object $forumCat, array $catIds = []): void {
$catIds[] = $currentCatId = $forumCat->info->getId();
self::precalculatePermissionsLog('Precalcuting permissions for forum category #%s (%s)...', $currentCatId, implode(' <- ', $catIds));
self::precalculatePermissionsLog('Calculating guest permission for forum category #%s...', $currentCatId);
$args = 0;
$stmt = $this->cache->get(sprintf(
'SELECT perms_category, BIT_OR(perms_allow) & ~BIT_OR(perms_deny) FROM msz_perms WHERE forum_id IN (%s) AND user_id IS NULL AND role_id IS NULL GROUP BY perms_category',
DbTools::prepareListString($catIds)
));
foreach($catIds as $catId)
$stmt->addParameter(++$args, $catId);
$stmt->execute();
$insert->reset();
$insert->addParameter(1, null);
$insert->addParameter(2, $currentCatId);
$result = $stmt->getResult();
while($result->next()) {
$category = $result->getString(0);
$perms = $result->getInteger(1);
if($perms === 0)
continue;
self::precalculatePermissionsLog('Inserting guest permissions for category %s with value %x for forum category #%s...', $category, $perms, $currentCatId);
$insert->addParameter(3, $category);
$insert->addParameter(4, $perms);
$insert->execute();
}
$args = 0;
$stmt = $this->cache->get(sprintf(
'SELECT perms_category, BIT_OR(perms_allow) & ~BIT_OR(perms_deny) FROM msz_perms WHERE forum_id IN (%s) AND (user_id = ? OR role_id IN (SELECT role_id FROM msz_users_roles WHERE user_id = ?)) GROUP BY perms_category',
DbTools::prepareListString($catIds)
));
foreach($catIds as $catId)
$stmt->addParameter(++$args, $catId);
$startArgs = $args;
foreach($userIds as $userId) {
$args = $startArgs;
$stmt->addParameter(++$args, $userId);
$stmt->addParameter(++$args, $userId);
$stmt->execute();
$insert->reset();
$insert->addParameter(1, $userId);
$insert->addParameter(2, $currentCatId);
$result = $stmt->getResult();
while($result->next()) {
$category = $result->getString(0);
$perms = $result->getInteger(1);
if($perms === 0)
continue;
self::precalculatePermissionsLog('Inserting user #%s permissions for category %s with value %x for forum category #%s...', $userId, $category, $perms, $currentCatId);
$insert->addParameter(3, $category);
$insert->addParameter(4, $perms);
$insert->execute();
}
}
foreach($forumCat->children as $forumChild)
$this->precalculatePermissionsForForumCategory($insert, $userIds, $forumChild, $catIds);
}
private static function precalculatePermissionsLog(string $fmt, ...$args): void {
echo DateTime::now()->format('[H:i:s.u] ');
vprintf($fmt, $args);
echo PHP_EOL;
}
}

View File

@ -1,6 +1,8 @@
<?php
namespace Misuzu\SharpChat;
use Misuzu\Perm;
use Misuzu\Perms\Permissions;
use Misuzu\Users\UserInfo;
final class SharpChatPerms {
@ -30,25 +32,25 @@ final class SharpChatPerms {
private const PERMS_MANAGE_FORUM = self::P_CREATE_CHANNEL | self::P_SET_CHAN_PERMA | self::P_SET_CHAN_PASS
| self::P_SET_CHAN_HIER | self::P_DELETE_CHANNEL | self::P_JOIN_ANY_CHAN;
public static function convert(UserInfo $userInfo): int {
$userInfo = (int)$userInfo->getId();
$perms = self::PERMS_DEFAULT;
public static function convert(Permissions $perms, UserInfo $userInfo): int {
$perms = $perms->getPermissions(['global', 'user'], $userInfo);
$convert = self::PERMS_DEFAULT;
if(perms_check_user(MSZ_PERMS_USER, $userInfo, MSZ_PERM_USER_MANAGE_USERS))
$perms |= self::PERMS_MANAGE_USERS;
if($perms->user->check(Perm::U_USERS_MANAGE))
$convert |= self::PERMS_MANAGE_USERS;
if(perms_check_user(MSZ_PERMS_USER, $userInfo, MSZ_PERM_USER_MANAGE_WARNINGS))
$perms |= self::P_KICK_USER;
if($perms->user->check(Perm::U_WARNINGS_MANAGE))
$convert |= self::P_KICK_USER;
if(perms_check_user(MSZ_PERMS_USER, $userInfo, MSZ_PERM_USER_MANAGE_BANS))
$perms |= self::P_BAN_USER;
if($perms->user->check(Perm::U_BANS_MANAGE))
$convert |= self::P_BAN_USER;
if(perms_check_user(MSZ_PERMS_USER, $userInfo, MSZ_PERM_USER_CHANGE_BACKGROUND))
$perms |= self::PERMS_CHANGE_BACKG;
if($perms->user->check(Perm::U_PROFILE_BACKGROUND_CHANGE))
$convert |= self::PERMS_CHANGE_BACKG;
if(perms_check_user(MSZ_PERMS_FORUM, $userInfo, MSZ_PERM_FORUM_MANAGE_FORUMS))
$perms |= self::PERMS_MANAGE_FORUM;
if($perms->global->check(Perm::G_FORUM_CATEGORIES_MANAGE))
$convert |= self::PERMS_MANAGE_FORUM;
return $perms;
return $convert;
}
}

View File

@ -10,6 +10,7 @@ use Misuzu\Auth\AuthInfo;
use Misuzu\Auth\Sessions;
use Misuzu\Config\IConfig;
use Misuzu\Emoticons\Emotes;
use Misuzu\Perms\Permissions;
use Misuzu\Users\Bans;
use Misuzu\Users\Users;
@ -19,6 +20,7 @@ final class SharpChatRoutes {
private Emotes $emotes;
private Users $users;
private Sessions $sessions;
private Permissions $perms;
private AuthInfo $authInfo;
private Closure $createAuthTokenPacker;
private string $hashKey;
@ -30,6 +32,7 @@ final class SharpChatRoutes {
Emotes $emotes,
Users $users,
Sessions $sessions,
Permissions $perms,
AuthInfo $authInfo,
Closure $createAuthTokenPacker // this sucks lol
) {
@ -38,6 +41,7 @@ final class SharpChatRoutes {
$this->emotes = $emotes;
$this->users = $users;
$this->sessions = $sessions;
$this->perms = $perms;
$this->authInfo = $authInfo;
$this->createAuthTokenPacker = $createAuthTokenPacker;
$this->hashKey = $this->config->getString('hashKey', 'woomy');
@ -268,7 +272,7 @@ final class SharpChatRoutes {
'colour_raw' => Colour::toMisuzu($userColour),
'rank' => $userRank,
'hierarchy' => $userRank,
'perms' => SharpChatPerms::convert($userInfo),
'perms' => SharpChatPerms::convert($this->perms, $userInfo),
];
}

View File

@ -4,6 +4,7 @@ namespace Misuzu\Users\Assets;
use InvalidArgumentException;
use RuntimeException;
use Index\Routing\IRouter;
use Misuzu\Perm;
use Misuzu\Auth\AuthInfo;
use Misuzu\Users\Bans;
use Misuzu\Users\Users;
@ -28,8 +29,8 @@ class AssetsRoutes {
private function canViewAsset($request, UserInfo $assetUser): bool {
if($this->bans->countActiveBans($assetUser))
return $this->authInfo->isLoggedIn() // allow staff viewing profile to still see banned user assets
&& perms_check_user(MSZ_PERMS_USER, (int)$this->authInfo->getUserId(), MSZ_PERM_USER_MANAGE_USERS)
// allow staff viewing profile to still see banned user assets
return $this->authInfo->getPerms('user')->check(Perm::U_USERS_MANAGE)
&& parse_url($request->getHeaderFirstLine('Referer'), PHP_URL_PATH) === url('user-profile');
return true;

View File

@ -1,729 +0,0 @@
<?php
define('MSZ_PERMS_ALLOW', 'allow');
define('MSZ_PERMS_DENY', 'deny');
define('MSZ_PERMS_GENERAL', 'general');
define('MSZ_PERM_GENERAL_CAN_MANAGE', 0x00000001);
define('MSZ_PERM_GENERAL_VIEW_LOGS', 0x00000002);
define('MSZ_PERM_GENERAL_MANAGE_EMOTES', 0x00000004);
define('MSZ_PERM_GENERAL_MANAGE_CONFIG', 0x00000008);
//define('MSZ_PERM_GENERAL_IS_TESTER', 0x00000010); Has been unused for a while
//define('MSZ_PERM_GENERAL_MANAGE_BLACKLIST', 0x00000020); Blacklist has been removed for now to reduce overhead and because it was broken(?)
//define('MSZ_PERM_GENERAL_MANAGE_TWITTER', 0x00000040); Twitter integration has been removed
define('MSZ_PERMS_USER', 'user');
define('MSZ_PERM_USER_EDIT_PROFILE', 0x00000001);
define('MSZ_PERM_USER_CHANGE_AVATAR', 0x00000002);
define('MSZ_PERM_USER_CHANGE_BACKGROUND', 0x00000004);
define('MSZ_PERM_USER_EDIT_ABOUT', 0x00000008);
define('MSZ_PERM_USER_EDIT_BIRTHDATE', 0x00000010);
define('MSZ_PERM_USER_EDIT_SIGNATURE', 0x00000020);
define('MSZ_PERM_USER_MANAGE_USERS', 0x00100000);
define('MSZ_PERM_USER_MANAGE_ROLES', 0x00200000);
define('MSZ_PERM_USER_MANAGE_PERMS', 0x00400000);
define('MSZ_PERM_USER_MANAGE_REPORTS', 0x00800000);
define('MSZ_PERM_USER_MANAGE_WARNINGS', 0x01000000);
//define('MSZ_PERM_USER_MANAGE_BLACKLISTS', 0x02000000); // Replaced with MSZ_PERM_GENERAL_MANAGE_BLACKLIST
define('MSZ_PERM_USER_MANAGE_NOTES', 0x04000000);
define('MSZ_PERM_USER_MANAGE_BANS', 0x08000000);
define('MSZ_PERM_USER_IMPERSONATE', 0x10000000);
define('MSZ_PERMS_CHANGELOG', 'changelog');
define('MSZ_PERM_CHANGELOG_MANAGE_CHANGES', 0x00000001);
define('MSZ_PERM_CHANGELOG_MANAGE_TAGS', 0x00000002);
//define('MSZ_PERM_CHANGELOG_MANAGE_ACTIONS', 0x00000004); Deprecated, actions are hardcoded now
define('MSZ_PERMS_NEWS', 'news');
define('MSZ_PERM_NEWS_MANAGE_POSTS', 0x00000001);
define('MSZ_PERM_NEWS_MANAGE_CATEGORIES', 0x00000002);
define('MSZ_PERMS_FORUM', 'forum');
define('MSZ_PERM_FORUM_MANAGE_FORUMS', 0x00000001);
define('MSZ_PERM_FORUM_VIEW_LEADERBOARD', 0x00000002);
define('MSZ_PERM_FORUM_TOPIC_REDIRS', 0x00000004);
define('MSZ_PERMS_COMMENTS', 'comments');
define('MSZ_PERM_COMMENTS_CREATE', 0x00000001);
//define('MSZ_PERM_COMMENTS_EDIT_OWN', 0x00000002);
//define('MSZ_PERM_COMMENTS_EDIT_ANY', 0x00000004);
define('MSZ_PERM_COMMENTS_DELETE_OWN', 0x00000008);
define('MSZ_PERM_COMMENTS_DELETE_ANY', 0x00000010);
define('MSZ_PERM_COMMENTS_PIN', 0x00000020);
define('MSZ_PERM_COMMENTS_LOCK', 0x00000040);
define('MSZ_PERM_COMMENTS_VOTE', 0x00000080);
define('MSZ_PERM_MODES', [
MSZ_PERMS_GENERAL, MSZ_PERMS_USER, MSZ_PERMS_CHANGELOG,
MSZ_PERMS_NEWS, MSZ_PERMS_FORUM, MSZ_PERMS_COMMENTS,
]);
define('MSZ_FORUM_PERMS_GENERAL', 'forum');
define('MSZ_FORUM_PERM_LIST_FORUM', 0x00000001); // can see stats, but will get error when trying to view
define('MSZ_FORUM_PERM_VIEW_FORUM', 0x00000002);
define('MSZ_FORUM_PERM_CREATE_TOPIC', 0x00000400);
//define('MSZ_FORUM_PERM_DELETE_TOPIC', 0x00000800); // use MSZ_FORUM_PERM_DELETE_ANY_POST instead
define('MSZ_FORUM_PERM_MOVE_TOPIC', 0x00001000);
define('MSZ_FORUM_PERM_LOCK_TOPIC', 0x00002000);
define('MSZ_FORUM_PERM_STICKY_TOPIC', 0x00004000);
define('MSZ_FORUM_PERM_ANNOUNCE_TOPIC', 0x00008000);
define('MSZ_FORUM_PERM_GLOBAL_ANNOUNCE_TOPIC', 0x00010000);
define('MSZ_FORUM_PERM_BUMP_TOPIC', 0x00020000);
//define('MSZ_FORUM_PERM_PRIORITY_VOTE', 0x00040000); // feature postponed, perhaps reuse if it makes sense to
define('MSZ_FORUM_PERM_CREATE_POST', 0x00100000);
define('MSZ_FORUM_PERM_EDIT_POST', 0x00200000);
define('MSZ_FORUM_PERM_EDIT_ANY_POST', 0x00400000);
define('MSZ_FORUM_PERM_DELETE_POST', 0x00800000);
define('MSZ_FORUM_PERM_DELETE_ANY_POST', 0x01000000);
define('MSZ_FORUM_PERM_MODES', [
MSZ_FORUM_PERMS_GENERAL,
]);
function perms_get_keys(array $modes = MSZ_PERM_MODES): array {
$perms = [];
foreach($modes as $mode) {
$perms[] = perms_get_key($mode, MSZ_PERMS_ALLOW);
$perms[] = perms_get_key($mode, MSZ_PERMS_DENY);
}
return $perms;
}
function perms_create(array $modes = MSZ_PERM_MODES): array {
return array_fill_keys(perms_get_keys($modes), 0);
}
function perms_get_key(string $prefix, string $suffix): string {
return $prefix . '_perms_' . $suffix;
}
function perms_get_select(array $modes = MSZ_PERM_MODES, string $allow = MSZ_PERMS_ALLOW, string $deny = MSZ_PERMS_DENY): string {
$select = '';
foreach($modes as $mode) {
$select .= sprintf(
'(BIT_OR(`%1$s_perms_%2$s`) &~ BIT_OR(`%1$s_perms_%3$s`)) AS `%1$s`,',
$mode, $allow, $deny
);
}
$select = substr($select, 0, -1);
return $select;
}
function perms_get_blank(array $modes = MSZ_PERM_MODES): array {
return array_fill_keys($modes, 0);
}
function perms_get_user(int|string $user): array {
if(is_string($user))
$user = (int)$user;
if($user < 1)
return perms_get_blank();
static $memo = [];
if(array_key_exists($user, $memo)) {
return $memo[$user];
}
$getPerms = \Misuzu\DB::prepare(sprintf(
'
SELECT %s
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_users_roles`
WHERE `user_id` = :user_id_2
)
)
',
perms_get_select()
));
$getPerms->bind('user_id_1', $user);
$getPerms->bind('user_id_2', $user);
return $memo[$user] = $getPerms->fetch();
}
function perms_delete_user(int|string $user): bool {
if(is_string($user))
$user = (int)$user;
if($user < 1)
return false;
$deletePermissions = \Misuzu\DB::prepare('
DELETE FROM `msz_permissions`
WHERE `role_id` IS NULL
AND `user_id` = :user_id
');
$deletePermissions->bind('user_id', $user);
return $deletePermissions->execute();
}
function perms_get_role(int $role): array {
if($role < 1) {
return perms_get_blank();
}
static $memo = [];
if(array_key_exists($role, $memo)) {
return $memo[$role];
}
$getPerms = \Misuzu\DB::prepare(sprintf(
'
SELECT %s
FROM `msz_permissions`
WHERE `role_id` = :role_id
AND `user_id` IS NULL
',
perms_get_select()
));
$getPerms->bind('role_id', $role);
return $memo[$role] = $getPerms->fetch();
}
function perms_get_user_raw(int|string $user): array {
if(is_string($user))
$user = (int)$user;
if($user < 1)
return perms_create();
$getPerms = \Misuzu\DB::prepare(sprintf('
SELECT `%s`
FROM `msz_permissions`
WHERE `user_id` = :user_id
AND `role_id` IS NULL
', implode('`, `', perms_get_keys())));
$getPerms->bind('user_id', $user);
$perms = $getPerms->fetch();
if(empty($perms)) {
return perms_create();
}
return $perms;
}
function perms_set_user_raw(int|string $user, array $perms): bool {
if(is_string($user))
$user = (int)$user;
if($user < 1)
return false;
$realPerms = perms_create();
$permKeys = array_keys($realPerms);
foreach($permKeys as $perm) {
$realPerms[$perm] = (int)($perms[$perm] ?? 0);
}
$setPermissions = \Misuzu\DB::prepare(sprintf(
'
REPLACE INTO `msz_permissions`
(`role_id`, `user_id`, `%s`)
VALUES
(NULL, :user_id, :%s)
',
implode('`, `', $permKeys),
implode(', :', $permKeys)
));
$setPermissions->bind('user_id', $user);
foreach($realPerms as $key => $value) {
$setPermissions->bind($key, $value);
}
return $setPermissions->execute();
}
function perms_get_role_raw(int $role): array {
if($role < 1) {
return perms_create();
}
$getPerms = \Misuzu\DB::prepare(sprintf('
SELECT `%s`
FROM `msz_permissions`
WHERE `user_id` IS NULL
AND `role_id` = :role_id
', implode('`, `', perms_get_keys())));
$getPerms->bind('role_id', $role);
$perms = $getPerms->fetch();
if(empty($perms)) {
return perms_create();
}
return $perms;
}
function perms_check(?int $perms, ?int $perm, bool $strict = false): bool {
$and = ($perms ?? 0) & ($perm ?? 0);
return $strict ? $and === $perm : $and > 0;
}
function perms_check_user(string $prefix, int|string|null $userId, int $perm, bool $strict = false): bool {
if(is_string($userId))
$userId = (int)$userId;
return $userId > 0 && perms_check(perms_get_user($userId)[$prefix] ?? 0, $perm, $strict);
}
function perms_check_bulk(int $perms, array $set, bool $strict = false): array {
foreach($set as $key => $perm) {
$set[$key] = perms_check($perms, $perm, $strict);
}
return $set;
}
function perms_check_user_bulk(string $prefix, int|string|null $userId, array $set, bool $strict = false): array {
$perms = perms_get_user($userId)[$prefix] ?? 0;
return perms_check_bulk($perms, $set, $strict);
}
function perms_for_comments(string|int $userId): array {
return perms_check_user_bulk(MSZ_PERMS_COMMENTS, $userId, [
'can_comment' => MSZ_PERM_COMMENTS_CREATE,
'can_delete' => MSZ_PERM_COMMENTS_DELETE_OWN | MSZ_PERM_COMMENTS_DELETE_ANY,
'can_delete_any' => MSZ_PERM_COMMENTS_DELETE_ANY,
'can_pin' => MSZ_PERM_COMMENTS_PIN,
'can_lock' => MSZ_PERM_COMMENTS_LOCK,
'can_vote' => MSZ_PERM_COMMENTS_VOTE,
]);
}
function forum_get_parent_id(int $forumId): int {
if($forumId < 1)
return 0;
static $memoized = [];
if(array_key_exists($forumId, $memoized))
return $memoized[$forumId];
$getParent = \Misuzu\DB::prepare('
SELECT `forum_parent`
FROM `msz_forum_categories`
WHERE `forum_id` = :forum_id
');
$getParent->bind('forum_id', $forumId);
return (int)$getParent->fetchColumn();
}
function forum_perms_get_user(?int $forum, int $user): array {
$perms = perms_get_blank(MSZ_FORUM_PERM_MODES);
if($user < 0 || $forum < 0)
return $perms;
static $memo = [];
$memoId = "{$forum}-{$user}";
if(array_key_exists($memoId, $memo))
return $memo[$memoId];
if($forum > 0)
$perms = forum_perms_get_user(
forum_get_parent_id($forum),
$user
);
$getPerms = \Misuzu\DB::prepare(sprintf(
'
SELECT %s
FROM `msz_forum_permissions`
WHERE (`forum_id` = :forum_id OR `forum_id` IS NULL)
AND (
(`user_id` IS NULL AND `role_id` IS NULL)
OR (`user_id` = :user_id_1 AND `role_id` IS NULL)
OR (
`user_id` IS NULL
AND `role_id` IN (
SELECT `role_id`
FROM `msz_users_roles`
WHERE `user_id` = :user_id_2
)
)
)
',
perms_get_select(MSZ_FORUM_PERM_MODES)
));
$getPerms->bind('forum_id', $forum);
$getPerms->bind('user_id_1', $user);
$getPerms->bind('user_id_2', $user);
$userPerms = $getPerms->fetch();
foreach($perms as $key => $value)
$perms[$key] |= $userPerms[$key] ?? 0;
return $memo[$memoId] = $perms;
}
function forum_perms_check_user(
string $prefix,
?int $forumId,
?int $userId,
int $perm,
bool $strict = false
): bool {
return perms_check(forum_perms_get_user($forumId, $userId)[$prefix] ?? 0, $perm, $strict);
}
define('MSZ_MANAGE_PERM_YES', 'yes');
define('MSZ_MANAGE_PERM_NO', 'no');
define('MSZ_MANAGE_PERM_NEVER', 'never');
function manage_perms_value(int $perm, int $allow, int $deny): string {
if(perms_check($deny, $perm))
return MSZ_MANAGE_PERM_NEVER;
if(perms_check($allow, $perm))
return MSZ_MANAGE_PERM_YES;
return MSZ_MANAGE_PERM_NO;
}
function manage_perms_apply(array $list, array $post, ?array $raw = null): ?array {
$perms = $raw !== null ? $raw : perms_create();
foreach($list as $section) {
if(empty($post[$section['section']]) || !is_array($post[$section['section']]))
continue;
$allowKey = perms_get_key($section['section'], MSZ_PERMS_ALLOW);
$denyKey = perms_get_key($section['section'], MSZ_PERMS_DENY);
foreach($section['perms'] as $perm) {
if(empty($post[$section['section']][$perm['section']]['value']))
continue;
$perm['perm'] = (int)$perm['perm']; // makes phpstan happy
switch($post[$section['section']][$perm['section']]['value']) {
case MSZ_MANAGE_PERM_YES:
$perms[$allowKey] |= $perm['perm'];
$perms[$denyKey] &= ~$perm['perm'];
break;
case MSZ_MANAGE_PERM_NEVER:
$perms[$allowKey] &= ~$perm['perm'];
$perms[$denyKey] |= $perm['perm'];
break;
case MSZ_MANAGE_PERM_NO:
default:
$perms[$allowKey] &= ~$perm['perm'];
$perms[$denyKey] &= ~$perm['perm'];
break;
}
}
}
$returnNothing = 0;
foreach($perms as $perm)
$returnNothing |= $perm;
return $returnNothing === 0 ? null : $perms;
}
function manage_perms_calculate(array $rawPerms, array $perms): array {
for($i = 0; $i < count($perms); $i++) {
$section = $perms[$i]['section'];
$allowKey = perms_get_key($section, MSZ_PERMS_ALLOW);
$denyKey = perms_get_key($section, MSZ_PERMS_DENY);
for($j = 0; $j < count($perms[$i]['perms']); $j++) {
$permission = $perms[$i]['perms'][$j]['perm'];
$perms[$i]['perms'][$j]['value'] = manage_perms_value($permission, $rawPerms[$allowKey], $rawPerms[$denyKey]);
}
}
return $perms;
}
function manage_perms_list(array $rawPerms): array {
return manage_perms_calculate($rawPerms, [
[
'section' => MSZ_PERMS_GENERAL,
'title' => 'General',
'perms' => [
[
'section' => 'can-manage',
'title' => 'Can access the management panel.',
'perm' => MSZ_PERM_GENERAL_CAN_MANAGE,
],
[
'section' => 'view-logs',
'title' => 'Can view audit logs.',
'perm' => MSZ_PERM_GENERAL_VIEW_LOGS,
],
[
'section' => 'manage-emotes',
'title' => 'Can manage emoticons.',
'perm' => MSZ_PERM_GENERAL_MANAGE_EMOTES,
],
[
'section' => 'manage-settings',
'title' => 'Can manage general Misuzu settings.',
'perm' => MSZ_PERM_GENERAL_MANAGE_CONFIG,
],
],
],
[
'section' => MSZ_PERMS_USER,
'title' => 'User',
'perms' => [
[
'section' => 'edit-profile',
'title' => 'Can edit own profile.',
'perm' => MSZ_PERM_USER_EDIT_PROFILE,
],
[
'section' => 'change-avatar',
'title' => 'Can change own avatar.',
'perm' => MSZ_PERM_USER_CHANGE_AVATAR,
],
[
'section' => 'change-background',
'title' => 'Can change own background.',
'perm' => MSZ_PERM_USER_CHANGE_BACKGROUND,
],
[
'section' => 'edit-about',
'title' => 'Can change own about section.',
'perm' => MSZ_PERM_USER_EDIT_ABOUT,
],
[
'section' => 'edit-birthdate',
'title' => 'Can change own birthdate.',
'perm' => MSZ_PERM_USER_EDIT_BIRTHDATE,
],
[
'section' => 'edit-signature',
'title' => 'Can change own signature.',
'perm' => MSZ_PERM_USER_EDIT_SIGNATURE,
],
[
'section' => 'manage-users',
'title' => 'Can manage other users.',
'perm' => MSZ_PERM_USER_MANAGE_USERS,
],
[
'section' => 'manage-roles',
'title' => 'Can manage roles.',
'perm' => MSZ_PERM_USER_MANAGE_ROLES,
],
[
'section' => 'manage-perms',
'title' => 'Can manage permissions.',
'perm' => MSZ_PERM_USER_MANAGE_PERMS,
],
[
'section' => 'manage-reports',
'title' => 'Can handle reports.',
'perm' => MSZ_PERM_USER_MANAGE_REPORTS,
],
[
'section' => 'manage-notes',
'title' => 'Can manage user notes.',
'perm' => MSZ_PERM_USER_MANAGE_NOTES,
],
[
'section' => 'manage-warnings',
'title' => 'Can manage user warnings.',
'perm' => MSZ_PERM_USER_MANAGE_WARNINGS,
],
[
'section' => 'manage-bans',
'title' => 'Can manage user bans.',
'perm' => MSZ_PERM_USER_MANAGE_BANS,
],
[
'section' => 'impersonate',
'title' => 'Can impersonate select users.',
'perm' => MSZ_PERM_USER_IMPERSONATE,
],
],
],
[
'section' => MSZ_PERMS_NEWS,
'title' => 'News',
'perms' => [
[
'section' => 'manage-posts',
'title' => 'Can manage posts.',
'perm' => MSZ_PERM_NEWS_MANAGE_POSTS,
],
[
'section' => 'manage-cats',
'title' => 'Can manage catagories.',
'perm' => MSZ_PERM_NEWS_MANAGE_CATEGORIES,
],
],
],
[
'section' => MSZ_PERMS_FORUM,
'title' => 'Forum',
'perms' => [
[
'section' => 'manage-forums',
'title' => 'Can manage forum sections.',
'perm' => MSZ_PERM_FORUM_MANAGE_FORUMS,
],
[
'section' => 'view-leaderboard',
'title' => 'Can view the forum leaderboard live.',
'perm' => MSZ_PERM_FORUM_VIEW_LEADERBOARD,
],
[
'section' => 'topic-redirs',
'title' => 'Can create redirects for deleted topics.',
'perm' => MSZ_PERM_FORUM_TOPIC_REDIRS,
],
],
],
[
'section' => MSZ_PERMS_COMMENTS,
'title' => 'Comments',
'perms' => [
[
'section' => 'create',
'title' => 'Can post comments.',
'perm' => MSZ_PERM_COMMENTS_CREATE,
],
[
'section' => 'delete-own',
'title' => 'Can delete own comments.',
'perm' => MSZ_PERM_COMMENTS_DELETE_OWN,
],
[
'section' => 'delete-any',
'title' => 'Can delete anyone\'s comments.',
'perm' => MSZ_PERM_COMMENTS_DELETE_ANY,
],
[
'section' => 'pin',
'title' => 'Can pin comments.',
'perm' => MSZ_PERM_COMMENTS_PIN,
],
[
'section' => 'lock',
'title' => 'Can lock comment threads.',
'perm' => MSZ_PERM_COMMENTS_LOCK,
],
[
'section' => 'vote',
'title' => 'Can like or dislike comments.',
'perm' => MSZ_PERM_COMMENTS_VOTE,
],
],
],
[
'section' => MSZ_PERMS_CHANGELOG,
'title' => 'Changelog',
'perms' => [
[
'section' => 'manage-changes',
'title' => 'Can manage changes.',
'perm' => MSZ_PERM_CHANGELOG_MANAGE_CHANGES,
],
[
'section' => 'manage-tags',
'title' => 'Can manage tags.',
'perm' => MSZ_PERM_CHANGELOG_MANAGE_TAGS,
],
],
],
]);
}
function manage_forum_perms_list(array $rawPerms): array {
return manage_perms_calculate($rawPerms, [
[
'section' => MSZ_FORUM_PERMS_GENERAL,
'title' => 'Forum',
'perms' => [
[
'section' => 'can-list',
'title' => 'Can see the forum listed, but not access it.',
'perm' => MSZ_FORUM_PERM_LIST_FORUM,
],
[
'section' => 'can-view',
'title' => 'Can view and access the forum.',
'perm' => MSZ_FORUM_PERM_VIEW_FORUM,
],
[
'section' => 'can-create-topic',
'title' => 'Can create topics.',
'perm' => MSZ_FORUM_PERM_CREATE_TOPIC,
],
[
'section' => 'can-move-topic',
'title' => 'Can move topics between forums.',
'perm' => MSZ_FORUM_PERM_MOVE_TOPIC,
],
[
'section' => 'can-lock-topic',
'title' => 'Can lock topics.',
'perm' => MSZ_FORUM_PERM_LOCK_TOPIC,
],
[
'section' => 'can-sticky-topic',
'title' => 'Can make topics sticky.',
'perm' => MSZ_FORUM_PERM_STICKY_TOPIC,
],
[
'section' => 'can-announce-topic',
'title' => 'Can make topics announcements.',
'perm' => MSZ_FORUM_PERM_ANNOUNCE_TOPIC,
],
[
'section' => 'can-global-announce-topic',
'title' => 'Can make topics global announcements.',
'perm' => MSZ_FORUM_PERM_GLOBAL_ANNOUNCE_TOPIC,
],
[
'section' => 'can-bump-topic',
'title' => 'Can bump topics without posting a reply.',
'perm' => MSZ_FORUM_PERM_BUMP_TOPIC,
],
[
'section' => 'can-create-post',
'title' => 'Can make posts (reply only, if create topic is disallowed).',
'perm' => MSZ_FORUM_PERM_CREATE_POST,
],
[
'section' => 'can-edit-post',
'title' => 'Can edit their own posts.',
'perm' => MSZ_FORUM_PERM_EDIT_POST,
],
[
'section' => 'can-edit-any-post',
'title' => 'Can edit any posts.',
'perm' => MSZ_FORUM_PERM_EDIT_ANY_POST,
],
[
'section' => 'can-delete-post',
'title' => 'Can delete own posts.',
'perm' => MSZ_FORUM_PERM_DELETE_POST,
],
[
'section' => 'can-delete-any-post',
'title' => 'Can delete any posts.',
'perm' => MSZ_FORUM_PERM_DELETE_ANY_POST,
],
],
],
]);
}

View File

@ -127,7 +127,7 @@
{% endif %}
</a>
{% endif %}
{% if perms.can_comment|default(false) %}
{% if perms.can_post|default(false) %}
<label class="comment__action comment__action--link" for="comment-reply-toggle-{{ comment.id }}">Reply</label>
{% endif %}
{% if perms.can_delete_any|default(false) or (poster.id|default(0) == user.id and perms.can_delete|default(false)) %}
@ -148,7 +148,7 @@
<div class="comment__replies comment__replies--indent-{{ indent }}" id="comment-{{ comment.id }}-replies">
{% from _self import comments_entry, comments_input %}
{% if user|default(null) is not null and category|default(null) is not null and perms.can_comment|default(false) %}
{% if user|default(null) is not null and category|default(null) is not null and perms.can_post|default(false) %}
{{ input_checkbox_raw('', false, 'comment__reply-toggle', '', false, {'id':'comment-reply-toggle-' ~ comment.id}) }}
{{ comments_input(category, user, perms, comment) }}
{% endif %}
@ -183,7 +183,7 @@
<div class="comments__notice">
This comment section was locked, <time datetime="{{ category.lockedTime|date('c') }}" title="{{ category.lockedTime|date('r') }}">{{ category.lockedTime|time_format }}</time>.
</div>
{% elseif not perms.can_comment|default(false) %}
{% elseif not perms.can_post|default(false) %}
<div class="comments__notice">
You are not allowed to post comments.
</div>
@ -199,12 +199,6 @@
</div>
{% endif %}
{#<noscript>
<div class="comments__javascript">
While the comments work fine without Javascript, it is recommended you enable it as it has a lower bandwidth overhead.
</div>
</noscript>#}
<div class="comments__listing">
{% if posts|length > 0 %}
{% from _self import comments_entry %}

View File

@ -10,17 +10,23 @@
{% if calculated_perms is defined %}
<table border="1">
<tr>
<th style="padding: 2px 5px;">Category</th>
<th style="padding: 2px 5px;">Allow</th>
<th style="padding: 2px 5px;">Deny</th>
</tr>
{% for key, value in calculated_perms %}
<tr>
<th>{{ key }}</th>
<td><code>{{ value }}</code></td>
<th style="padding: 2px 5px;">{{ key }}</th>
<td style="padding: 2px 5px;"><code>{{ value.allow }}</code></td>
<td style="padding: 2px 5px;"><code>{{ value.deny }}</code></td>
</tr>
{% endfor %}
</table>
{% endif %}
<form method="post" action="">
{{ permissions_table(perms) }}
{{ permissions_table(perms_lists, perms_infos) }}
<button class="input__button">Calculate</button>
</form>
</div>

View File

@ -14,14 +14,14 @@
{% endfor %}
{% endmacro %}
{% macro permissions_table(permissions, readonly) %}
{% macro permissions_table(lists, infos, readonly) %}
{% from '_layout/input.twig' import input_checkbox %}
<div class="permissions">
{% for perms in permissions %}
{% for list in lists %}
<div class="permissions__line permissions__line--header">
<div class="permissions__title">
{{ perms.title }}
{{ list.title }}
</div>
<div class="permissions__choice">
Yes
@ -34,19 +34,19 @@
</div>
</div>
{% for perm in perms.perms %}
{% for perm in list.perms %}
<div class="permissions__line">
<div class="permissions__title">
{{ perm.title }}
</div>
<div class="permissions__choice__wrapper">
{{ input_checkbox('perms[' ~ perms.section ~ '][' ~ perm.section ~ '][value]', '', perm.value == 'yes', 'permissions__choice permissions__choice--radio permissions__choice--yes', 'yes', true, null, readonly) }}
{{ input_checkbox(perm.name, '', infos[perm.category].checkAllow(perm.value) ?? false, 'permissions__choice permissions__choice--radio permissions__choice--yes', 'yes', true, null, readonly) }}
</div>
<div class="permissions__choice__wrapper">
{{ input_checkbox('perms[' ~ perms.section ~ '][' ~ perm.section ~ '][value]', '', perm.value == 'no', 'permissions__choice permissions__choice--radio permissions__choice--no', 'no', true, null, readonly) }}
{{ input_checkbox(perm.name, '', infos[perm.category].checkNeutral(perm.value) ?? true, 'permissions__choice permissions__choice--radio permissions__choice--no', 'no', true, null, readonly) }}
</div>
<div class="permissions__choice__wrapper">
{{ input_checkbox('perms[' ~ perms.section ~ '][' ~ perm.section ~ '][value]', '', perm.value == 'never', 'permissions__choice permissions__choice--radio permissions__choice--never', 'never', true, null, readonly) }}
{{ input_checkbox(perm.name, '', infos[perm.category].checkDeny(perm.value) ?? false, 'permissions__choice permissions__choice--radio permissions__choice--never', 'never', true, null, readonly) }}
</div>
</div>
{% endfor %}

View File

@ -93,7 +93,7 @@
<div class="container">
{{ container_title('Permissions') }}
{{ permissions_table(permissions, not can_manage_perms) }}
{{ permissions_table(perms_lists, perms_infos, not can_edit_perms) }}
</div>
<button class="input__button">{{ role_info is not null ? 'Update role' : 'Create role' }}</button>

View File

@ -183,11 +183,11 @@
</form>
{% endif %}
{% if permissions is not empty %}
{% if perms_lists is defined and perms_infos is defined %}
<form method="post" action="{{ url('manage-user', {'user': user_info.id}) }}" class="container manage__user__container">
{{ container_title('Permissions for ' ~ user_info.name ~ ' (' ~ user_info.id ~ ')') }}
{{ permissions_table(permissions, not can_edit_perms) }}
{{ permissions_table(perms_lists, perms_infos, not can_edit_perms) }}
{% if can_edit_perms %}
{{ input_csrf() }}

View File

@ -81,7 +81,8 @@ msz_sched_task_func('Recount forum topics and posts.', true, function() use ($ms
msz_sched_task_sql('Clean up expired 2fa tokens.', false,
'DELETE FROM msz_auth_tfa WHERE tfa_created < NOW() - INTERVAL 15 MINUTE');
// make sure this one remains last
// very heavy stuff that should
msz_sched_task_func('Resync statistics counters.', true, function() use ($msz) {
$dbConn = $msz->getDbConn();
$counters = $msz->getCounters();
@ -139,6 +140,15 @@ msz_sched_task_func('Resync statistics counters.', true, function() use ($msz) {
}
});
msz_sched_task_func('Recalculate permissions (maybe)...', false, function() use ($msz) {
$needsRecalc = $msz->getConfig()->getBoolean('perms.needsRecalc');
if(!$needsRecalc)
return;
$msz->getConfig()->removeValues('perms.needsRecalc');
$msz->getPerms()->precalculatePermissions($msz->getForum());
});
echo 'Running ' . count($schedTasks) . ' tasks...' . PHP_EOL;
$dbConn = $msz->getDbConn();

8
tools/recalc-perms Executable file
View File

@ -0,0 +1,8 @@
#!/usr/bin/env php
<?php
namespace Misuzu;
require_once __DIR__ . '/../misuzu.php';
$msz->getConfig()->removeValues('perms.needsRecalc');
$msz->getPerms()->precalculatePermissions($msz->getForum());