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 (
'title' => 'PonePaste',
'description' => 'PonePaste can store green',
'baseurl' => 'ponepaste.local/',
'keywords' => '',
'site_name' => 'PonePaste',
'email' => '',
'additional_scripts' => '',
'email' => ''
),
'interface' =>
array (

View file

@ -14,54 +14,44 @@ class AbilityHelper {
}
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 $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 {
$this->modelToActions['PonePaste\\Models\\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) {
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) {
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) {
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'] = [
'view' => function(User | null $user, User $subject) {
return true;
return true; // Everyone can view users
},
'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;
class User extends Model {
public const ROLE_MODERATOR = 1;
public const ROLE_ADMIN = 2;
protected $table = 'users';
protected $fillable = [
'username', 'password', 'recovery_code_hash'

View file

@ -249,10 +249,8 @@ $site_info = getSiteInfo();
$global_site_info = $site_info['site_info'];
$row = $site_info['site_info'];
$title = trim($row['title']);
$baseurl = paste_protocol() . rtrim(trim($row['baseurl']), '/');
$site_name = trim($row['site_name']);
$email = trim($row['email']);
$additional_scripts = trim($row['additional_scripts']);
// Setup theme
$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 {
global $baseurl;
$baseurl = pp_site_url();
$stats = false;
if ($content) {
@ -238,6 +238,10 @@ function paste_protocol() : string {
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. */
function escapeLikeQuery(string $query) : string {
return str_replace(['\\', '_', '%'], ['\\\\', '\\_', '\\%'], $query);

View file

@ -18,7 +18,7 @@ function updateAdminHistory(User $admin, int $action) : void {
$log->save();
}
if ($current_user === null || !$current_user->admin) {
if ($current_user === null || $current_user->role < User::ROLE_MODERATOR) {
header('Location: ..');
die();
}
@ -37,4 +37,14 @@ if (isset($_GET['logout'])) {
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();

View file

@ -1,10 +1,13 @@
<?php
use PonePaste\Models\AdminLog;
use PonePaste\Models\User;
define('IN_PONEPASTE', 1);
require_once('common.php');
checkAdminAccess(User::ROLE_ADMIN);
const CONFIG_FILE_PATH = '../../config/site.php';
function updateConfiguration(string $path, array $new_config) : void {
@ -33,11 +36,9 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$new_site_info = [
'title' => trim($data['title']),
'description' => trim($data['description']),
'baseurl' => trim($data['baseurl']),
'keywords' => trim($data['keywords']),
'site_name' => trim($data['site_name']),
'email' => trim($data['email']),
'additional_scripts' => trim($data['additional_scripts'])
'email' => trim($data['email'])
];
$current_config['site_info'] = $new_site_info;
@ -167,22 +168,6 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
</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">
<label class="col-sm-2 control-label form-label"
for="site_info_description">Site Description</label>
@ -214,18 +199,6 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
</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"/>
<div class="form-group">

View file

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

View file

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

View file

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

View file

@ -5,14 +5,13 @@ use PonePaste\Models\User;
define('IN_PONEPASTE', 1);
require_once(__DIR__ . '/common.php');
checkAdminAccess(User::ROLE_ADMIN);
list($per_page, $current_page) = pp_setup_pagination();
$total_users = User::count();
$all_users = User::limit($per_page)->offset($current_page * $per_page)->get();
?>
<!DOCTYPE html>
<html lang="en">
<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"/>
</head>
<body>
<div id="top" class="clearfix">
<!-- Start App Logo -->
<div class="applogo">

View file

@ -2,6 +2,7 @@
/* prevent inclusion of arbitrary files */
use PonePaste\Models\Report;
use PonePaste\Models\User;
$template_candidates = scandir(__DIR__);
if (!in_array($page_template . '.php', $template_candidates)) {
@ -81,7 +82,7 @@ $flashes = getFlashes();
</span>
<span>Events</span>
</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; ?>
<a class="button navbar-item mx-2" href="/admin" <?= $has_reports ? 'style="color: red;"' : '' ?>>
<span class="icon has-text-info">

View file

@ -4,122 +4,6 @@
<div class="bd-main-container container">
<div class="bd-duo">
<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 -->
<?php if ($_SERVER['REQUEST_METHOD'] == 'POST') {
if (isset($error)) { ?>
@ -138,8 +22,7 @@
<!-- Title -->
<div class="level-item is-pulled-left" style="margin-right: 5px;">
<p class="control has-icons-left">
<input type="text" class="input" name="title" onchange="getFileName()"
placeholder="Title"
<input type="text" class="input" name="title" placeholder="Title"
value="<?php echo (isset($_POST['title'])) ? pp_html_escape($_POST['title']) : ''; ?>">
<span class="icon is-small is-left">
<i class="fa fa-font"></i>
@ -319,7 +202,6 @@
</form>
</div>
</div>
<?php } ?>
</div>
</main>

View file

@ -127,7 +127,7 @@ $selectedloader = "$bg[$i]"; // set variable equal to which random filename was
title="Full Screen"></i></a>
<div class="panel-embed my-5 is-hidden">
<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) {
echo 'embed/';
} else {