misuzu/src/Perm.php
2024-01-30 23:47:02 +00:00

450 lines
23 KiB
PHP

<?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_MMMMFFFF_NNNNNNNN_LLLLLLLL_GGGGGGGG
// M -> Messages permissions
// 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_TIMINGS_VIEW = 0b00000_00000000_00000000_00000000_00000000_00000000_10000000;
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_MESSAGES_VIEW = 0b00000_00000000_00000000_00010000_00000000_00000000_00000000;
public const G_MESSAGES_SEND = 0b00000_00000000_00000000_00100000_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;
// CHAT ALLOCATION:
// 0bXXXXX_XXXXXXXX_XXXXXXXX_XXXXXXXX_XXXXXXMA_MMUMUUMT_ATATAMMM
// U -> Normal user permissions
// T -> Tenshi permissions
// M -> Moderator permissions
// A -> Administrator permissions
// no staircase here because it's a mess
public const C_USER_KICK = 0b00000_00000000_00000000_00000000_00000000_00000000_00000001;
public const C_USER_BAN = 0b00000_00000000_00000000_00000000_00000000_00000000_00000010;
//public const C_USER_SILENCE = 0b00000_00000000_00000000_00000000_00000000_00000000_00000100; // deprecated: silencing is no longer a thing
public const C_MESSAGE_BROADCAST = 0b00000_00000000_00000000_00000000_00000000_00000000_00001000;
public const C_NICK_SET_OWN = 0b00000_00000000_00000000_00000000_00000000_00000000_00010000;
public const C_NICK_SET_ANY = 0b00000_00000000_00000000_00000000_00000000_00000000_00100000;
public const C_CHANNEL_CREATE = 0b00000_00000000_00000000_00000000_00000000_00000000_01000000;
public const C_CHANNEL_SET_PERSIST = 0b00000_00000000_00000000_00000000_00000000_00000000_10000000;
public const C_CHANNEL_SET_PASSWORD = 0b00000_00000000_00000000_00000000_00000000_00000001_00000000;
public const C_CHANNEL_SET_MIN_RANK = 0b00000_00000000_00000000_00000000_00000000_00000010_00000000;
public const C_MESSAGE_SEND = 0b00000_00000000_00000000_00000000_00000000_00000100_00000000;
public const C_MESSAGE_DELETE_OWN = 0b00000_00000000_00000000_00000000_00000000_00001000_00000000;
public const C_MESSAGE_DELETE_ANY = 0b00000_00000000_00000000_00000000_00000000_00010000_00000000;
public const C_MESSAGE_EDIT_OWN = 0b00000_00000000_00000000_00000000_00000000_00100000_00000000;
public const C_MESSAGE_EDIT_ANY = 0b00000_00000000_00000000_00000000_00000000_01000000_00000000;
public const C_USER_VIEW_ADDR = 0b00000_00000000_00000000_00000000_00000000_10000000_00000000;
public const C_CHANNEL_DELETE = 0b00000_00000000_00000000_00000000_00000001_00000000_00000000;
public const C_CHANNEL_JOIN_ANY = 0b00000_00000000_00000000_00000000_00000010_00000000_00000000;
public const INFO_FOR_USER = ['global', 'user', 'chat'];
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:messages', 'global:comments', 'user:personal', 'user:manage', 'chat:general'];
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,
self::G_TIMINGS_VIEW,
],
],
'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:messages' => [
'title' => 'Private Messages Permissions',
'perms' => [
'global',
self::G_MESSAGES_VIEW,
self::G_MESSAGES_SEND,
],
],
'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,
],
],
'chat:general' => [
'title' => 'Chat Permissions',
'perms' => [
'chat',
self::C_MESSAGE_SEND,
self::C_MESSAGE_BROADCAST,
self::C_MESSAGE_EDIT_OWN,
self::C_MESSAGE_EDIT_ANY,
self::C_MESSAGE_DELETE_OWN,
self::C_MESSAGE_DELETE_ANY,
self::C_NICK_SET_OWN,
self::C_NICK_SET_ANY,
self::C_USER_KICK,
self::C_USER_BAN,
self::C_USER_VIEW_ADDR,
self::C_CHANNEL_CREATE,
self::C_CHANNEL_SET_PERSIST,
self::C_CHANNEL_SET_PASSWORD,
self::C_CHANNEL_SET_MIN_RANK,
self::C_CHANNEL_JOIN_ANY,
self::C_CHANNEL_DELETE,
],
],
];
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_TIMINGS_VIEW => 'Can view page load timings.',
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_MESSAGES_VIEW => 'Can view private messages.',
self::G_MESSAGES_SEND => 'Can send private messages.',
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.',
],
'chat' => [
self::C_USER_KICK => 'Can kick other users.',
self::C_USER_BAN => 'Can ban other users.',
//self::C_USER_SILENCE => 'Can silence other users.',
self::C_MESSAGE_BROADCAST => 'Can broadcast messages across all channels.',
self::C_NICK_SET_OWN => 'Can change own nickname.',
self::C_NICK_SET_ANY => 'Can change ANYONE\'s nickname.',
self::C_CHANNEL_CREATE => 'Can create channels.',
self::C_CHANNEL_SET_PERSIST => 'Can set channels to persist after all users leave it.',
self::C_CHANNEL_SET_PASSWORD => 'Can set passwords for own channel.',
self::C_CHANNEL_SET_MIN_RANK => 'Can set minimum requires rank to join channel.',
self::C_MESSAGE_SEND => 'Can send messages.',
self::C_MESSAGE_DELETE_OWN => 'Can delete own messages.',
self::C_MESSAGE_DELETE_ANY => 'Can delete ANY message.',
self::C_MESSAGE_EDIT_OWN => 'Can edit own messages.',
self::C_MESSAGE_EDIT_ANY => 'Can edit ANY message.',
self::C_USER_VIEW_ADDR => 'Can view IP addresses of other users.',
self::C_CHANNEL_DELETE => 'Can delete own channels.',
self::C_CHANNEL_JOIN_ANY => 'Can join any channel regardless of join requirements.',
],
];
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;
// else
if(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;
}
}