feat: add moderator role system

This commit is contained in:
Floorb 2023-05-13 21:19:35 -04:00
parent c4d653b13f
commit 799de10f50
14 changed files with 76 additions and 211 deletions

View file

@ -5,11 +5,9 @@ return array (
array ( array (
'title' => 'PonePaste', 'title' => 'PonePaste',
'description' => 'PonePaste can store green', 'description' => 'PonePaste can store green',
'baseurl' => 'ponepaste.local/',
'keywords' => '', 'keywords' => '',
'site_name' => 'PonePaste', 'site_name' => 'PonePaste',
'email' => '', 'email' => ''
'additional_scripts' => '',
), ),
'interface' => 'interface' =>
array ( array (

View file

@ -14,54 +14,44 @@ class AbilityHelper {
} }
public function can(string $action, mixed $subject) : bool { public function can(string $action, mixed $subject) : bool {
if ($this->user && $this->user->admin) { if ($this->user !== null
&& $this->user->role == User::ROLE_ADMIN) { // Admins can do anything
return true; return true;
} }
return $this->modelToActions[$subject::class][$action]($this->user, $subject); return $this->modelToActions[$subject::class][$action]($this->user, $subject);
// $is_destructive = in_array($action, self::DESTRUCTIVE_ACTIONS);
//
// if (is_a($subject, 'PonePaste\\Models\\Paste')) {
// if (((int) $subject->visible === Paste::VISIBILITY_PRIVATE) || $is_destructive) {
// return $this->user !== null && $subject->user_id === $this->user->id;
// }
//
// if ($subject->is_hidden) {
// return false;
// }
//
// return true;
// }
//
// if (is_a($subject, 'PonePaste\\Models\\User')) {
// return !$is_destructive || ($this->user !== null && $subject->id === $this->user->id);
// }
//
// return false;
} }
private function setupAllowedActions() : void { private function setupAllowedActions() : void {
$this->modelToActions['PonePaste\\Models\\Paste'] = [ $this->modelToActions['PonePaste\\Models\\Paste'] = [
'view' => function(User | null $user, Paste $paste) { 'view' => function(User | null $user, Paste $paste) {
return ((int) $paste->visible !== Paste::VISIBILITY_PRIVATE && !$paste->is_hidden) || ($user !== null && $user->id === $paste->user_id); $publicly_visible = ((int) $paste->visible !== Paste::VISIBILITY_PRIVATE) && !$paste->is_hidden;
return $publicly_visible // Everyone can see public pastes
|| ($user !== null && $user->id === $paste->user_id) // Creators of pastes can see their own private pastes
|| $user->role >= User::ROLE_MODERATOR; // Moderators and above can see all pastes
}, },
'edit' => function(User | null $user, Paste $paste) { 'edit' => function(User | null $user, Paste $paste) {
return $user !== null && $user->id === $paste->user_id; return $user !== null
&& $user->id === $paste->user_id; // Creators of non-anonymous pastes can edit their own pastes
}, },
'hide' => function(User | null $user, Paste $paste) { 'hide' => function(User | null $user, Paste $paste) {
return $user !== null && $user->admin; return $user !== null
&& $user->role >= User::ROLE_MODERATOR; // Moderators and above can hide pastes
}, },
'delete' => function(User | null $user, Paste $paste) { 'delete' => function(User | null $user, Paste $paste) {
return $user !== null && $user->id === $paste->user_id; return $user !== null
&& ($user->id === $paste->user_id // Creators of pastes can delete their own pastes
|| $user->role >= User::ROLE_ADMIN); // Admins can delete all pastes
} }
]; ];
$this->modelToActions['PonePaste\\Models\\User'] = [ $this->modelToActions['PonePaste\\Models\\User'] = [
'view' => function(User | null $user, User $subject) { 'view' => function(User | null $user, User $subject) {
return true; return true; // Everyone can view users
}, },
'edit' => function(User | null $user, User $subject) { 'edit' => function(User | null $user, User $subject) {
return $user !== null && $user->id === $subject->id; return $user !== null
&& $user->id === $subject->id; // Users can edit their own profiles
}, },
]; ];
} }

View file

@ -4,6 +4,9 @@ namespace PonePaste\Models;
use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Model;
class User extends Model { class User extends Model {
public const ROLE_MODERATOR = 1;
public const ROLE_ADMIN = 2;
protected $table = 'users'; protected $table = 'users';
protected $fillable = [ protected $fillable = [
'username', 'password', 'recovery_code_hash' 'username', 'password', 'recovery_code_hash'

View file

@ -249,10 +249,8 @@ $site_info = getSiteInfo();
$global_site_info = $site_info['site_info']; $global_site_info = $site_info['site_info'];
$row = $site_info['site_info']; $row = $site_info['site_info'];
$title = trim($row['title']); $title = trim($row['title']);
$baseurl = paste_protocol() . rtrim(trim($row['baseurl']), '/');
$site_name = trim($row['site_name']); $site_name = trim($row['site_name']);
$email = trim($row['email']); $email = trim($row['email']);
$additional_scripts = trim($row['additional_scripts']);
// Setup theme // Setup theme
$default_theme = 'bulma'; $default_theme = 'bulma';

View file

@ -147,7 +147,7 @@ function truncate(string $input, int $maxWords, int $maxChars) : string {
} }
function embedView($paste_id, $p_title, $content, $title) : bool { function embedView($paste_id, $p_title, $content, $title) : bool {
global $baseurl; $baseurl = pp_site_url();
$stats = false; $stats = false;
if ($content) { if ($content) {
@ -238,6 +238,10 @@ function paste_protocol() : string {
return !empty($_SERVER['HTTPS']) ? 'https://' : 'http://'; return !empty($_SERVER['HTTPS']) ? 'https://' : 'http://';
} }
function pp_site_url() : string {
return paste_protocol() . $_SERVER['HTTP_HOST'];
}
/* get rid of unintended wildcards in a parameter to LIKE queries; not a security issue, just unexpected behaviour. */ /* get rid of unintended wildcards in a parameter to LIKE queries; not a security issue, just unexpected behaviour. */
function escapeLikeQuery(string $query) : string { function escapeLikeQuery(string $query) : string {
return str_replace(['\\', '_', '%'], ['\\\\', '\\_', '\\%'], $query); return str_replace(['\\', '_', '%'], ['\\\\', '\\_', '\\%'], $query);

View file

@ -18,7 +18,7 @@ function updateAdminHistory(User $admin, int $action) : void {
$log->save(); $log->save();
} }
if ($current_user === null || !$current_user->admin) { if ($current_user === null || $current_user->role < User::ROLE_MODERATOR) {
header('Location: ..'); header('Location: ..');
die(); die();
} }
@ -37,4 +37,14 @@ if (isset($_GET['logout'])) {
exit(); exit();
} }
function checkAdminAccess(int $role) {
global $current_user;
if ($current_user === null || $current_user->role < $role) {
flashError('You do not have access to this page.');
header('Location: /admin/');
die();
}
}
$flashes = getFlashes(); $flashes = getFlashes();

View file

@ -1,10 +1,13 @@
<?php <?php
use PonePaste\Models\AdminLog; use PonePaste\Models\AdminLog;
use PonePaste\Models\User;
define('IN_PONEPASTE', 1); define('IN_PONEPASTE', 1);
require_once('common.php'); require_once('common.php');
checkAdminAccess(User::ROLE_ADMIN);
const CONFIG_FILE_PATH = '../../config/site.php'; const CONFIG_FILE_PATH = '../../config/site.php';
function updateConfiguration(string $path, array $new_config) : void { function updateConfiguration(string $path, array $new_config) : void {
@ -33,11 +36,9 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$new_site_info = [ $new_site_info = [
'title' => trim($data['title']), 'title' => trim($data['title']),
'description' => trim($data['description']), 'description' => trim($data['description']),
'baseurl' => trim($data['baseurl']),
'keywords' => trim($data['keywords']), 'keywords' => trim($data['keywords']),
'site_name' => trim($data['site_name']), 'site_name' => trim($data['site_name']),
'email' => trim($data['email']), 'email' => trim($data['email'])
'additional_scripts' => trim($data['additional_scripts'])
]; ];
$current_config['site_info'] = $new_site_info; $current_config['site_info'] = $new_site_info;
@ -167,22 +168,6 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
</div> </div>
</div> </div>
<div class="form-group">
<label class="col-sm-2 control-label form-label" for="site_info_baseurl">Domain
name</label>
<div class="col-sm-1" style="padding:5px;">
<span class="badge">
<?= !empty($_SERVER['HTTPS']) ? 'https://' : 'http://' ?>;
</span>
</div>
<div class="col-sm-5">
<input type="text" class="form-control" name="site_info[baseurl]"
id="site_info_baseurl"
placeholder="eg: ponepaste.org (no trailing slash)"
value="<?= pp_html_escape($current_site_info['baseurl']); ?>">
</div>
</div>
<div class="form-group"> <div class="form-group">
<label class="col-sm-2 control-label form-label" <label class="col-sm-2 control-label form-label"
for="site_info_description">Site Description</label> for="site_info_description">Site Description</label>
@ -214,18 +199,6 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
</div> </div>
</div> </div>
<div class="form-group">
<label class="col-sm-2 control-label form-label"
for="site_info_additional_scripts">Additional Site
Scripts</label>
<div class="col-sm-10">
<textarea class="form-control" id="additional_scripts"
id="site_info_additional_scripts"
name="site_info[additional_scripts]"
rows="8"><?= pp_html_escape($current_site_info['title']); ?></textarea>
</div>
</div>
<input type="hidden" name="action" value="site_info"/> <input type="hidden" name="action" value="site_info"/>
<div class="form-group"> <div class="form-group">

View file

@ -15,7 +15,7 @@ function updateAdminHistory(User $admin, int $action) : void {
$log->save(); $log->save();
} }
if ($current_user === null || !$current_user->admin) { if ($current_user === null || $current_user->role < User::ROLE_MODERATOR) {
header('Location: ..'); header('Location: ..');
die(); die();
} }

View file

@ -1,11 +1,14 @@
<?php <?php
$menu_options = [
['name' => 'Dashboard', 'icon' => 'fa-home', 'path' => '/admin/dashboard.php'], use PonePaste\Models\User;
['name' => 'Configuration', 'icon' => 'fa-cogs', 'path' => '/admin/configuration.php'],
['name' => 'Admin Password', 'icon' => 'fa-user', 'path' => '/admin/admin.php'], $menu_options = [
['name' => 'Reports', 'icon' => 'fa-flag', 'path' => '/admin/reports.php'], ['name' => 'Dashboard', 'icon' => 'fa-home', 'path' => '/admin/dashboard.php', 'access' => User::ROLE_MODERATOR],
['name' => 'Pastes', 'icon' => 'fa-clipboard', 'path' => '/admin/pastes.php'], ['name' => 'Configuration', 'icon' => 'fa-cogs', 'path' => '/admin/configuration.php', 'access' => User::ROLE_ADMIN],
['name' => 'Users', 'icon' => 'fa-users', 'path' => '/admin/users.php'] ['name' => 'Admin Password', 'icon' => 'fa-user', 'path' => '/admin/admin.php', 'access' => User::ROLE_MODERATOR],
['name' => 'Reports', 'icon' => 'fa-flag', 'path' => '/admin/reports.php', 'access' => User::ROLE_MODERATOR],
['name' => 'Pastes', 'icon' => 'fa-clipboard', 'path' => '/admin/pastes.php', 'access' => User::ROLE_ADMIN],
['name' => 'Users', 'icon' => 'fa-users', 'path' => '/admin/users.php', 'access' => User::ROLE_ADMIN]
]; ];
$current_path = $_SERVER['PHP_SELF']; $current_path = $_SERVER['PHP_SELF'];
?> ?>
@ -13,12 +16,14 @@
<div class="col-md-12"> <div class="col-md-12">
<ul class="panel quick-menu clearfix"> <ul class="panel quick-menu clearfix">
<?php foreach ($menu_options as $option): ?> <?php foreach ($menu_options as $option): ?>
<li class="col-xs-3 col-sm-2 col-md-1 <?= ($option['path'] === $current_path) ? 'menu-active' : '' ?>"> <?php if ($current_user->role >= $option['access']): ?>
<a href="<?= $option['path']; ?>"> <li class="col-xs-3 col-sm-2 col-md-1 <?= ($option['path'] === $current_path) ? 'menu-active' : '' ?>">
<i class="fa <?= $option['icon']; ?>"></i> <a href="<?= $option['path']; ?>">
<?= $option['name']; ?> <i class="fa <?= $option['icon']; ?>"></i>
</a> <?= $option['name']; ?>
</li> </a>
</li>
<?php endif; ?>
<?php endforeach; ?> <?php endforeach; ?>
</ul> </ul>
</div> </div>

View file

@ -3,6 +3,9 @@ define('IN_PONEPASTE', 1);
require_once(__DIR__ . '/common.php'); require_once(__DIR__ . '/common.php');
use PonePaste\Models\Paste; use PonePaste\Models\Paste;
use PonePaste\Models\User;
checkAdminAccess(User::ROLE_ADMIN);
list($per_page, $current_page) = pp_setup_pagination(); list($per_page, $current_page) = pp_setup_pagination();

View file

@ -5,14 +5,13 @@ use PonePaste\Models\User;
define('IN_PONEPASTE', 1); define('IN_PONEPASTE', 1);
require_once(__DIR__ . '/common.php'); require_once(__DIR__ . '/common.php');
checkAdminAccess(User::ROLE_ADMIN);
list($per_page, $current_page) = pp_setup_pagination(); list($per_page, $current_page) = pp_setup_pagination();
$total_users = User::count(); $total_users = User::count();
$all_users = User::limit($per_page)->offset($current_page * $per_page)->get(); $all_users = User::limit($per_page)->offset($current_page * $per_page)->get();
?> ?>
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en"> <html lang="en">
<head> <head>
@ -24,7 +23,6 @@ $all_users = User::limit($per_page)->offset($current_page * $per_page)->get();
<link href="css/datatables.min.css" rel="stylesheet" type="text/css"/> <link href="css/datatables.min.css" rel="stylesheet" type="text/css"/>
</head> </head>
<body> <body>
<div id="top" class="clearfix"> <div id="top" class="clearfix">
<!-- Start App Logo --> <!-- Start App Logo -->
<div class="applogo"> <div class="applogo">

View file

@ -2,6 +2,7 @@
/* prevent inclusion of arbitrary files */ /* prevent inclusion of arbitrary files */
use PonePaste\Models\Report; use PonePaste\Models\Report;
use PonePaste\Models\User;
$template_candidates = scandir(__DIR__); $template_candidates = scandir(__DIR__);
if (!in_array($page_template . '.php', $template_candidates)) { if (!in_array($page_template . '.php', $template_candidates)) {
@ -81,7 +82,7 @@ $flashes = getFlashes();
</span> </span>
<span>Events</span> <span>Events</span>
</a> </a>
<?php if ($current_user !== null && $current_user->admin): ?> <?php if ($current_user !== null && $current_user->role >= User::ROLE_MODERATOR): ?>
<?php $has_reports = Report::where(['open' => true])->count() > 0; ?> <?php $has_reports = Report::where(['open' => true])->count() > 0; ?>
<a class="button navbar-item mx-2" href="/admin" <?= $has_reports ? 'style="color: red;"' : '' ?>> <a class="button navbar-item mx-2" href="/admin" <?= $has_reports ? 'style="color: red;"' : '' ?>>
<span class="icon has-text-info"> <span class="icon has-text-info">

View file

@ -4,122 +4,6 @@
<div class="bd-main-container container"> <div class="bd-main-container container">
<div class="bd-duo"> <div class="bd-duo">
<div class="bd-lead"> <div class="bd-lead">
<!-- Guests -->
<?php if ($site_disable_guests) { // Site permissions
?>
<section class="hero is-medium">
<div class="">
<article class="message is-info">
<div class="message-header">
<p>Site News:</p>
</div>
<div class="message-body">
<div class="content is-normal">
<ul>
<li>Ponepaste has now a favorite system. You can now favorite pastes and
bookmark them on your user page under "Favorites"</li>
<li>Report function and UI has been overhauled.</li>
<li>The archive page has now been updated, tags are now clickable for a
faster search.</li>
<li>Tags UI has been overhauled. Tags containing "SAFE" and "NSFW" will
appear green and red.</li>
<li>When Creating paste the tag input box has been updated with a new visual
style.</li>
<li>Tags are now being canonized, if you see your tags change, it's just the
admin working in the background</li>
</ul>
</div>
</div>
</article>
<div class="container">
<div class="columns is-multiline is-mobile">
<div class="column">
<div class="panel-body">
<div class="list-widget pagination-content">
<?php
$res = getRandomPastes($conn, 10);
foreach ($res
as $index => $row) {
$title = Trim($row['title']);
$titlehov = ($row['title']);
$p_member = Trim($row['member']);
$p_id = Trim($row['id']);
$p_date = Trim($row['date']);
$p_time = Trim($row['now_time']);
$nowtime = time();
$oldtime = $p_time;
$title = truncate($title, 24, 60);
?>
<p class="no-margin">
<?php
if (PP_MOD_REWRITE) {
echo '<header class="bd-category-header my-1">
<a data-tooltip="' . $titlehov . '" href="' . $p_id . '" title="' . $title . '">' . $title . ' </a>
<a class="icon is-pulled-right has-tooltip-arrow has-tooltip-left-mobile has-tooltip-bottom-desktop has-tooltip-left-until-widescreen" data-tooltip="' . $p_time . '">
<i class="far fa-clock has-text-grey" aria-hidden="true"></i>
</a>
<p class="subtitle is-7">' . 'by ' . '
<i><a href="https://Ponepaste.org/user/' . $p_member . '">' . $p_member . '</a></i>
</p>' .
'</header>';
} else {
echo '<a href="' . $p_id . '" title="' . $titlehov . '">' . ucfirst($title) . '</a>';
}
}
?>
</p>
</div>
</div>
</div>
<div class="column">
<div class="panel-body">
<div class="list-widget pagination-content">
<?php
$res = getRandomPastes($conn, 10);
foreach ($res
as $index => $row) {
$title = Trim($row['title']);
$titlehov = ($row['title']);
$p_member = Trim($row['member']);
$p_id = Trim($row['id']);
$p_date = Trim($row['date']);
$p_time = Trim($row['now_time']);
$nowtime = time();
$oldtime = $p_time;
$title = truncate($title, 24, 60);
?>
<p class="no-margin">
<?php
if (PP_MOD_REWRITE) {
echo '<header class="bd-category-header my-1">
<a data-tooltip="' . $titlehov . '" href="' . $p_id . '" title="' . $title . '">' . $title . ' </a>
<a class="icon is-pulled-right has-tooltip-arrow has-tooltip-left-mobile has-tooltip-bottom-desktop has-tooltip-left-until-widescreen" data-tooltip="' . $p_time . '">
<i class="far fa-clock has-text-grey" aria-hidden="true"></i>
</a>
<p class="subtitle is-7">' . 'by ' . '
<i><a href="https://Ponepaste.org/user/' . $p_member . '">' . $p_member . '</a></i>
</p>' .
'</header>';
} else {
echo '<a href="' . $p_id . '" title="' . $titlehov . '">' . ucfirst($title) . '</a>';
}
}
?>
</p>
</div>
</div>
</div>
</div>
</div>
</div>
</section>
<?php } else { ?>
<!-- Paste Panel --> <!-- Paste Panel -->
<?php if ($_SERVER['REQUEST_METHOD'] == 'POST') { <?php if ($_SERVER['REQUEST_METHOD'] == 'POST') {
if (isset($error)) { ?> if (isset($error)) { ?>
@ -138,12 +22,11 @@
<!-- Title --> <!-- Title -->
<div class="level-item is-pulled-left" style="margin-right: 5px;"> <div class="level-item is-pulled-left" style="margin-right: 5px;">
<p class="control has-icons-left"> <p class="control has-icons-left">
<input type="text" class="input" name="title" onchange="getFileName()" <input type="text" class="input" name="title" placeholder="Title"
placeholder="Title"
value="<?php echo (isset($_POST['title'])) ? pp_html_escape($_POST['title']) : ''; ?>"> value="<?php echo (isset($_POST['title'])) ? pp_html_escape($_POST['title']) : ''; ?>">
<span class="icon is-small is-left"> <span class="icon is-small is-left">
<i class="fa fa-font"></i> <i class="fa fa-font"></i>
</span> </span>
</p> </p>
</div> </div>
<!-- Format --> <!-- Format -->
@ -165,7 +48,7 @@
</div> </div>
<div class="level-item is-pulled-left mx-1"> <div class="level-item is-pulled-left mx-1">
<a class="button" onclick="highlight(document.getElementById('code')); return false;"><i <a class="button" onclick="highlight(document.getElementById('code')); return false;"><i
class="fas fa-indent"></i>&nbsp;Highlight</a> class="fas fa-indent"></i>&nbsp;Highlight</a>
</div> </div>
<div class="level-item is-pulled-left mx-1"> <div class="level-item is-pulled-left mx-1">
<input class="button is-info" type="submit" name="submit" id="submit" value="Paste"/> <input class="button is-info" type="submit" name="submit" id="submit" value="Paste"/>
@ -218,11 +101,11 @@
?> ?>
<select name="paste_expire_date"> <select name="paste_expire_date">
<?= optionsForSelect( <?= optionsForSelect(
['Never', 'View Once', '10 minutes', '1 hour', '1 day', '1 week', '2 weeks', '1 month'], ['Never', 'View Once', '10 minutes', '1 hour', '1 day', '1 week', '2 weeks', '1 month'],
['N', 'self', '0Y0M0DT0H10M', '1H', '1D', '1W', '2W', '1M'], ['N', 'self', '0Y0M0DT0H10M', '1H', '1D', '1W', '2W', '1M'],
$post_expire $post_expire
); ?> ); ?>
</select> </select>
</div> </div>
</div> </div>
@ -319,7 +202,6 @@
</form> </form>
</div> </div>
</div> </div>
<?php } ?>
</div> </div>
</main> </main>

View file

@ -127,7 +127,7 @@ $selectedloader = "$bg[$i]"; // set variable equal to which random filename was
title="Full Screen"></i></a> title="Full Screen"></i></a>
<div class="panel-embed my-5 is-hidden"> <div class="panel-embed my-5 is-hidden">
<input type="text" class="input has-background-white-ter has-text-grey" <input type="text" class="input has-background-white-ter has-text-grey"
value='<?php echo '<script src="' . $protocol . $baseurl; value='<?php echo '<script src="' . pp_site_url() . '/';
if (PP_MOD_REWRITE) { if (PP_MOD_REWRITE) {
echo 'embed/'; echo 'embed/';
} else { } else {