Check config perms, validate boolean inputs

This commit is contained in:
Florine W. Dekker 2022-12-07 15:51:36 +01:00
parent 5a99441a0c
commit fbce900475
Signed by: FWDekker
GPG Key ID: D3DCFAA8A4560BE0
8 changed files with 63 additions and 18 deletions

View File

@ -1,7 +1,7 @@
{
"name": "fwdekker/death-notifier",
"description": "Get notified when a famous person dies.",
"version": "0.17.2", "_comment_version": "Also update version in `package.json`!",
"version": "0.17.3", "_comment_version": "Also update version in `package.json`!",
"type": "project",
"license": "MIT",
"homepage": "https://git.fwdekker.com/tools/death-notifier",

BIN
composer.lock generated

Binary file not shown.

BIN
package-lock.json generated

Binary file not shown.

View File

@ -1,6 +1,6 @@
{
"name": "death-notifier",
"version": "0.17.2", "_comment_version": "Also update version in `composer.json`!",
"version": "0.17.3", "_comment_version": "Also update version in `composer.json`!",
"description": "Get notified when a famous person dies.",
"author": "Florine W. Dekker",
"browser": "dist/bundle.js",

View File

@ -3,33 +3,37 @@
# TODO: Add i18n
[admin]
# bcrypt hash of password to use the CLI of `api.php`. If set to its default value, or if empty, the CLI is disabled
# bcrypt hash of password to use the CLI of `api.php`. If set to its default value, or if empty, the CLI is disabled.
cli_secret = REPLACE THIS WITH A SECRET VALUE
[database]
# Relative path to SQLite database
# Relative path to SQLite database.
filename = .death-notifier.db
[logger]
# File to store logs in
# File to store logs in.
file = .death-notifier.log
# Log level. See https://seldaek.github.io/monolog/doc/01-usage.html#log-levels
level = 200
[mail]
# Host name of SMTP server to send mail through
# Host name of SMTP server to send mail through.
host = TODO
# Port of SMTP server to send mail through
# Port of SMTP server to send mail through.
port = TODO
# Username to authenticate with at SMTP server
# Username to authenticate with at SMTP server.
username = TODO
# Password to authenticate with at SMTP server
# Password to authenticate with at SMTP server.
password = TODO
# Name to show to recipient
# Name to show to recipient.
from_name = TODO
[security]
# `true` if and only if insecure file permissions for config files should be tolerated.
allow_config_insecure_permissions = false
[server]
# The path to the main page. Going to this path in your browser should show the contents of `index.html`
# The path to the directory containing the site's main page.
base_path = https://example.com/death-notifier/
# The message to display at the top of all pages. An empty string hides the message
# The message to display at the top of all pages. A blank string hides the message.
global_message =

View File

@ -2,6 +2,7 @@
namespace com\fwdekker\deathnotifier;
use Error;
use InvalidArgumentException;
@ -40,7 +41,6 @@ class Config
*/
private static function read_config(): array
{
// TODO: Check permissions, return `null` if too permissive
$config = parse_ini_file("config.default.ini.php", process_sections: true, scanner_mode: INI_SCANNER_TYPED);
if ($config === false) throw new InvalidArgumentException("Invalid `config.default.ini.php` file.");
@ -49,6 +49,20 @@ class Config
if ($config_custom === false) throw new InvalidArgumentException("Invalid `config.ini.php` file.");
$config = array_replace_recursive($config, $config_custom);
// Check file permissions
if (!$config["security"]["allow_config_insecure_permissions"]) {
$perms = fileperms("config.ini.php");
if ($perms === false)
throw new Error("Failed to read file permissions for `config.ini.php` even though the file exists.");
$perms = substr(decoct($perms), 3);
if ($perms !== "600")
throw new InvalidArgumentException(
"Insecure file permissions for `config.ini.php`. " .
"Should be 600 but was $perms."
);
}
}
return $config;

View File

@ -3,6 +3,7 @@
namespace com\fwdekker\deathnotifier\user;
use com\fwdekker\deathnotifier\Action;
use com\fwdekker\deathnotifier\validator\IsBooleanRule;
use com\fwdekker\deathnotifier\validator\IsValidCsrfTokenRule;
use com\fwdekker\deathnotifier\UnexpectedException;
use com\fwdekker\deathnotifier\validator\IsSetRule;
@ -51,18 +52,17 @@ class ToggleNotificationsAction extends Action
(new SessionRuleSet(validate_logged_in: true))->check($_SESSION);
(new RuleSet([
"token" => [new IsValidCsrfTokenRule()],
// TODO: Do we need an IsBooleanRule? (check the `== true` below)
"enable_notifications" => [new IsSetRule()],
"enable_notifications" => [new IsBooleanRule()],
]))->check($inputs);
$this->user_list->transaction(function () use ($inputs) {
$user_data = $this->user_list->get_user_by_uuid($_SESSION["uuid"]);
if ($user_data === null)
throw new UnexpectedException("Failed to retrieve user data. Refresh the page and try again.");
if ($user_data["email_verification_token"] === null)
throw new InvalidInputException("Please verify your email address before enabling notifications.");
if ($inputs["enable_notifications"] && $user_data["email_verification_token"] !== null)
throw new InvalidInputException("Please verify your email address before toggling notifications.");
$this->user_list->set_notifications_enabled($_SESSION["uuid"], $inputs["enable_notifications"] == true);
$this->user_list->set_notifications_enabled($_SESSION["uuid"], $inputs["enable_notifications"]);
});
return null;

View File

@ -0,0 +1,27 @@
<?php
namespace com\fwdekker\deathnotifier\validator;
/**
* Validates that the input is a boolean.
*/
class IsBooleanRule extends Rule
{
/**
* Validates that the input is a boolean.
*
* @param array<int|string, mixed> $inputs the list of inputs in which the value at {@see $key} should be checked
* @param string $key the key in {@see $inputs} of the input to check
* @return void if the checked input is a boolean
* @throws InvalidInputException if the checked input is not a boolean
*/
public function check(array $inputs, string $key): void
{
if (!isset($inputs[$key]) || !is_bool($inputs[$key]))
throw new InvalidInputException(
$this->override_message ?? "Field '" . htmlentities($key) . "' must be a boolean.",
$key
);
}
}