diff --git a/composer.json b/composer.json index 6468661..e09277f 100644 --- a/composer.json +++ b/composer.json @@ -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", diff --git a/composer.lock b/composer.lock index 3250f41..c8dccbd 100644 Binary files a/composer.lock and b/composer.lock differ diff --git a/package-lock.json b/package-lock.json index f4561e3..a40223f 100644 Binary files a/package-lock.json and b/package-lock.json differ diff --git a/package.json b/package.json index 75b52bc..b836d0a 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/src/main/config.default.ini.php b/src/main/config.default.ini.php index cfaf326..1dea51f 100644 --- a/src/main/config.default.ini.php +++ b/src/main/config.default.ini.php @@ -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 = diff --git a/src/main/php/com/fwdekker/deathnotifier/Config.php b/src/main/php/com/fwdekker/deathnotifier/Config.php index 0d9beec..18ef6d4 100644 --- a/src/main/php/com/fwdekker/deathnotifier/Config.php +++ b/src/main/php/com/fwdekker/deathnotifier/Config.php @@ -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; diff --git a/src/main/php/com/fwdekker/deathnotifier/user/ToggleNotificationsAction.php b/src/main/php/com/fwdekker/deathnotifier/user/ToggleNotificationsAction.php index 362b194..4558b1b 100644 --- a/src/main/php/com/fwdekker/deathnotifier/user/ToggleNotificationsAction.php +++ b/src/main/php/com/fwdekker/deathnotifier/user/ToggleNotificationsAction.php @@ -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; diff --git a/src/main/php/com/fwdekker/deathnotifier/validator/IsBooleanRule.php b/src/main/php/com/fwdekker/deathnotifier/validator/IsBooleanRule.php new file mode 100644 index 0000000..ed55095 --- /dev/null +++ b/src/main/php/com/fwdekker/deathnotifier/validator/IsBooleanRule.php @@ -0,0 +1,27 @@ + $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 + ); + } +}