chore: move paste actions to paste_action.php to require admin authentication

This commit is contained in:
Floorb 2023-07-05 03:22:09 -04:00
parent 36abc1725e
commit 265b85d489
9 changed files with 106 additions and 66 deletions

View file

@ -7,15 +7,19 @@ class AdminLog extends Model {
public const ACTION_LOGIN = 0;
public const ACTION_FAIL_LOGIN = 1;
public const ACTION_EDIT_CONFIG = 2;
public const ACTION_HIDE_PASTE = 3;
public const ACTION_BLANK_PASTE = 4;
public const ACTION_NAMES = [
'Login',
'Failed Login',
'Edit Config'
'Edit Config',
'Hide Paste',
'Blank Paste'
];
protected $table = 'admin_logs';
protected $fillable = ['user_id', 'action', 'ip', 'time'];
protected $fillable = ['user_id', 'action', 'ip', 'time', 'message'];
public $timestamps = false;

View file

@ -33,12 +33,16 @@ function urlForPage($page = '') : string {
return (isset($_SERVER['HTTPS']) ? 'https://' : 'http://') . $_SERVER['HTTP_HOST'] . rtrim(dirname($_SERVER['PHP_SELF']), '/\\') . '/' . $page;
}
function urlForPaste(Paste $paste) : string {
if (PP_MOD_REWRITE) {
return "/{$paste->id}";
function urlForPaste(int | Paste $paste) : string {
if (!is_int($paste)) {
$paste = $paste->id;
}
return "/paste.php?id={$paste->id}";
if (PP_MOD_REWRITE) {
return "/{$paste}";
}
return "/paste.php?id={$paste}";
}
function urlForReport(Paste $paste) : string {

View file

@ -1,6 +1,8 @@
<?php
use Illuminate\Database\Eloquent\Collection;
use PonePaste\Models\AdminLog;
use PonePaste\Models\Paste;
use PonePaste\Models\User;
function tagsToHtml(array | Collection $tags) : string {
$output = "";
@ -332,4 +334,15 @@ function pp_setup_pagination() : array {
function pp_output_paginator(int $per_page, int $current_page) : void {
}
function updateAdminHistory(User $admin, int $action, string $message = null) : void {
$log = new AdminLog([
'user_id' => $admin->id,
'action' => $action,
'ip' => $_SERVER['REMOTE_ADDR'],
'message' => $message
]);
$log->save();
}

View file

@ -5,25 +5,20 @@ if (!defined('IN_PONEPASTE')) {
require_once('../../includes/common.php');
use PonePaste\Models\AdminLog;
use PonePaste\Models\User;
function updateAdminHistory(User $admin, int $action) : void {
$log = new AdminLog([
'user_id' => $admin->id,
'action' => $action,
'ip' => $_SERVER['REMOTE_ADDR']
]);
$log->save();
}
if ($current_user === null || $current_user->role < User::ROLE_MODERATOR) {
header('Location: ..');
die();
}
if (!isset($_SESSION['admin_login'])) {
// this is a hack, paste_id is set when POSTing to admin/paste_action.php, which we can only arrive at from a paste page
if (isset($_POST['paste_id'])) {
flashError('You must authenticate to perform that action.');
$_SESSION['redirect_back'] = urlForPaste($_POST['paste_id']);
}
header('Location: .');
exit();
}

View file

@ -231,6 +231,7 @@ $is_admin = $current_user->role >= User::ROLE_ADMIN;
<td>Username</td>
<td>Date</td>
<td>Action</td>
<td>Information</td>
<td>IP Address</td>
</tr>
</thead>
@ -240,6 +241,7 @@ $is_admin = $current_user->role >= User::ROLE_ADMIN;
<td><?= pp_html_escape($entry->user->username); ?></td>
<td><?= pp_html_escape($entry->time); ?></td>
<td><?= pp_html_escape(AdminLog::ACTION_NAMES[$entry->action]); ?></td>
<td><?= !empty($entry->message) ? pp_html_escape($entry->message) : '[none]' ?></td>
<td><?= $is_admin ? pp_html_escape($entry->ip) : '[masked]' ?></td>
</tr>
<?php endforeach; ?>

View file

@ -5,16 +5,6 @@ require_once(__DIR__ . '/../../includes/common.php');
use PonePaste\Models\User;
use PonePaste\Models\AdminLog;
function updateAdminHistory(User $admin, int $action) : void {
$log = new AdminLog([
'user_id' => $admin->id,
'action' => $action,
'ip' => $_SERVER['REMOTE_ADDR']
]);
$log->save();
}
if ($current_user === null || $current_user->role < User::ROLE_MODERATOR) {
header('Location: ..');
die();
@ -25,11 +15,18 @@ if (isset($_SESSION['admin_login']) && $_SESSION['admin_login']) {
exit();
}
$flashes = getFlashes();
if ($_SERVER['REQUEST_METHOD'] == 'POST') {
if (pp_password_verify($_POST['password'], $current_user->admin_password_hash)) {
updateAdminHistory($current_user, AdminLog::ACTION_LOGIN);
$_SESSION['admin_login'] = true;
header("Location: dashboard.php");
if (isset($_SESSION['redirect_back'])) {
flashSuccess('You have been logged in. Please try your action again.');
header('Location: ' . $_SESSION['redirect_back']);
} else {
header("Location: dashboard.php");
}
exit();
} else {
updateAdminHistory($current_user, AdminLog::ACTION_FAIL_LOGIN);
@ -60,6 +57,7 @@ if ($_SERVER['REQUEST_METHOD'] == 'POST') {
echo $msg;
}
?>
<?php outputFlashes($flashes); ?>
<form action="." method="post">
<div class="top">
<h1>PonePaste Admin Authentication</h1>

View file

@ -0,0 +1,60 @@
<?php
define('IN_PONEPASTE', 1);
require_once(__DIR__ . '/common.php');
use PonePaste\Models\AdminLog;
use PonePaste\Models\Paste;
if (empty($_POST['paste_id'])) {
echo "Error: No paste ID specified.";
die();
}
$paste = Paste::find((int) $_POST['paste_id']);
if (!$paste) {
echo "Error: Paste not found.";
die();
}
if (isset($_POST['hide'])) {
if (!can('hide', $paste)) {
flashError('You do not have permission to hide this paste.');
} else {
$is_hidden = !$paste->is_hidden;
if ($is_hidden) {
$paste->reports()->update(['open' => false]);
}
$paste->is_hidden = $is_hidden;
$paste->save();
$redis->del('ajax_pastes'); /* Expire from Redis so it doesn't show up anymore */
updateAdminHistory($current_user, AdminLog::ACTION_HIDE_PASTE, 'Paste ' . $paste->id . ' ' . ($is_hidden ? 'hidden' : 'unhidden') . '.');
flashSuccess('Paste ' . ($is_hidden ? 'hidden' : 'unhidden') . '.');
}
header('Location: ' . urlForPaste($paste));
die();
} elseif (isset($_POST['blank'])) {
if (!can('blank', $paste)) {
flashError('You do not have permission to blank this paste.');
} else {
$paste->content = '';
$paste->title = 'Removed by moderator';
$paste->tags()->detach();
$paste->save();
$redis->del('ajax_pastes'); /* Expire from Redis so it doesn't show up anymore */
updateAdminHistory($current_user, AdminLog::ACTION_BLANK_PASTE, 'Paste ' . $paste->id . 'blanked.');
flashSuccess('Paste contents blanked.');
}
header('Location: ' . urlForPaste($paste));
die();
} else {
flashError('Internal Error: No action specified.');
header('Location: ' . urlForPaste($paste));
}

View file

@ -102,43 +102,6 @@ if (isset($_POST['delete'])) {
die();
}
if (isset($_POST['hide'])) {
if (!can('hide', $paste)) {
$error = 'You do not have permission to hide this paste.';
goto Not_Valid_Paste;
}
$is_hidden = !$paste->is_hidden;
if ($is_hidden) {
$paste->reports()->update(['open' => false]);
}
$paste->is_hidden = $is_hidden;
$paste->save();
$redis->del('ajax_pastes'); /* Expire from Redis so it doesn't show up anymore */
flashSuccess('Paste ' . ($is_hidden ? 'hidden' : 'unhidden') . '.');
header('Location: ' . urlForPaste($paste));
die();
}
if (isset($_POST['blank'])) {
if (!can('blank', $paste)) {
$error = 'You do not have permission to blank this paste.';
goto Not_Valid_Paste;
}
$paste->content = '';
$paste->title = 'Removed by moderator';
$paste->tags()->detach();
$paste->save();
$redis->del('ajax_pastes'); /* Expire from Redis so it doesn't show up anymore */
flashSuccess('Paste contents blanked.');
header('Location: ' . urlForPaste($paste));
die();
}
/* Verify paste password */
$password_required = $p_password !== null && $p_password !== 'NONE';
$password_valid = true;

View file

@ -149,7 +149,8 @@
<?php if (can('hide', $paste)): ?>
<div class="mod-tools">
<p>Moderation Tools</p>
<form method="post">
<form action="/admin/paste_action.php" method="post">
<input type="hidden" name="paste_id" value="<?= $paste->id ?>" />
<?php if (isset($csrf_token)): ?>
<input type="hidden" name="csrf_token" value="<?= $csrf_token ?>"/>
<?php endif; ?>