Restructure validation logic
This commit is contained in:
parent
732298bf09
commit
12e7437335
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"name": "fwdekker/death-notifier",
|
||||
"description": "Get notified when a famous person dies.",
|
||||
"version": "0.19.0", "_comment_version": "Also update version in `package.json`!",
|
||||
"version": "0.19.1", "_comment_version": "Also update version in `package.json`!",
|
||||
"type": "project",
|
||||
"license": "MIT",
|
||||
"homepage": "https://git.fwdekker.com/tools/death-notifier",
|
||||
|
|
Binary file not shown.
Binary file not shown.
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "death-notifier",
|
||||
"version": "0.19.0", "_comment_version": "Also update version in `composer.json`!",
|
||||
"version": "0.19.1", "_comment_version": "Also update version in `composer.json`!",
|
||||
"description": "Get notified when a famous person dies.",
|
||||
"author": "Florine W. Dekker",
|
||||
"browser": "dist/bundle.js",
|
||||
|
|
|
@ -35,5 +35,7 @@ allow_config_insecure_permissions = false
|
|||
[server]
|
||||
# The path to the directory containing the site's main page.
|
||||
base_path = https://example.com/death-notifier/
|
||||
# The path at which users can report bugs, or an empty string if there is no such path.
|
||||
issue_path =
|
||||
# The message to display at the top of all pages. A blank string hides the message.
|
||||
global_message =
|
||||
|
|
|
@ -237,7 +237,9 @@
|
|||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody></tbody>
|
||||
<tbody>
|
||||
<!-- TODO: Show loading icon while fetching entries -->
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<form id="add-tracking-form" novalidate>
|
||||
|
|
|
@ -2,7 +2,8 @@
|
|||
|
||||
namespace com\fwdekker\deathnotifier;
|
||||
|
||||
use com\fwdekker\deathnotifier\validator\InvalidInputException;
|
||||
use com\fwdekker\deathnotifier\validation\InvalidTypeException;
|
||||
use com\fwdekker\deathnotifier\validation\InvalidValueException;
|
||||
|
||||
|
||||
/**
|
||||
|
@ -17,7 +18,8 @@ abstract class Action
|
|||
*
|
||||
* @param array<int|string, mixed> $inputs the inputs to perform the action with
|
||||
* @return mixed the requested data; may be `null`
|
||||
* @throws InvalidInputException if any of the inputs is invalid
|
||||
* @throws InvalidTypeException if any of the inputs has the incorrect type
|
||||
* @throws InvalidValueException if any of the inputs is invalid
|
||||
* @throws UnexpectedException if the action could not be performed even though the inputs are valid
|
||||
*/
|
||||
abstract function handle(array $inputs): mixed;
|
||||
|
|
|
@ -2,7 +2,8 @@
|
|||
|
||||
namespace com\fwdekker\deathnotifier;
|
||||
|
||||
use com\fwdekker\deathnotifier\validator\InvalidInputException;
|
||||
use com\fwdekker\deathnotifier\validation\InvalidTypeException;
|
||||
use com\fwdekker\deathnotifier\validation\InvalidValueException;
|
||||
use Monolog\Logger;
|
||||
|
||||
|
||||
|
@ -75,8 +76,8 @@ class ActionDispatcher
|
|||
* @param array<int|string, mixed> $inputs the inputs to the action, where the `action` key specifies which action
|
||||
* to execute
|
||||
* @return Response an unsatisfied `Response` if no registered {@see Action} could be found to handle the request,
|
||||
* an unsatisfied `Response` if an `Action` could be found but threw an {@see UnexpectedException} or an
|
||||
* {@see InvalidInputException}, or a satisfied `Response` containing the return value of `Action` that was invoked
|
||||
* or if an `Action` could be found but threw an {@see InvalidTypeException}, {@see InvalidValueException}, or
|
||||
* {@see UnexpectedException}, or a satisfied `Response` containing the return value of `Action` that was invoked
|
||||
* with {@see $inputs}
|
||||
*/
|
||||
public function handle(ActionMethod $method, array $inputs): Response
|
||||
|
@ -98,8 +99,30 @@ class ActionDispatcher
|
|||
} catch (UnexpectedException $exception) {
|
||||
$this->logger->error($exception);
|
||||
return Response::unsatisfied(message: $exception->getMessage());
|
||||
} catch (InvalidInputException $exception) {
|
||||
} catch (InvalidTypeException $exception) {
|
||||
return Response::unsatisfied(message: $this->format_invalid_type_message($exception));
|
||||
} catch (InvalidValueException $exception) {
|
||||
return Response::unsatisfied(message: $exception->getMessage(), target: $exception->target);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns a user-friendly message to display if an {@see InvalidTypeException} is thrown.
|
||||
*
|
||||
* @param InvalidTypeException $exception the exception to format
|
||||
* @return string a user-friendly message to display if an {@see InvalidTypeException} is thrown
|
||||
*/
|
||||
private function format_invalid_type_message(InvalidTypeException $exception): string
|
||||
{
|
||||
$message = "Malformed request: " . $exception->getMessage() . " ";
|
||||
|
||||
$issue_path = Config::get("server.issue_path");
|
||||
if ($issue_path === null)
|
||||
$message .= "This is a bug. Reload the page and try again.";
|
||||
else
|
||||
$message .= `This is a bug. You can report this bug at <a href="$issue_path">the issue tracker</a>.`;
|
||||
|
||||
return $message;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -27,7 +27,7 @@ class Config
|
|||
*
|
||||
* @param string|null $index the index of the property to return, using `.` as a nesting delimiter; or `null` to
|
||||
* return the entire configuration
|
||||
* @return mixed the property at {@see $index}
|
||||
* @return mixed|null the property at {@see $index}, or `null` if there is no such property
|
||||
*/
|
||||
public static function get(?string $index = null): mixed
|
||||
{
|
||||
|
@ -38,8 +38,12 @@ class Config
|
|||
return self::$config;
|
||||
|
||||
$output = self::$config;
|
||||
foreach (explode(".", $index) as $key)
|
||||
foreach (explode(".", $index) as $key) {
|
||||
if (!isset($output[$key]))
|
||||
return null;
|
||||
|
||||
$output = $output[$key];
|
||||
}
|
||||
return $output;
|
||||
}
|
||||
|
||||
|
@ -51,16 +55,7 @@ class Config
|
|||
*/
|
||||
public static function has(string $index): bool
|
||||
{
|
||||
$output = self::get();
|
||||
|
||||
foreach (explode(".", $index) as $key) {
|
||||
if (!isset($output[$key]))
|
||||
return false;
|
||||
|
||||
$output = $output[$key];
|
||||
}
|
||||
|
||||
return true;
|
||||
return self::get($index) !== null;
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
namespace com\fwdekker\deathnotifier;
|
||||
|
||||
use com\fwdekker\deathnotifier\validator\InvalidInputException;
|
||||
use com\fwdekker\deathnotifier\validation\InvalidValueException;
|
||||
|
||||
|
||||
/**
|
||||
|
@ -39,7 +39,7 @@ class EmulateCronAction extends Action
|
|||
*
|
||||
* @param array<int|string, mixed> $inputs the inputs to perform the action with
|
||||
* @return never
|
||||
* @throws InvalidInputException if any of the inputs is invalid
|
||||
* @throws InvalidValueException if any of the inputs is invalid
|
||||
* @throws UnexpectedException if the action could not be performed even though the inputs are valid
|
||||
* @noinspection PhpDocRedundantThrowsInspection may be thrown by {@see $actions}
|
||||
*/
|
||||
|
@ -47,6 +47,7 @@ class EmulateCronAction extends Action
|
|||
{
|
||||
// @phpstan-ignore-next-line
|
||||
while (true) {
|
||||
// TODO: Log last cron task in database, to make it really clear when it last happened
|
||||
foreach ($this->actions as $action)
|
||||
$action->handle($inputs);
|
||||
print("Done.\n");
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
namespace com\fwdekker\deathnotifier;
|
||||
|
||||
use com\fwdekker\deathnotifier\validator\InvalidInputException;
|
||||
use com\fwdekker\deathnotifier\validation\InvalidValueException;
|
||||
use Exception;
|
||||
use JsonException;
|
||||
|
||||
|
@ -16,7 +16,7 @@ class Util
|
|||
* Parses POST values from JSON-based inputs.
|
||||
*
|
||||
* @return array<int|string, mixed> the parsed POSTed values, or an empty array if no values were POSTed
|
||||
* @throws InvalidInputException if there are no POST input values, or if the POST input is not valid JSON
|
||||
* @throws InvalidValueException if there are no POST input values, or if the POST input is not valid JSON
|
||||
*/
|
||||
static function parse_post(): array
|
||||
{
|
||||
|
@ -27,11 +27,11 @@ class Util
|
|||
try {
|
||||
$post = json_decode($post_input, associative: true, flags: JSON_THROW_ON_ERROR);
|
||||
if ($post === null)
|
||||
throw new InvalidInputException("Malformed request: POST data is `null`.");
|
||||
throw new InvalidValueException("Malformed request: POST data is `null`.");
|
||||
|
||||
return $post;
|
||||
} catch (JsonException) {
|
||||
throw new InvalidInputException("Malformed request: POST data could not be parsed as JSON.");
|
||||
throw new InvalidValueException("Malformed request: POST data could not be parsed as JSON.");
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -5,9 +5,10 @@ namespace com\fwdekker\deathnotifier\mailer;
|
|||
use com\fwdekker\deathnotifier\Action;
|
||||
use com\fwdekker\deathnotifier\Config;
|
||||
use com\fwdekker\deathnotifier\UnexpectedException;
|
||||
use com\fwdekker\deathnotifier\validator\EqualsCliPasswordRule;
|
||||
use com\fwdekker\deathnotifier\validator\InvalidInputException;
|
||||
use com\fwdekker\deathnotifier\validator\RuleSet;
|
||||
use com\fwdekker\deathnotifier\validation\EqualsCliPasswordRule;
|
||||
use com\fwdekker\deathnotifier\validation\InvalidTypeException;
|
||||
use com\fwdekker\deathnotifier\validation\InvalidValueException;
|
||||
use com\fwdekker\deathnotifier\validation\RuleSet;
|
||||
use PHPMailer\PHPMailer\Exception as PHPMailerException;
|
||||
use PHPMailer\PHPMailer\PHPMailer;
|
||||
use PHPMailer\PHPMailer\SMTP;
|
||||
|
@ -40,7 +41,8 @@ class ProcessEmailQueueAction extends Action
|
|||
*
|
||||
* @param array<int|string, mixed> $inputs `"password": string`: the CLI password
|
||||
* @return null
|
||||
* @throws InvalidInputException if the CLI password is wrong
|
||||
* @throws InvalidTypeException if any of the inputs has the incorrect type
|
||||
* @throws InvalidValueException if the CLI password is wrong
|
||||
* @throws UnexpectedException if the mailer fails to send an email
|
||||
*/
|
||||
public function handle(array $inputs): mixed
|
||||
|
|
|
@ -5,12 +5,13 @@ namespace com\fwdekker\deathnotifier\tracking;
|
|||
use com\fwdekker\deathnotifier\Action;
|
||||
use com\fwdekker\deathnotifier\IllegalStateError;
|
||||
use com\fwdekker\deathnotifier\UnexpectedException;
|
||||
use com\fwdekker\deathnotifier\validator\HasStringLengthRule;
|
||||
use com\fwdekker\deathnotifier\validator\InvalidInputException;
|
||||
use com\fwdekker\deathnotifier\validator\IsNotBlankRule;
|
||||
use com\fwdekker\deathnotifier\validator\IsValidCsrfTokenRule;
|
||||
use com\fwdekker\deathnotifier\validator\RuleSet;
|
||||
use com\fwdekker\deathnotifier\validator\SessionRuleSet;
|
||||
use com\fwdekker\deathnotifier\validation\HasStringLengthRule;
|
||||
use com\fwdekker\deathnotifier\validation\InvalidTypeException;
|
||||
use com\fwdekker\deathnotifier\validation\InvalidValueException;
|
||||
use com\fwdekker\deathnotifier\validation\IsNotBlankRule;
|
||||
use com\fwdekker\deathnotifier\validation\IsValidCsrfTokenRule;
|
||||
use com\fwdekker\deathnotifier\validation\RuleSet;
|
||||
use com\fwdekker\deathnotifier\validation\LoginValidator;
|
||||
use com\fwdekker\deathnotifier\wikipedia\ArticleType;
|
||||
use com\fwdekker\deathnotifier\wikipedia\PersonStatus;
|
||||
use com\fwdekker\deathnotifier\wikipedia\Wikipedia;
|
||||
|
@ -54,14 +55,15 @@ class AddTrackingAction extends Action
|
|||
* of the person to track
|
||||
* @return array{"input_name": string, "normalized_name": string} the person's name as given by the user, and the
|
||||
* normalized version of that name
|
||||
* @throws InvalidInputException if the user is not logged in, if no valid CSRF token is present, if the article
|
||||
* @throws InvalidTypeException if any of the inputs has the incorrect type
|
||||
* @throws InvalidValueException if the user is not logged in, if no valid CSRF token is present, if the article
|
||||
* title is a blank string, if the article title is too short or too long, if the specified article does not exist,
|
||||
* if the specified article is not about a person, or if the user is already tracking this article
|
||||
* @throws UnexpectedException if Wikipedia could not be reached
|
||||
*/
|
||||
public function handle(array $inputs): array
|
||||
{
|
||||
(new SessionRuleSet(validate_logged_in: true))->check($_SESSION);
|
||||
(new LoginValidator(validate_logged_in: true))->check($_SESSION);
|
||||
(new RuleSet([
|
||||
"token" => [new IsValidCsrfTokenRule()],
|
||||
"person_name" => [
|
||||
|
@ -73,7 +75,7 @@ class AddTrackingAction extends Action
|
|||
[$normalized_name, $status] = $this->get_and_validate_page_info(strval($inputs["person_name"]));
|
||||
$this->tracking_list->transaction(function () use ($normalized_name, $status) {
|
||||
if ($this->tracking_list->has_tracking($_SESSION["uuid"], $normalized_name))
|
||||
throw new InvalidInputException("You are already tracking <b>$normalized_name</b>.");
|
||||
throw new InvalidValueException("You are already tracking <b>$normalized_name</b>.");
|
||||
|
||||
$this->tracking_list->add_tracking($_SESSION["uuid"], $normalized_name, $status);
|
||||
});
|
||||
|
@ -86,7 +88,7 @@ class AddTrackingAction extends Action
|
|||
*
|
||||
* @param string $person_name the title of the article about a person to return the information of
|
||||
* @return array{string, PersonStatus} the normalized name and `PersonStatus` of the specified article
|
||||
* @throws InvalidInputException if the article about {@see $person_name} does not exist or is not about a person
|
||||
* @throws InvalidValueException if the article about {@see $person_name} does not exist or is not about a person
|
||||
* @throws UnexpectedException if Wikipedia could not be reached
|
||||
*/
|
||||
private function get_and_validate_page_info(string $person_name): array
|
||||
|
@ -105,7 +107,7 @@ class AddTrackingAction extends Action
|
|||
}
|
||||
|
||||
if (in_array($normalized_name, $info->missing)) {
|
||||
throw new InvalidInputException(
|
||||
throw new InvalidValueException(
|
||||
$this->override_message ??
|
||||
"Wikipedia does not have an article about " .
|
||||
"<b><a href='https://en.wikipedia.org/wiki/Special:Search?search=" .
|
||||
|
@ -114,7 +116,7 @@ class AddTrackingAction extends Action
|
|||
"person_name"
|
||||
);
|
||||
} else if ($type === ArticleType::Disambiguation) {
|
||||
throw new InvalidInputException(
|
||||
throw new InvalidValueException(
|
||||
$this->override_message ??
|
||||
"<b><a href='https://en.wikipedia.org/wiki/" . rawurlencode($normalized_name) . "'>" .
|
||||
htmlentities($normalized_name) . "</a></b> refers to multiple articles. " .
|
||||
|
@ -123,7 +125,7 @@ class AddTrackingAction extends Action
|
|||
"person_name"
|
||||
);
|
||||
} else if ($type === ArticleType::Other) {
|
||||
throw new InvalidInputException(
|
||||
throw new InvalidValueException(
|
||||
$this->override_message ??
|
||||
"The Wikipedia article about " .
|
||||
"<b><a href='https://en.wikipedia.org/wiki/" . rawurlencode($normalized_name) . "'>" .
|
||||
|
|
|
@ -3,10 +3,11 @@
|
|||
namespace com\fwdekker\deathnotifier\tracking;
|
||||
|
||||
use com\fwdekker\deathnotifier\Action;
|
||||
use com\fwdekker\deathnotifier\validator\InvalidInputException;
|
||||
use com\fwdekker\deathnotifier\validator\IsValidCsrfTokenRule;
|
||||
use com\fwdekker\deathnotifier\validator\RuleSet;
|
||||
use com\fwdekker\deathnotifier\validator\SessionRuleSet;
|
||||
use com\fwdekker\deathnotifier\validation\InvalidTypeException;
|
||||
use com\fwdekker\deathnotifier\validation\InvalidValueException;
|
||||
use com\fwdekker\deathnotifier\validation\IsValidCsrfTokenRule;
|
||||
use com\fwdekker\deathnotifier\validation\RuleSet;
|
||||
use com\fwdekker\deathnotifier\validation\LoginValidator;
|
||||
|
||||
|
||||
/**
|
||||
|
@ -38,11 +39,12 @@ class ListTrackingsAction extends Action
|
|||
*
|
||||
* @param array<int|string, mixed> $inputs `"token": string`: a valid CSRF token
|
||||
* @return array<array{"name": string, "status": string, "is_deleted": bool}> all trackings of the current user
|
||||
* @throws InvalidInputException if the user is not logged in or if no valid CSRF token is present
|
||||
* @throws InvalidTypeException if any of the inputs has the incorrect type
|
||||
* @throws InvalidValueException if the user is not logged in or if no valid CSRF token is present
|
||||
*/
|
||||
public function handle(array $inputs): array
|
||||
{
|
||||
(new SessionRuleSet(validate_logged_in: true))->check($_SESSION);
|
||||
(new LoginValidator(validate_logged_in: true))->check($_SESSION);
|
||||
(new RuleSet(["token" => [new IsValidCsrfTokenRule()]]))->check($inputs);
|
||||
|
||||
return $this->tracking_list->list_trackings($_SESSION["uuid"]);
|
||||
|
|
|
@ -3,11 +3,12 @@
|
|||
namespace com\fwdekker\deathnotifier\tracking;
|
||||
|
||||
use com\fwdekker\deathnotifier\Action;
|
||||
use com\fwdekker\deathnotifier\validator\InvalidInputException;
|
||||
use com\fwdekker\deathnotifier\validator\IsStringRule;
|
||||
use com\fwdekker\deathnotifier\validator\IsValidCsrfTokenRule;
|
||||
use com\fwdekker\deathnotifier\validator\RuleSet;
|
||||
use com\fwdekker\deathnotifier\validator\SessionRuleSet;
|
||||
use com\fwdekker\deathnotifier\validation\InvalidTypeException;
|
||||
use com\fwdekker\deathnotifier\validation\InvalidValueException;
|
||||
use com\fwdekker\deathnotifier\validation\IsStringRule;
|
||||
use com\fwdekker\deathnotifier\validation\IsValidCsrfTokenRule;
|
||||
use com\fwdekker\deathnotifier\validation\RuleSet;
|
||||
use com\fwdekker\deathnotifier\validation\LoginValidator;
|
||||
|
||||
|
||||
/**
|
||||
|
@ -40,12 +41,12 @@ class RemoveTrackingAction extends Action
|
|||
* @param array<int|string, mixed> $inputs `"token": string`: a valid CSRF token, `"person_name": string`: the name
|
||||
* of the person to stop tracking
|
||||
* @return null
|
||||
* @throws InvalidInputException if the user is not logged in, if no valid CSRF token is present, or if the article
|
||||
* title is not a string
|
||||
* @throws InvalidTypeException if any of the inputs has the incorrect type
|
||||
* @throws InvalidValueException if the user is not logged in or if no valid CSRF token is present
|
||||
*/
|
||||
public function handle(array $inputs): mixed
|
||||
{
|
||||
(new SessionRuleSet(validate_logged_in: true))->check($_SESSION);
|
||||
(new LoginValidator(validate_logged_in: true))->check($_SESSION);
|
||||
(new RuleSet([
|
||||
"token" => [new IsValidCsrfTokenRule()],
|
||||
"person_name" => [new IsStringRule()],
|
||||
|
|
|
@ -8,9 +8,10 @@ use com\fwdekker\deathnotifier\LoggerUtil;
|
|||
use com\fwdekker\deathnotifier\mailer\Email;
|
||||
use com\fwdekker\deathnotifier\mailer\EmailQueue;
|
||||
use com\fwdekker\deathnotifier\UnexpectedException;
|
||||
use com\fwdekker\deathnotifier\validator\EqualsCliPasswordRule;
|
||||
use com\fwdekker\deathnotifier\validator\InvalidInputException;
|
||||
use com\fwdekker\deathnotifier\validator\RuleSet;
|
||||
use com\fwdekker\deathnotifier\validation\EqualsCliPasswordRule;
|
||||
use com\fwdekker\deathnotifier\validation\InvalidTypeException;
|
||||
use com\fwdekker\deathnotifier\validation\InvalidValueException;
|
||||
use com\fwdekker\deathnotifier\validation\RuleSet;
|
||||
use com\fwdekker\deathnotifier\wikipedia\Wikipedia;
|
||||
use com\fwdekker\deathnotifier\wikipedia\WikipediaException;
|
||||
use Monolog\Logger;
|
||||
|
@ -64,7 +65,8 @@ class UpdateTrackingsAction extends Action
|
|||
*
|
||||
* @param array<int|string, mixed> $inputs `"password": string`: the CLI password
|
||||
* @return null
|
||||
* @throws InvalidInputException if the CLI password is wrong
|
||||
* @throws InvalidTypeException if any of the inputs has the incorrect type
|
||||
* @throws InvalidValueException if the CLI password is wrong
|
||||
* @throws UnexpectedException if Wikipedia could not be reached
|
||||
* @noinspection PhpDocRedundantThrowsInspection can be thrown through {@see TrackingList::transaction()}
|
||||
*/
|
||||
|
|
|
@ -8,11 +8,12 @@ use com\fwdekker\deathnotifier\mailer\Email;
|
|||
use com\fwdekker\deathnotifier\mailer\EmailQueue;
|
||||
use com\fwdekker\deathnotifier\tracking\TrackingList;
|
||||
use com\fwdekker\deathnotifier\UnexpectedException;
|
||||
use com\fwdekker\deathnotifier\validator\InvalidInputException;
|
||||
use com\fwdekker\deathnotifier\validator\IsEmailRule;
|
||||
use com\fwdekker\deathnotifier\validator\IsValidCsrfTokenRule;
|
||||
use com\fwdekker\deathnotifier\validator\RuleSet;
|
||||
use com\fwdekker\deathnotifier\validator\SessionRuleSet;
|
||||
use com\fwdekker\deathnotifier\validation\InvalidTypeException;
|
||||
use com\fwdekker\deathnotifier\validation\InvalidValueException;
|
||||
use com\fwdekker\deathnotifier\validation\IsEmailRule;
|
||||
use com\fwdekker\deathnotifier\validation\IsValidCsrfTokenRule;
|
||||
use com\fwdekker\deathnotifier\validation\RuleSet;
|
||||
use com\fwdekker\deathnotifier\validation\LoginValidator;
|
||||
|
||||
|
||||
/**
|
||||
|
@ -54,14 +55,15 @@ class ChangeEmailAction extends Action
|
|||
* @param array<int|string, mixed> $inputs `"token": string`: a valid CSRF token, `"email": string`: the new email
|
||||
* address
|
||||
* @return null
|
||||
* @throws InvalidInputException if the user is not logged in, if no valid CSRF token is present, if the email
|
||||
* @throws InvalidTypeException if any of the inputs has the incorrect type
|
||||
* @throws InvalidValueException if the user is not logged in, if no valid CSRF token is present, if the email
|
||||
* address is invalid, if the email address is not different, or if the email address is already used
|
||||
* @throws UnexpectedException if the current user has been deleted
|
||||
* @noinspection PhpDocRedundantThrowsInspection can be thrown through {@see TrackingList::transaction()}
|
||||
*/
|
||||
public function handle(array $inputs): mixed
|
||||
{
|
||||
(new SessionRuleSet(validate_logged_in: true))->check($_SESSION);
|
||||
(new LoginValidator(validate_logged_in: true))->check($_SESSION);
|
||||
(new RuleSet([
|
||||
"token" => [new IsValidCsrfTokenRule()],
|
||||
"email" => [new IsEmailRule()],
|
||||
|
@ -70,11 +72,11 @@ class ChangeEmailAction extends Action
|
|||
$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.");
|
||||
throw new UnexpectedException("Failed to retrieve user data. Please refresh the page and try again.");
|
||||
if ($inputs["email"] === $user_data["email"])
|
||||
throw new InvalidInputException("That is already your email address.", "email");
|
||||
throw new InvalidValueException("That is already your email address.", "email");
|
||||
if ($this->user_list->has_user_with_email($inputs["email"]))
|
||||
throw new InvalidInputException("That email address is already in use by someone else.", "email");
|
||||
throw new InvalidValueException("That email address is already in use by someone else.", "email");
|
||||
|
||||
$token = $this->user_list->set_email($_SESSION["uuid"], $inputs["email"]);
|
||||
$this->email_queue->queue_email(new ChangeEmailFromEmail($user_data["email"], $inputs["email"]));
|
||||
|
|
|
@ -7,12 +7,13 @@ use com\fwdekker\deathnotifier\Config;
|
|||
use com\fwdekker\deathnotifier\mailer\Email;
|
||||
use com\fwdekker\deathnotifier\mailer\EmailQueue;
|
||||
use com\fwdekker\deathnotifier\UnexpectedException;
|
||||
use com\fwdekker\deathnotifier\validator\HasStringLengthRule;
|
||||
use com\fwdekker\deathnotifier\validator\InvalidInputException;
|
||||
use com\fwdekker\deathnotifier\validator\IsStringRule;
|
||||
use com\fwdekker\deathnotifier\validator\IsValidCsrfTokenRule;
|
||||
use com\fwdekker\deathnotifier\validator\RuleSet;
|
||||
use com\fwdekker\deathnotifier\validator\SessionRuleSet;
|
||||
use com\fwdekker\deathnotifier\validation\HasStringLengthRule;
|
||||
use com\fwdekker\deathnotifier\validation\InvalidTypeException;
|
||||
use com\fwdekker\deathnotifier\validation\InvalidValueException;
|
||||
use com\fwdekker\deathnotifier\validation\IsStringRule;
|
||||
use com\fwdekker\deathnotifier\validation\IsValidCsrfTokenRule;
|
||||
use com\fwdekker\deathnotifier\validation\RuleSet;
|
||||
use com\fwdekker\deathnotifier\validation\LoginValidator;
|
||||
|
||||
|
||||
/**
|
||||
|
@ -53,14 +54,15 @@ class ChangePasswordAction extends Action
|
|||
* @param array<int|string, mixed> $inputs `"token": string`: a valid CSRF token, `"password_old": string`: the old
|
||||
* password, `"password_new": string`: the new password
|
||||
* @return null
|
||||
* @throws InvalidInputException if the user is not logged in, if no valid CSRF token is present, if the old
|
||||
* @throws InvalidTypeException if any of the inputs has the incorrect type
|
||||
* @throws InvalidValueException if the user is not logged in, if no valid CSRF token is present, if the old
|
||||
* password is incorrect, or if the new password is too short or too long
|
||||
* @throws UnexpectedException if the current user has been deleted
|
||||
* @noinspection PhpDocRedundantThrowsInspection can be thrown through {@see TrackingList::transaction()}
|
||||
*/
|
||||
public function handle(array $inputs): mixed
|
||||
{
|
||||
(new SessionRuleSet(validate_logged_in: true))->check($_SESSION);
|
||||
(new LoginValidator(validate_logged_in: true))->check($_SESSION);
|
||||
(new RuleSet([
|
||||
"token" => [new IsValidCsrfTokenRule()],
|
||||
"password_old" => [new IsStringRule()],
|
||||
|
@ -70,9 +72,9 @@ class ChangePasswordAction extends Action
|
|||
$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.");
|
||||
throw new UnexpectedException("Failed to retrieve user data. Please refresh the page and try again.");
|
||||
if (!password_verify($inputs["password_old"], $user_data["password"]))
|
||||
throw new InvalidInputException("Incorrect old password.", "password_old");
|
||||
throw new InvalidValueException("Incorrect old password.", "password_old");
|
||||
|
||||
$this->user_list->set_password($_SESSION["uuid"], $inputs["password_new"]);
|
||||
$this->email_queue->queue_email(new ChangePasswordEmail($user_data["email"]));
|
||||
|
|
|
@ -4,10 +4,11 @@ namespace com\fwdekker\deathnotifier\user;
|
|||
|
||||
use com\fwdekker\deathnotifier\Action;
|
||||
use com\fwdekker\deathnotifier\UnexpectedException;
|
||||
use com\fwdekker\deathnotifier\validator\InvalidInputException;
|
||||
use com\fwdekker\deathnotifier\validator\IsValidCsrfTokenRule;
|
||||
use com\fwdekker\deathnotifier\validator\RuleSet;
|
||||
use com\fwdekker\deathnotifier\validator\SessionRuleSet;
|
||||
use com\fwdekker\deathnotifier\validation\InvalidTypeException;
|
||||
use com\fwdekker\deathnotifier\validation\InvalidValueException;
|
||||
use com\fwdekker\deathnotifier\validation\IsValidCsrfTokenRule;
|
||||
use com\fwdekker\deathnotifier\validation\RuleSet;
|
||||
use com\fwdekker\deathnotifier\validation\LoginValidator;
|
||||
|
||||
|
||||
/**
|
||||
|
@ -40,17 +41,18 @@ class GetPublicUserDataAction extends Action
|
|||
* @param array<int|string, mixed> $inputs `"token": string`: a valid CSRF token
|
||||
* @return array{"email": string, "email_verified": bool, "email_notifications_enabled": bool,
|
||||
* "password_last_change": int} the user's public data
|
||||
* @throws InvalidInputException if the user is not logged in or if no valid CSRF token is present
|
||||
* @throws InvalidTypeException if any of the inputs has the incorrect type
|
||||
* @throws InvalidValueException if the user is not logged in or if no valid CSRF token is present
|
||||
* @throws UnexpectedException if the current user has been deleted
|
||||
*/
|
||||
public function handle(array $inputs): array
|
||||
{
|
||||
(new SessionRuleSet(validate_logged_in: true))->check($_SESSION);
|
||||
(new LoginValidator(validate_logged_in: true))->check($_SESSION);
|
||||
(new RuleSet(["token" => [new IsValidCsrfTokenRule()]]))->check($inputs);
|
||||
|
||||
$user_data = $this->user_list->get_user_by_uuid($_SESSION["uuid"]);
|
||||
if ($user_data === null)
|
||||
throw new UnexpectedException("Failed to retrieve account data. Refresh the page and try again.");
|
||||
throw new UnexpectedException("Failed to retrieve account data. Please refresh the page and try again.");
|
||||
|
||||
return [
|
||||
"email" => $user_data["email"],
|
||||
|
|
|
@ -3,12 +3,13 @@
|
|||
namespace com\fwdekker\deathnotifier\user;
|
||||
|
||||
use com\fwdekker\deathnotifier\Action;
|
||||
use com\fwdekker\deathnotifier\validator\InvalidInputException;
|
||||
use com\fwdekker\deathnotifier\validator\IsEmailRule;
|
||||
use com\fwdekker\deathnotifier\validator\IsStringRule;
|
||||
use com\fwdekker\deathnotifier\validator\IsValidCsrfTokenRule;
|
||||
use com\fwdekker\deathnotifier\validator\RuleSet;
|
||||
use com\fwdekker\deathnotifier\validator\SessionRuleSet;
|
||||
use com\fwdekker\deathnotifier\validation\InvalidTypeException;
|
||||
use com\fwdekker\deathnotifier\validation\InvalidValueException;
|
||||
use com\fwdekker\deathnotifier\validation\IsEmailRule;
|
||||
use com\fwdekker\deathnotifier\validation\IsStringRule;
|
||||
use com\fwdekker\deathnotifier\validation\IsValidCsrfTokenRule;
|
||||
use com\fwdekker\deathnotifier\validation\RuleSet;
|
||||
use com\fwdekker\deathnotifier\validation\LoginValidator;
|
||||
|
||||
|
||||
/**
|
||||
|
@ -41,12 +42,13 @@ class LoginAction extends Action
|
|||
* @param array<int|string, mixed> $inputs `"token": string`: a valid CSRF token, `"email": string`: the email to
|
||||
* log in with, `"password": string`: the password to log in with
|
||||
* @return null
|
||||
* @throws InvalidInputException if the user is logged in, if no account with the given email address exists, if the
|
||||
* @throws InvalidTypeException if any of the inputs has the incorrect type
|
||||
* @throws InvalidValueException if the user is logged in, if no account with the given email address exists, if the
|
||||
* password is wrong, or if no valid CSRF token is present
|
||||
*/
|
||||
public function handle(array $inputs): mixed
|
||||
{
|
||||
(new SessionRuleSet(validate_logged_out: true))->check($_SESSION);
|
||||
(new LoginValidator(validate_logged_out: true))->check($_SESSION);
|
||||
(new RuleSet([
|
||||
"token" => [new IsValidCsrfTokenRule()],
|
||||
"email" => [new IsEmailRule()],
|
||||
|
@ -55,9 +57,9 @@ class LoginAction extends Action
|
|||
|
||||
$user_data = $this->user_list->get_user_by_email($inputs["email"]);
|
||||
if ($user_data === null)
|
||||
throw new InvalidInputException("No user with that email address has been registered.", "password");
|
||||
throw new InvalidValueException("No user with that email address has been registered.", "password");
|
||||
if (!password_verify($inputs["password"], $user_data["password"]))
|
||||
throw new InvalidInputException("Incorrect password.", "password");
|
||||
throw new InvalidValueException("Incorrect password.", "password");
|
||||
|
||||
$_SESSION["uuid"] = $user_data["uuid"];
|
||||
return null;
|
||||
|
|
|
@ -5,10 +5,11 @@ namespace com\fwdekker\deathnotifier\user;
|
|||
use com\fwdekker\deathnotifier\Action;
|
||||
use com\fwdekker\deathnotifier\UnexpectedException;
|
||||
use com\fwdekker\deathnotifier\Util;
|
||||
use com\fwdekker\deathnotifier\validator\InvalidInputException;
|
||||
use com\fwdekker\deathnotifier\validator\IsValidCsrfTokenRule;
|
||||
use com\fwdekker\deathnotifier\validator\RuleSet;
|
||||
use com\fwdekker\deathnotifier\validator\SessionRuleSet;
|
||||
use com\fwdekker\deathnotifier\validation\InvalidTypeException;
|
||||
use com\fwdekker\deathnotifier\validation\InvalidValueException;
|
||||
use com\fwdekker\deathnotifier\validation\IsValidCsrfTokenRule;
|
||||
use com\fwdekker\deathnotifier\validation\RuleSet;
|
||||
use com\fwdekker\deathnotifier\validation\LoginValidator;
|
||||
use Exception;
|
||||
|
||||
|
||||
|
@ -24,12 +25,13 @@ class LogoutAction extends Action
|
|||
*
|
||||
* @param array<int|string, mixed> $inputs `"token": string`: a valid CSRF token
|
||||
* @return null
|
||||
* @throws InvalidInputException if the user is not logged in or if no valid CSRF token is present
|
||||
* @throws InvalidTypeException if any of the inputs has the incorrect type
|
||||
* @throws InvalidValueException if the user is not logged in or if no valid CSRF token is present
|
||||
* @throws UnexpectedException if no new CSRF token could be generated
|
||||
*/
|
||||
public function handle(array $inputs): mixed
|
||||
{
|
||||
(new SessionRuleSet(validate_logged_in: true))->check($_SESSION);
|
||||
(new LoginValidator(validate_logged_in: true))->check($_SESSION);
|
||||
(new RuleSet(["token" => [new IsValidCsrfTokenRule()]]))->check($inputs);
|
||||
|
||||
session_destroy();
|
||||
|
|
|
@ -6,12 +6,13 @@ use com\fwdekker\deathnotifier\Action;
|
|||
use com\fwdekker\deathnotifier\Config;
|
||||
use com\fwdekker\deathnotifier\mailer\Email;
|
||||
use com\fwdekker\deathnotifier\mailer\EmailQueue;
|
||||
use com\fwdekker\deathnotifier\validator\HasStringLengthRule;
|
||||
use com\fwdekker\deathnotifier\validator\InvalidInputException;
|
||||
use com\fwdekker\deathnotifier\validator\IsEmailRule;
|
||||
use com\fwdekker\deathnotifier\validator\IsValidCsrfTokenRule;
|
||||
use com\fwdekker\deathnotifier\validator\RuleSet;
|
||||
use com\fwdekker\deathnotifier\validator\SessionRuleSet;
|
||||
use com\fwdekker\deathnotifier\validation\HasStringLengthRule;
|
||||
use com\fwdekker\deathnotifier\validation\InvalidTypeException;
|
||||
use com\fwdekker\deathnotifier\validation\InvalidValueException;
|
||||
use com\fwdekker\deathnotifier\validation\IsEmailRule;
|
||||
use com\fwdekker\deathnotifier\validation\IsValidCsrfTokenRule;
|
||||
use com\fwdekker\deathnotifier\validation\RuleSet;
|
||||
use com\fwdekker\deathnotifier\validation\LoginValidator;
|
||||
|
||||
|
||||
/**
|
||||
|
@ -52,12 +53,13 @@ class RegisterAction extends Action
|
|||
* @param array<int|string, mixed> $inputs `"token": string`: a valid CSRF token, `"email": string`: the email
|
||||
* address to register the account under, `"password": string`: the password to use for the new account
|
||||
* @return null
|
||||
* @throws InvalidInputException if the user is logged in, if no valid CSRF token is present, if the email address
|
||||
* @throws InvalidTypeException if any of the inputs has the incorrect type
|
||||
* @throws InvalidValueException if the user is logged in, if no valid CSRF token is present, if the email address
|
||||
* is invalid, if the email address is already in use, or if the password is too short or too long
|
||||
*/
|
||||
public function handle(array $inputs): mixed
|
||||
{
|
||||
(new SessionRuleSet(validate_logged_out: true))->check($_SESSION);
|
||||
(new LoginValidator(validate_logged_out: true))->check($_SESSION);
|
||||
(new RuleSet([
|
||||
"token" => [new IsValidCsrfTokenRule()],
|
||||
"email" => [new IsEmailRule()],
|
||||
|
@ -66,7 +68,7 @@ class RegisterAction extends Action
|
|||
|
||||
$this->user_list->transaction(function () use ($inputs) {
|
||||
if ($this->user_list->has_user_with_email($inputs["email"]))
|
||||
throw new InvalidInputException("That email address already in use.", "email");
|
||||
throw new InvalidValueException("That email address already in use.", "email");
|
||||
|
||||
$token = $this->user_list->add_user($inputs["email"], $inputs["password"]);
|
||||
$this->email_queue->queue_email(new RegisterEmail($inputs["email"], $token));
|
||||
|
|
|
@ -8,10 +8,11 @@ use com\fwdekker\deathnotifier\mailer\Email;
|
|||
use com\fwdekker\deathnotifier\mailer\EmailQueue;
|
||||
use com\fwdekker\deathnotifier\UnexpectedException;
|
||||
use com\fwdekker\deathnotifier\Util;
|
||||
use com\fwdekker\deathnotifier\validator\InvalidInputException;
|
||||
use com\fwdekker\deathnotifier\validator\IsValidCsrfTokenRule;
|
||||
use com\fwdekker\deathnotifier\validator\RuleSet;
|
||||
use com\fwdekker\deathnotifier\validator\SessionRuleSet;
|
||||
use com\fwdekker\deathnotifier\validation\InvalidTypeException;
|
||||
use com\fwdekker\deathnotifier\validation\InvalidValueException;
|
||||
use com\fwdekker\deathnotifier\validation\IsValidCsrfTokenRule;
|
||||
use com\fwdekker\deathnotifier\validation\RuleSet;
|
||||
use com\fwdekker\deathnotifier\validation\LoginValidator;
|
||||
|
||||
|
||||
/**
|
||||
|
@ -51,29 +52,30 @@ class ResendVerifyEmailAction extends Action
|
|||
*
|
||||
* @param array<int|string, mixed> $inputs `"token": string`: a valid CSRF token
|
||||
* @return null
|
||||
* @throws InvalidInputException if the user is logged out, if no valid CSRF token is present, if the email address
|
||||
* @throws InvalidTypeException if any of the inputs has the incorrect type
|
||||
* @throws InvalidValueException if the user is logged out, if no valid CSRF token is present, if the email address
|
||||
* is already verified, or if a verification email was sent too recently
|
||||
* @throws UnexpectedException if the current user has been deleted
|
||||
* @noinspection PhpDocRedundantThrowsInspection can be thrown through {@see TrackingList::transaction()}
|
||||
*/
|
||||
public function handle(array $inputs): mixed
|
||||
{
|
||||
(new SessionRuleSet(validate_logged_in: true))->check($_SESSION);
|
||||
(new LoginValidator(validate_logged_in: true))->check($_SESSION);
|
||||
(new RuleSet(["token" => [new IsValidCsrfTokenRule()]]))->check($inputs);
|
||||
|
||||
$this->user_list->transaction(function () {
|
||||
$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.");
|
||||
throw new UnexpectedException("Failed to retrieve user data. Please refresh the page and try again.");
|
||||
if ($user_data["email_verification_token"] === null)
|
||||
throw new InvalidInputException("Your email address is already verified.");
|
||||
throw new InvalidValueException("Your email address is already verified.");
|
||||
|
||||
$minutes_left = Util::minutes_until_interval_elapsed(
|
||||
$user_data["email_verification_token_timestamp"],
|
||||
UserList::MINUTES_BETWEEN_VERIFICATION_EMAILS
|
||||
);
|
||||
if ($minutes_left > 0) {
|
||||
throw new InvalidInputException(
|
||||
throw new InvalidValueException(
|
||||
"A verification email was sent recently. " .
|
||||
"Please wait $minutes_left more minute(s) before requesting a new email."
|
||||
);
|
||||
|
|
|
@ -7,12 +7,13 @@ use com\fwdekker\deathnotifier\Config;
|
|||
use com\fwdekker\deathnotifier\IllegalStateError;
|
||||
use com\fwdekker\deathnotifier\mailer\Email;
|
||||
use com\fwdekker\deathnotifier\mailer\EmailQueue;
|
||||
use com\fwdekker\deathnotifier\validator\HasStringLengthRule;
|
||||
use com\fwdekker\deathnotifier\validator\InvalidInputException;
|
||||
use com\fwdekker\deathnotifier\validator\IsEmailRule;
|
||||
use com\fwdekker\deathnotifier\validator\IsStringRule;
|
||||
use com\fwdekker\deathnotifier\validator\IsValidCsrfTokenRule;
|
||||
use com\fwdekker\deathnotifier\validator\RuleSet;
|
||||
use com\fwdekker\deathnotifier\validation\HasStringLengthRule;
|
||||
use com\fwdekker\deathnotifier\validation\InvalidTypeException;
|
||||
use com\fwdekker\deathnotifier\validation\InvalidValueException;
|
||||
use com\fwdekker\deathnotifier\validation\IsEmailRule;
|
||||
use com\fwdekker\deathnotifier\validation\IsStringRule;
|
||||
use com\fwdekker\deathnotifier\validation\IsValidCsrfTokenRule;
|
||||
use com\fwdekker\deathnotifier\validation\RuleSet;
|
||||
|
||||
|
||||
/**
|
||||
|
@ -54,7 +55,8 @@ class ResetPasswordAction extends Action
|
|||
* address of the account to reset the password of, `"password": string`: the new password to set,
|
||||
* `"reset_token": string`: the token to reset the password with
|
||||
* @return null
|
||||
* @throws InvalidInputException if no valid CSRF token is present, if no account with the given email address
|
||||
* @throws InvalidTypeException if any of the inputs has the incorrect type
|
||||
* @throws InvalidValueException if no valid CSRF token is present, if no account with the given email address
|
||||
* exists, if the password is too short or too long, if the reset token is invalid, or if the reset token has
|
||||
* expired
|
||||
*/
|
||||
|
|
|
@ -7,10 +7,11 @@ use com\fwdekker\deathnotifier\Config;
|
|||
use com\fwdekker\deathnotifier\mailer\Email;
|
||||
use com\fwdekker\deathnotifier\mailer\EmailQueue;
|
||||
use com\fwdekker\deathnotifier\Util;
|
||||
use com\fwdekker\deathnotifier\validator\InvalidInputException;
|
||||
use com\fwdekker\deathnotifier\validator\IsEmailRule;
|
||||
use com\fwdekker\deathnotifier\validator\IsValidCsrfTokenRule;
|
||||
use com\fwdekker\deathnotifier\validator\RuleSet;
|
||||
use com\fwdekker\deathnotifier\validation\InvalidTypeException;
|
||||
use com\fwdekker\deathnotifier\validation\InvalidValueException;
|
||||
use com\fwdekker\deathnotifier\validation\IsEmailRule;
|
||||
use com\fwdekker\deathnotifier\validation\IsValidCsrfTokenRule;
|
||||
use com\fwdekker\deathnotifier\validation\RuleSet;
|
||||
|
||||
|
||||
/**
|
||||
|
@ -51,7 +52,8 @@ class SendPasswordResetAction extends Action
|
|||
* @param array<int|string, mixed> $inputs `"token": string`: a valid CSRF token, `"email": string`: the email
|
||||
* address of the account to send a password reset email for
|
||||
* @return null
|
||||
* @throws InvalidInputException if no valid CSRF token is present, if no account with the given email address
|
||||
* @throws InvalidTypeException if any of the inputs has the incorrect type
|
||||
* @throws InvalidValueException if no valid CSRF token is present, if no account with the given email address
|
||||
* exists, or if a password reset email was sent too recently
|
||||
*/
|
||||
public function handle(array $inputs): mixed
|
||||
|
@ -64,14 +66,14 @@ class SendPasswordResetAction extends Action
|
|||
$this->user_list->transaction(function () use ($inputs) {
|
||||
$user_data = $this->user_list->get_user_by_email($inputs["email"]);
|
||||
if ($user_data === null)
|
||||
throw new InvalidInputException("No user with that email address has been registered.");
|
||||
throw new InvalidValueException("No user with that email address has been registered.");
|
||||
|
||||
$minutes_left = Util::minutes_until_interval_elapsed(
|
||||
$user_data["password_reset_token_timestamp"],
|
||||
UserList::MINUTES_BETWEEN_PASSWORD_RESETS
|
||||
);
|
||||
if ($minutes_left > 0) {
|
||||
throw new InvalidInputException(
|
||||
throw new InvalidValueException(
|
||||
"A password reset email was sent recently. " .
|
||||
"Please wait $minutes_left more minute(s) before requesting a new email."
|
||||
);
|
||||
|
|
|
@ -4,11 +4,12 @@ namespace com\fwdekker\deathnotifier\user;
|
|||
|
||||
use com\fwdekker\deathnotifier\Action;
|
||||
use com\fwdekker\deathnotifier\UnexpectedException;
|
||||
use com\fwdekker\deathnotifier\validator\InvalidInputException;
|
||||
use com\fwdekker\deathnotifier\validator\IsBooleanRule;
|
||||
use com\fwdekker\deathnotifier\validator\IsValidCsrfTokenRule;
|
||||
use com\fwdekker\deathnotifier\validator\RuleSet;
|
||||
use com\fwdekker\deathnotifier\validator\SessionRuleSet;
|
||||
use com\fwdekker\deathnotifier\validation\InvalidTypeException;
|
||||
use com\fwdekker\deathnotifier\validation\InvalidValueException;
|
||||
use com\fwdekker\deathnotifier\validation\IsBooleanRule;
|
||||
use com\fwdekker\deathnotifier\validation\IsValidCsrfTokenRule;
|
||||
use com\fwdekker\deathnotifier\validation\RuleSet;
|
||||
use com\fwdekker\deathnotifier\validation\LoginValidator;
|
||||
|
||||
|
||||
/**
|
||||
|
@ -41,14 +42,15 @@ class ToggleNotificationsAction extends Action
|
|||
* @param array<int|string, mixed> $inputs `"token": string`: a valid CSRF token, `"enable_notifications": bool`:
|
||||
* `true` if and only if notifications should be enabled
|
||||
* @return null
|
||||
* @throws InvalidInputException if the user is not logged in, if no valid CSRF token is present, if the toggle
|
||||
* value is not a boolean, or if the user's email address it not verified
|
||||
* @throws InvalidTypeException if any of the inputs has the incorrect type
|
||||
* @throws InvalidValueException if the user is not logged in, if no valid CSRF token is present, or if the user's
|
||||
* email address is not verified
|
||||
* @throws UnexpectedException if the current user has been deleted
|
||||
* @noinspection PhpDocRedundantThrowsInspection can be thrown through {@see TrackingList::transaction()}
|
||||
*/
|
||||
public function handle(array $inputs): mixed
|
||||
{
|
||||
(new SessionRuleSet(validate_logged_in: true))->check($_SESSION);
|
||||
(new LoginValidator(validate_logged_in: true))->check($_SESSION);
|
||||
(new RuleSet([
|
||||
"token" => [new IsValidCsrfTokenRule()],
|
||||
"enable_notifications" => [new IsBooleanRule()],
|
||||
|
@ -57,9 +59,9 @@ class ToggleNotificationsAction extends Action
|
|||
$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.");
|
||||
throw new UnexpectedException("Failed to retrieve user data. Please refresh the page and try again.");
|
||||
if ($inputs["enable_notifications"] && $user_data["email_verification_token"] !== null)
|
||||
throw new InvalidInputException("Please verify your email address before toggling notifications.");
|
||||
throw new InvalidValueException("Please verify your email address before toggling notifications.");
|
||||
|
||||
$this->user_list->set_notifications_enabled($_SESSION["uuid"], $inputs["enable_notifications"]);
|
||||
});
|
||||
|
|
|
@ -5,10 +5,11 @@ namespace com\fwdekker\deathnotifier\user;
|
|||
use com\fwdekker\deathnotifier\Action;
|
||||
use com\fwdekker\deathnotifier\UnexpectedException;
|
||||
use com\fwdekker\deathnotifier\Util;
|
||||
use com\fwdekker\deathnotifier\validator\InvalidInputException;
|
||||
use com\fwdekker\deathnotifier\validator\IsValidCsrfTokenRule;
|
||||
use com\fwdekker\deathnotifier\validator\RuleSet;
|
||||
use com\fwdekker\deathnotifier\validator\SessionRuleSet;
|
||||
use com\fwdekker\deathnotifier\validation\InvalidTypeException;
|
||||
use com\fwdekker\deathnotifier\validation\InvalidValueException;
|
||||
use com\fwdekker\deathnotifier\validation\IsValidCsrfTokenRule;
|
||||
use com\fwdekker\deathnotifier\validation\RuleSet;
|
||||
use com\fwdekker\deathnotifier\validation\LoginValidator;
|
||||
use Exception;
|
||||
|
||||
|
||||
|
@ -41,12 +42,13 @@ class UserDeleteAction extends Action
|
|||
*
|
||||
* @param array<int|string, mixed> $inputs `"token": string`: a valid CSRF token
|
||||
* @return null
|
||||
* @throws InvalidInputException if the user is not logged in or if no valid CSRF token is present
|
||||
* @throws InvalidTypeException if any of the inputs has the incorrect type
|
||||
* @throws InvalidValueException if the user is not logged in or if no valid CSRF token is present
|
||||
* @throws UnexpectedException if no new CSRF token could be generated
|
||||
*/
|
||||
public function handle(array $inputs): mixed
|
||||
{
|
||||
(new SessionRuleSet(validate_logged_in: true))->check($_SESSION);
|
||||
(new LoginValidator(validate_logged_in: true))->check($_SESSION);
|
||||
(new RuleSet(["token" => [new IsValidCsrfTokenRule()]]))->check($inputs);
|
||||
|
||||
$this->user_list->remove_user_by_uuid($_SESSION["uuid"]);
|
||||
|
|
|
@ -4,11 +4,12 @@ namespace com\fwdekker\deathnotifier\user;
|
|||
|
||||
use com\fwdekker\deathnotifier\Action;
|
||||
use com\fwdekker\deathnotifier\Util;
|
||||
use com\fwdekker\deathnotifier\validator\InvalidInputException;
|
||||
use com\fwdekker\deathnotifier\validator\IsEmailRule;
|
||||
use com\fwdekker\deathnotifier\validator\IsStringRule;
|
||||
use com\fwdekker\deathnotifier\validator\IsValidCsrfTokenRule;
|
||||
use com\fwdekker\deathnotifier\validator\RuleSet;
|
||||
use com\fwdekker\deathnotifier\validation\InvalidTypeException;
|
||||
use com\fwdekker\deathnotifier\validation\InvalidValueException;
|
||||
use com\fwdekker\deathnotifier\validation\IsEmailRule;
|
||||
use com\fwdekker\deathnotifier\validation\IsStringRule;
|
||||
use com\fwdekker\deathnotifier\validation\IsValidCsrfTokenRule;
|
||||
use com\fwdekker\deathnotifier\validation\RuleSet;
|
||||
|
||||
|
||||
/**
|
||||
|
@ -41,7 +42,8 @@ class ValidatePasswordResetTokenAction extends Action
|
|||
* @param array<int|string, mixed> $inputs `"token": string`: a valid CSRF token, `"email"`: the email address to
|
||||
* validate the password reset token of, `"reset_token": string`: the password reset token to validate
|
||||
* @return null
|
||||
* @throws InvalidInputException if no valid CSRF token is present, if no account with the given email address
|
||||
* @throws InvalidTypeException if any of the inputs has the incorrect type
|
||||
* @throws InvalidValueException if no valid CSRF token is present, if no account with the given email address
|
||||
* exists, if the reset token is invalid, or if the reset token has expired
|
||||
*/
|
||||
public function handle(array $inputs): mixed
|
||||
|
@ -63,16 +65,16 @@ class ValidatePasswordResetTokenAction extends Action
|
|||
* @param string $email the email address to validate the password reset token of
|
||||
* @param string $reset_token the password reset token to validate
|
||||
* @return void
|
||||
* @throws InvalidInputException if no account with the given email address exists, if the reset token is invalid,
|
||||
* @throws InvalidValueException if no account with the given email address exists, if the reset token is invalid,
|
||||
* or if the reset token has expired
|
||||
*/
|
||||
public static function validate_token(UserList $user_list, string $email, string $reset_token): void
|
||||
{
|
||||
$user_data = $user_list->get_user_by_email($email);
|
||||
if ($user_data === null)
|
||||
throw new InvalidInputException("No user with that email address has been registered.");
|
||||
throw new InvalidValueException("No user with that email address has been registered.");
|
||||
if ($reset_token !== $user_data["password_reset_token"])
|
||||
throw new InvalidInputException(
|
||||
throw new InvalidValueException(
|
||||
"This password reset link is invalid. " .
|
||||
"This may happen if you recently changed your email address or you requested another " .
|
||||
"password reset link, in which case a new password reset link should arrive in your inbox soon. " .
|
||||
|
@ -84,7 +86,7 @@ class ValidatePasswordResetTokenAction extends Action
|
|||
UserList::MINUTES_VALID_PASSWORD_RESET
|
||||
);
|
||||
if ($minutes_left < 0) {
|
||||
throw new InvalidInputException(
|
||||
throw new InvalidValueException(
|
||||
`This password reset link has expired. <a href="./">Return to the front page</a> and press the ` .
|
||||
`"Forgot password?" button to request a new password reset link.`
|
||||
);
|
||||
|
|
|
@ -4,11 +4,12 @@ namespace com\fwdekker\deathnotifier\user;
|
|||
|
||||
use com\fwdekker\deathnotifier\Action;
|
||||
use com\fwdekker\deathnotifier\Util;
|
||||
use com\fwdekker\deathnotifier\validator\InvalidInputException;
|
||||
use com\fwdekker\deathnotifier\validator\IsEmailRule;
|
||||
use com\fwdekker\deathnotifier\validator\IsStringRule;
|
||||
use com\fwdekker\deathnotifier\validator\IsValidCsrfTokenRule;
|
||||
use com\fwdekker\deathnotifier\validator\RuleSet;
|
||||
use com\fwdekker\deathnotifier\validation\InvalidTypeException;
|
||||
use com\fwdekker\deathnotifier\validation\InvalidValueException;
|
||||
use com\fwdekker\deathnotifier\validation\IsEmailRule;
|
||||
use com\fwdekker\deathnotifier\validation\IsStringRule;
|
||||
use com\fwdekker\deathnotifier\validation\IsValidCsrfTokenRule;
|
||||
use com\fwdekker\deathnotifier\validation\RuleSet;
|
||||
|
||||
|
||||
/**
|
||||
|
@ -41,7 +42,8 @@ class VerifyEmailAction extends Action
|
|||
* @param array<int|string, mixed> $inputs `"token": string`: a valid CSRF token, `"email"`: the email address to
|
||||
* verify, `"verify_token": string`: the token to verify the email address with
|
||||
* @return null
|
||||
* @throws InvalidInputException if no valid CSRF token is present, if no account with the given email address
|
||||
* @throws InvalidTypeException if any of the inputs has the incorrect type
|
||||
* @throws InvalidValueException if no valid CSRF token is present, if no account with the given email address
|
||||
* exists, if the reset token is invalid, or if the reset token has expired
|
||||
*/
|
||||
public function handle(array $inputs): mixed
|
||||
|
@ -55,11 +57,11 @@ class VerifyEmailAction extends Action
|
|||
$this->user_list->transaction(function () use ($inputs) {
|
||||
$user_data = $this->user_list->get_user_by_email($inputs["email"]);
|
||||
if ($user_data === null)
|
||||
throw new InvalidInputException("No user with that email address has been registered.");
|
||||
throw new InvalidValueException("No user with that email address has been registered.");
|
||||
if ($user_data["email_verification_token"] === null)
|
||||
throw new InvalidInputException("Your email address is already verified. You can close this page.");
|
||||
throw new InvalidValueException("Your email address is already verified. You can close this page.");
|
||||
if ($inputs["verify_token"] !== $user_data["email_verification_token"])
|
||||
throw new InvalidInputException(
|
||||
throw new InvalidValueException(
|
||||
"This verification link is invalid. " .
|
||||
"This may happen if you recently changed your email address or you requested another " .
|
||||
"verification link, in which case a new verification link should arrive in your inbox soon. " .
|
||||
|
@ -71,7 +73,7 @@ class VerifyEmailAction extends Action
|
|||
UserList::MINUTES_VALID_VERIFICATION
|
||||
);
|
||||
if ($minutes_left < 0)
|
||||
throw new InvalidInputException(
|
||||
throw new InvalidValueException(
|
||||
"This email verification link has expired. Log in and request a new verification email."
|
||||
);
|
||||
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
<?php
|
||||
|
||||
namespace com\fwdekker\deathnotifier\validator;
|
||||
namespace com\fwdekker\deathnotifier\validation;
|
||||
|
||||
use com\fwdekker\deathnotifier\Config;
|
||||
use com\fwdekker\deathnotifier\IllegalArgumentError;
|
||||
use com\fwdekker\deathnotifier\IllegalStateError;
|
||||
|
||||
|
||||
/**
|
||||
|
@ -27,22 +27,21 @@ class EqualsCliPasswordRule extends Rule
|
|||
* @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 equals the CLI password
|
||||
* @throws InvalidInputException if the checked input does not equal the CLI password
|
||||
* @throws IllegalArgumentError if the CLI password is a blank string, if the CLI password is at its default value,
|
||||
* if the checked input is not set
|
||||
* @throws InvalidTypeException if the CLI password is a blank string, if the CLI password is at its default
|
||||
* value, or if the checked input is not set
|
||||
* @throws InvalidValueException if the checked input does not equal the CLI password
|
||||
*/
|
||||
public function check(array $inputs, string $key): void
|
||||
{
|
||||
if (!Config::has(self::CONFIG_KEY) || trim(Config::get(self::CONFIG_KEY)) === "")
|
||||
throw new IllegalArgumentError("The CLI is disabled because the CLI password is not set.");
|
||||
|
||||
throw new IllegalStateError("The CLI is disabled because the CLI password is not set.");
|
||||
if (Config::get(self::CONFIG_KEY) === self::DEFAULT)
|
||||
throw new IllegalArgumentError("The CLI is disabled because the CLI password is set to the default.");
|
||||
throw new IllegalStateError("The CLI is disabled because the CLI password is set to the default.");
|
||||
|
||||
if (!isset($inputs[$key]))
|
||||
throw new InvalidInputException("This operation requires the CLI password.", $key);
|
||||
throw new InvalidTypeException("This operation requires the CLI password.");
|
||||
|
||||
if (!password_verify($inputs[$key], Config::get(self::CONFIG_KEY)))
|
||||
throw new InvalidInputException("Incorrect CLI password.", $key);
|
||||
throw new InvalidValueException("Incorrect CLI password.", $key);
|
||||
}
|
||||
}
|
|
@ -1,6 +1,8 @@
|
|||
<?php
|
||||
|
||||
namespace com\fwdekker\deathnotifier\validator;
|
||||
namespace com\fwdekker\deathnotifier\validation;
|
||||
|
||||
use InvalidArgumentException;
|
||||
|
||||
|
||||
/**
|
||||
|
@ -26,6 +28,11 @@ class HasStringLengthRule extends Rule
|
|||
*/
|
||||
public function __construct(?int $min_length = null, ?int $max_length = null)
|
||||
{
|
||||
if ($min_length !== null && $max_length !== null && $min_length > $max_length)
|
||||
throw new InvalidArgumentException("Minimum length should not exceed maximum length.");
|
||||
if ($min_length === null && $max_length === null)
|
||||
throw new InvalidArgumentException("Either minimum length or maximum length should be non-null.");
|
||||
|
||||
$this->min_length = $min_length;
|
||||
$this->max_length = $max_length;
|
||||
}
|
||||
|
@ -37,18 +44,23 @@ class HasStringLengthRule extends Rule
|
|||
* @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 of the specified length
|
||||
* @throws InvalidInputException if the checked input is not set or is not of the specified length
|
||||
* @throws InvalidTypeException if the checked input is not set or is not a string
|
||||
* @throws InvalidValueException if the checked input is not of the specified length
|
||||
*/
|
||||
public function check(array $inputs, string $key): void
|
||||
{
|
||||
if (!isset($inputs[$key]))
|
||||
throw new InvalidInputException("Missing input '$key'.", $key);
|
||||
throw new InvalidTypeException("Required input '$key' not set.");
|
||||
if (!is_string($inputs[$key]))
|
||||
throw new InvalidInputException("Field must be a string.", $key);
|
||||
throw new InvalidTypeException("Input '$key' should be string, but is " . gettype($inputs[$key]) . ".");
|
||||
|
||||
if ($this->min_length !== null && strlen($inputs[$key]) < $this->min_length)
|
||||
throw new InvalidInputException("Use at least $this->min_length character(s).", $key);
|
||||
if ($this->max_length !== null && strlen($inputs[$key]) > $this->max_length)
|
||||
throw new InvalidInputException("Use at most $this->max_length character(s).", $key);
|
||||
if ($this->min_length !== null && strlen($inputs[$key]) < $this->min_length) {
|
||||
$characters = $this->min_length !== 1 ? "characters" : "character";
|
||||
throw new InvalidValueException("Use at least $this->min_length $characters.", $key);
|
||||
}
|
||||
if ($this->max_length !== null && strlen($inputs[$key]) > $this->max_length) {
|
||||
$characters = $this->min_length !== 1 ? "characters" : "character";
|
||||
throw new InvalidValueException("Use at most $this->max_length $characters.", $key);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
<?php
|
||||
|
||||
namespace com\fwdekker\deathnotifier\validation;
|
||||
|
||||
use com\fwdekker\deathnotifier\MalformedRequestException;
|
||||
|
||||
|
||||
/**
|
||||
* Thrown to indicate that a request to the server is missing a required input or has an input of the incorrect type.
|
||||
*
|
||||
* If the input is set and of the correct type, but is invalid for another reason, throw {@see InvalidValueException}
|
||||
* instead.
|
||||
*/
|
||||
class InvalidTypeException extends MalformedRequestException
|
||||
{
|
||||
// Intentionally left empty
|
||||
}
|
|
@ -1,6 +1,6 @@
|
|||
<?php
|
||||
|
||||
namespace com\fwdekker\deathnotifier\validator;
|
||||
namespace com\fwdekker\deathnotifier\validation;
|
||||
|
||||
use com\fwdekker\deathnotifier\MalformedRequestException;
|
||||
use Throwable;
|
||||
|
@ -8,8 +8,10 @@ use Throwable;
|
|||
|
||||
/**
|
||||
* Thrown to indicate that a request to the server contains an invalid input.
|
||||
*
|
||||
* If the input is not set or is of the incorrect type, throw {@see InvalidTypeException} instead.
|
||||
*/
|
||||
class InvalidInputException extends MalformedRequestException
|
||||
class InvalidValueException extends MalformedRequestException
|
||||
{
|
||||
/**
|
||||
* @var string|null the input element that caused the exception, or `null` if no such element could be identified
|
||||
|
@ -18,7 +20,7 @@ class InvalidInputException extends MalformedRequestException
|
|||
|
||||
|
||||
/**
|
||||
* Constructs a new `InvalidInputException`.
|
||||
* Constructs a new `InvalidValueException`.
|
||||
*
|
||||
* @param string $message the message to show to the user
|
||||
* @param string|null $target the input that caused the exception, or `null` if no such element could be identified
|
|
@ -1,6 +1,6 @@
|
|||
<?php
|
||||
|
||||
namespace com\fwdekker\deathnotifier\validator;
|
||||
namespace com\fwdekker\deathnotifier\validation;
|
||||
|
||||
|
||||
/**
|
||||
|
@ -14,11 +14,13 @@ class IsBooleanRule extends Rule
|
|||
* @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
|
||||
* @throws InvalidTypeException if the checked input is not set or 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("Field '" . htmlentities($key) . "' must be a boolean.", $key);
|
||||
if (!isset($inputs[$key]))
|
||||
throw new InvalidTypeException("Required input '$key' not set.");
|
||||
if (!is_bool($inputs[$key]))
|
||||
throw new InvalidTypeException("Input '$key' should be boolean, but is " . gettype($inputs[$key]) . ".");
|
||||
}
|
||||
}
|
|
@ -1,8 +1,6 @@
|
|||
<?php
|
||||
|
||||
namespace com\fwdekker\deathnotifier\validator;
|
||||
|
||||
use com\fwdekker\deathnotifier\IllegalArgumentError;
|
||||
namespace com\fwdekker\deathnotifier\validation;
|
||||
|
||||
|
||||
/**
|
||||
|
@ -16,28 +14,28 @@ class IsEmailRule extends Rule
|
|||
* @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 an email address
|
||||
* @throws InvalidInputException if the checked input is not an email address
|
||||
* @throws IllegalArgumentError if the checked input is not set or is not a string
|
||||
* @throws InvalidTypeException if the checked input is not set or is not a string
|
||||
* @throws InvalidValueException if the checked input is not an email address
|
||||
*/
|
||||
public function check(array $inputs, string $key): void
|
||||
{
|
||||
if (!isset($inputs[$key]))
|
||||
throw new IllegalArgumentError("Required input '$key' not set.");
|
||||
throw new InvalidTypeException("Required input '$key' not set.");
|
||||
if (!is_string($inputs[$key]))
|
||||
throw new IllegalArgumentError("Input '$key' should be string, but is " . gettype($inputs[$key]) . ".");
|
||||
throw new InvalidTypeException("Input '$key' should be string, but is " . gettype($inputs[$key]) . ".");
|
||||
|
||||
$input = $inputs[$key];
|
||||
if (str_starts_with($input, " "))
|
||||
throw new InvalidInputException("Remove the spaces at the start.", $key);
|
||||
throw new InvalidValueException("Remove the spaces at the start.", $key);
|
||||
if (str_ends_with($input, " "))
|
||||
throw new InvalidInputException("Remove the spaces at the end.", $key);
|
||||
throw new InvalidValueException("Remove the spaces at the end.", $key);
|
||||
if ($input === "")
|
||||
throw new InvalidInputException("Enter an email address.", $key);
|
||||
throw new InvalidValueException("Enter an email address.", $key);
|
||||
if (!str_contains($input, "@"))
|
||||
throw new InvalidInputException("Don't forget to add an '@' symbol.", $key);
|
||||
throw new InvalidValueException("Don't forget to add an '@' symbol.", $key);
|
||||
if (str_ends_with($input, "@"))
|
||||
throw new InvalidInputException("Add a domain name after the '@'.", $key);
|
||||
throw new InvalidValueException("Add a domain name after the '@'.", $key);
|
||||
if (!filter_var($input, FILTER_VALIDATE_EMAIL))
|
||||
throw new InvalidInputException("Enter a valid email address.", $key);
|
||||
throw new InvalidValueException("Enter a valid email address.", $key);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,33 @@
|
|||
<?php
|
||||
|
||||
namespace com\fwdekker\deathnotifier\validation;
|
||||
|
||||
|
||||
/**
|
||||
* Validates that the input is not a blank string.
|
||||
*/
|
||||
class IsNotBlankRule extends Rule
|
||||
{
|
||||
/**
|
||||
* Validates that the input is not a blank string.
|
||||
*
|
||||
* @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 not a blank string
|
||||
* @throws InvalidTypeException if the checked input is not set or is not a string
|
||||
* @throws InvalidValueException if the checked input is a blank string
|
||||
*/
|
||||
public function check(array $inputs, string $key): void
|
||||
{
|
||||
if (!isset($inputs[$key]))
|
||||
throw new InvalidTypeException("Required input '$key' not set.");
|
||||
if (!is_string($inputs[$key]))
|
||||
throw new InvalidTypeException("Input '$key' should be string, but is " . gettype($inputs[$key]) . ".");
|
||||
|
||||
if ($inputs[$key] === "")
|
||||
throw new InvalidValueException("Use at least one character.", $key);
|
||||
if (trim($inputs[$key]) === "")
|
||||
throw new InvalidValueException("Use at least one character other than a space.", $key);
|
||||
|
||||
}
|
||||
}
|
|
@ -1,6 +1,6 @@
|
|||
<?php
|
||||
|
||||
namespace com\fwdekker\deathnotifier\validator;
|
||||
namespace com\fwdekker\deathnotifier\validation;
|
||||
|
||||
|
||||
/**
|
||||
|
@ -14,11 +14,13 @@ class IsStringRule extends Rule
|
|||
* @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 string
|
||||
* @throws InvalidInputException if the checked input is not a string
|
||||
* @throws InvalidTypeException if the checked input is not set or if the checked input is not a string
|
||||
*/
|
||||
public function check(array $inputs, string $key): void
|
||||
{
|
||||
if (!isset($inputs[$key]) || !is_string($inputs[$key]))
|
||||
throw new InvalidInputException("Field '" . htmlentities($key) . "' must be a string.", $key);
|
||||
if (!isset($inputs[$key]))
|
||||
throw new InvalidTypeException("Required input '$key' not set.");
|
||||
if (!is_string($inputs[$key]))
|
||||
throw new InvalidTypeException("Input '$key' should be string, but is " . gettype($inputs[$key]) . ".");
|
||||
}
|
||||
}
|
|
@ -0,0 +1,30 @@
|
|||
<?php
|
||||
|
||||
namespace com\fwdekker\deathnotifier\validation;
|
||||
|
||||
|
||||
/**
|
||||
* Validates that the input is a valid CSRF token.
|
||||
*/
|
||||
class IsValidCsrfTokenRule extends Rule
|
||||
{
|
||||
/**
|
||||
* Validates that the input is a valid CSRF token.
|
||||
*
|
||||
* @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 valid CSRF token
|
||||
* @throws InvalidTypeException if the checked input is not set or if the checked input is not a string
|
||||
* @throws InvalidValueException if the checked input is not a valid CSRF token
|
||||
*/
|
||||
public function check(array $inputs, string $key): void
|
||||
{
|
||||
if (!isset($inputs[$key]))
|
||||
throw new InvalidTypeException("Missing CSRF token.");
|
||||
if (!is_string($inputs[$key]))
|
||||
throw new InvalidTypeException("CSRF token should be string, but is " . gettype($inputs[$key]) . ".");
|
||||
|
||||
if ($inputs[$key] !== $_SESSION["token"])
|
||||
throw new InvalidValueException("Invalid CSRF token. Please refresh the page and try again.", $key);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,60 @@
|
|||
<?php
|
||||
|
||||
namespace com\fwdekker\deathnotifier\validation;
|
||||
|
||||
use com\fwdekker\deathnotifier\IllegalArgumentError;
|
||||
|
||||
|
||||
/**
|
||||
* Like a {@see RuleSet}, but specifically to validate whether the user is logged in.
|
||||
*/
|
||||
class LoginValidator
|
||||
{
|
||||
/**
|
||||
* @var bool `true` if and only if this validator should check if the user is logged in
|
||||
*/
|
||||
private readonly bool $validate_logged_in;
|
||||
/**
|
||||
* @var bool `true` if and only if this validator should check if the user is logged out
|
||||
*/
|
||||
private readonly bool $validate_logged_out;
|
||||
|
||||
|
||||
/**
|
||||
* Constructs a new `LoginValidator`.
|
||||
*
|
||||
* @param bool $validate_logged_in `true` if and only if this validator should check if the user is logged in
|
||||
* @param bool $validate_logged_out `true` if and only if this validator should check if the user is logged out
|
||||
*/
|
||||
public function __construct(bool $validate_logged_in = false, bool $validate_logged_out = false)
|
||||
{
|
||||
if ($validate_logged_in && $validate_logged_out)
|
||||
throw new IllegalArgumentError("Cannot require that user is both logged in and logged out.");
|
||||
if (!$validate_logged_in && !$validate_logged_out)
|
||||
throw new IllegalArgumentError("Must require that user is either logged in or logged out.");
|
||||
|
||||
$this->validate_logged_in = $validate_logged_in;
|
||||
$this->validate_logged_out = $validate_logged_out;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Checks whether the user's login status is as desired.
|
||||
*
|
||||
* @param array<int|string, mixed> $inputs the list of inputs to validate
|
||||
* @return void if the user's login status is as desired
|
||||
* @throws InvalidValueException if the user's login status is not as desired
|
||||
*/
|
||||
public function check(array $inputs): void
|
||||
{
|
||||
if ($this->validate_logged_in && !isset($inputs["uuid"]))
|
||||
throw new InvalidValueException(
|
||||
"You must be logged in to perform this action. Please refresh the page and try again."
|
||||
);
|
||||
|
||||
if ($this->validate_logged_out && isset($inputs["uuid"]))
|
||||
throw new InvalidValueException(
|
||||
"You must be logged out to perform this action. Please refresh the page and try again."
|
||||
);
|
||||
}
|
||||
}
|
|
@ -1,8 +1,6 @@
|
|||
<?php
|
||||
|
||||
namespace com\fwdekker\deathnotifier\validator;
|
||||
|
||||
use com\fwdekker\deathnotifier\IllegalArgumentError;
|
||||
namespace com\fwdekker\deathnotifier\validation;
|
||||
|
||||
|
||||
/**
|
||||
|
@ -16,8 +14,8 @@ abstract class Rule
|
|||
* @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 rule holds for the checked input
|
||||
* @throws InvalidInputException if the rule does not hold for the checked input
|
||||
* @throws IllegalArgumentError if the checked input is not set or of the wrong type
|
||||
* @throws InvalidTypeException if the checked input is not set or of the wrong type
|
||||
* @throws InvalidValueException if the rule does not hold for the checked input
|
||||
*/
|
||||
public abstract function check(array $inputs, string $key): void;
|
||||
}
|
|
@ -0,0 +1,43 @@
|
|||
<?php
|
||||
|
||||
namespace com\fwdekker\deathnotifier\validation;
|
||||
|
||||
|
||||
/**
|
||||
* A set of {@see Rule Rules} to apply to an array of inputs.
|
||||
*/
|
||||
class RuleSet
|
||||
{
|
||||
/**
|
||||
* @var array<string, array<Rule>> a mapping from input keys to the array of rules to apply to that input
|
||||
*/
|
||||
private readonly array $rule_lists;
|
||||
|
||||
|
||||
/**
|
||||
* Constructs a new `RuleSet`.
|
||||
*
|
||||
* @param array<string, array<Rule>> $rule_lists a mapping from input keys to the array of rules to apply to that
|
||||
* input
|
||||
*/
|
||||
public function __construct(array $rule_lists)
|
||||
{
|
||||
$this->rule_lists = $rule_lists;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Verifies that the input is of the specific length.
|
||||
*
|
||||
* @param array<int|string, mixed> $inputs the list of inputs to validate
|
||||
* @return void if the inputs satisfy the rules of this rule set
|
||||
* @throws InvalidTypeException if any rule throws this exception for any input
|
||||
* @throws InvalidValueException if any rule throws this exception for any input
|
||||
*/
|
||||
public function check(array $inputs): void
|
||||
{
|
||||
foreach ($this->rule_lists as $key => $rule_list)
|
||||
foreach ($rule_list as $rule)
|
||||
$rule->check($inputs, $key);
|
||||
}
|
||||
}
|
|
@ -1,24 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace com\fwdekker\deathnotifier\validator;
|
||||
|
||||
|
||||
/**
|
||||
* Validates that the input is not a blank string.
|
||||
*/
|
||||
class IsNotBlankRule extends Rule
|
||||
{
|
||||
/**
|
||||
* Validates that the input is not a blank string.
|
||||
*
|
||||
* @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 not a blank string
|
||||
* @throws InvalidInputException if the checked input is not set, is not a string, or is a blank string
|
||||
*/
|
||||
public function check(array $inputs, string $key): void
|
||||
{
|
||||
if (!isset($inputs[$key]) || !is_string($inputs[$key]) || trim($inputs[$key]) === "")
|
||||
throw new InvalidInputException("Use at least one character.", $key);
|
||||
}
|
||||
}
|
|
@ -1,45 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace com\fwdekker\deathnotifier\validator;
|
||||
|
||||
|
||||
/**
|
||||
* Validates that the input is not set.
|
||||
*/
|
||||
class IsNotSetRule extends Rule
|
||||
{
|
||||
/**
|
||||
* @var string|null the message to return if the input is not set, or `null` to show the default message
|
||||
*/
|
||||
public readonly ?string $override_message;
|
||||
|
||||
|
||||
/**
|
||||
* Instantiates a new `IsSetRule`.
|
||||
*
|
||||
* @param string|null $override_message the message to return if the input is not set, or `null` to show the default
|
||||
* message
|
||||
*/
|
||||
public function __construct(?string $override_message = null)
|
||||
{
|
||||
$this->override_message = $override_message;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Validates that the input is not set.
|
||||
*
|
||||
* @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 not set
|
||||
* @throws InvalidInputException if the checked input is set
|
||||
*/
|
||||
public function check(array $inputs, string $key): void
|
||||
{
|
||||
if (isset($inputs[$key]))
|
||||
throw new InvalidInputException(
|
||||
$this->override_message ?? "Field '" . htmlentities($key) . "' must not be set.",
|
||||
$key
|
||||
);
|
||||
}
|
||||
}
|
|
@ -1,45 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace com\fwdekker\deathnotifier\validator;
|
||||
|
||||
|
||||
/**
|
||||
* Validates that the input is set.
|
||||
*/
|
||||
class IsSetRule extends Rule
|
||||
{
|
||||
/**
|
||||
* @var string|null the message to return if the input is not set, or `null` to show the default message
|
||||
*/
|
||||
public readonly ?string $override_message;
|
||||
|
||||
|
||||
/**
|
||||
* Instantiates a new `IsSetRule`.
|
||||
*
|
||||
* @param string|null $override_message the message to return if the input is not set, or `null` to show the default
|
||||
* message
|
||||
*/
|
||||
public function __construct(?string $override_message = null)
|
||||
{
|
||||
$this->override_message = $override_message;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Validates that the input is set.
|
||||
*
|
||||
* @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 set
|
||||
* @throws InvalidInputException if the checked input is not set
|
||||
*/
|
||||
public function check(array $inputs, string $key): void
|
||||
{
|
||||
if (!isset($inputs[$key]))
|
||||
throw new InvalidInputException(
|
||||
$this->override_message ?? "Field '" . htmlentities($key) . "' must be set.",
|
||||
$key
|
||||
);
|
||||
}
|
||||
}
|
|
@ -1,24 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace com\fwdekker\deathnotifier\validator;
|
||||
|
||||
|
||||
/**
|
||||
* Validates that the input is a valid CSRF token.
|
||||
*/
|
||||
class IsValidCsrfTokenRule extends Rule
|
||||
{
|
||||
/**
|
||||
* Validates that the input is a valid CSRF token.
|
||||
*
|
||||
* @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 valid CSRF token
|
||||
* @throws InvalidInputException if the checked input is not a valid CSRF token
|
||||
*/
|
||||
public function check(array $inputs, string $key): void
|
||||
{
|
||||
if (!isset($inputs[$key]) || $inputs[$key] !== $_SESSION["token"])
|
||||
throw new InvalidInputException("Invalid request token. Please refresh the page and try again.", $key);
|
||||
}
|
||||
}
|
|
@ -1,41 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace com\fwdekker\deathnotifier\validator;
|
||||
|
||||
|
||||
/**
|
||||
* A set of {@see Rule Rules} to apply to an array of inputs.
|
||||
*/
|
||||
class RuleSet
|
||||
{
|
||||
/**
|
||||
* @var array<string, array<Rule>> the rules to apply to the inputs
|
||||
*/
|
||||
private readonly array $rule_set;
|
||||
|
||||
|
||||
/**
|
||||
* Constructs a new `RuleSet`.
|
||||
*
|
||||
* @param array<string, array<Rule>> $rules the rules to apply to the inputs
|
||||
*/
|
||||
public function __construct(array $rules)
|
||||
{
|
||||
$this->rule_set = $rules;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Verifies that the input is of the specific length.
|
||||
*
|
||||
* @param array<int|string, mixed> $inputs the list of inputs to validate
|
||||
* @return void if the inputs satisfy the rules of this rule set
|
||||
* @throws InvalidInputException if any input does not satisfy any rule of this rule set
|
||||
*/
|
||||
public function check(array $inputs): void
|
||||
{
|
||||
foreach ($this->rule_set as $key => $rules)
|
||||
foreach ($rules as $rule)
|
||||
$rule->check($inputs, $key);
|
||||
}
|
||||
}
|
|
@ -1,35 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace com\fwdekker\deathnotifier\validator;
|
||||
|
||||
use com\fwdekker\deathnotifier\IllegalArgumentError;
|
||||
|
||||
|
||||
/**
|
||||
* A {@see RuleSet} specifically to validate the current session.
|
||||
*/
|
||||
class SessionRuleSet extends RuleSet
|
||||
{
|
||||
/**
|
||||
* Constructs a new `RuleSet`.
|
||||
*
|
||||
* @param bool $validate_logged_in `true` if and only if this rule set should validate that the user is logged in
|
||||
* @param bool $validate_logged_out `true` if and only if this rule set should validate that the user is logged out
|
||||
*/
|
||||
public function __construct(bool $validate_logged_in = false,
|
||||
bool $validate_logged_out = false)
|
||||
{
|
||||
if ($validate_logged_in && $validate_logged_out)
|
||||
throw new IllegalArgumentError("Cannot require that user is both logged in and logged out.");
|
||||
|
||||
$rules = [];
|
||||
if ($validate_logged_in)
|
||||
$rules["uuid"] =
|
||||
[new IsSetRule("You must be logged in to perform this action. Refresh the page and try again.")];
|
||||
if ($validate_logged_out)
|
||||
$rules["uuid"] =
|
||||
[new IsNotSetRule("You must be logged out to perform this action. Refresh the page and try again.")];
|
||||
|
||||
parent::__construct($rules);
|
||||
}
|
||||
}
|
|
@ -1,6 +1,6 @@
|
|||
<?php
|
||||
|
||||
namespace com\fwdekker\deathnotifier\validator;
|
||||
namespace com\fwdekker\deathnotifier\validation;
|
||||
|
||||
use com\fwdekker\deathnotifier\Config;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
@ -91,6 +91,16 @@ class ConfigTest extends TestCase
|
|||
self::assertEquals("value", Config::get("test_section.property"));
|
||||
}
|
||||
|
||||
public function test_get_returns_null_if_section_does_not_exist(): void
|
||||
{
|
||||
self::assertNull(Config::get("test_section"));
|
||||
}
|
||||
|
||||
public function test_get_returns_null_if_property_does_not_exist(): void
|
||||
{
|
||||
self::assertNull(Config::get("test_section.property"));
|
||||
}
|
||||
|
||||
|
||||
public function test_reset(): void
|
||||
{
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
<?php
|
||||
|
||||
namespace com\fwdekker\deathnotifier\validator;
|
||||
namespace com\fwdekker\deathnotifier\validation;
|
||||
|
||||
use com\fwdekker\deathnotifier\Config;
|
||||
use com\fwdekker\deathnotifier\IllegalArgumentError;
|
||||
use com\fwdekker\deathnotifier\IllegalStateError;
|
||||
use com\fwdekker\deathnotifier\MalformedRequestException;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Throwable;
|
||||
|
||||
|
@ -16,20 +17,16 @@ class EqualsCliPasswordRuleTest extends TestCase
|
|||
/**
|
||||
* Tests the output of {@see EqualsCliPasswordRule::check()}.
|
||||
*
|
||||
* @param string $name the name to give to the test case
|
||||
* @param string|null $password the password that is set in the configuration
|
||||
* @param string|null $input the user's input
|
||||
* @param class-string<Throwable>|null $exception the exception that is asserted to be thrown
|
||||
* @param string|null $exception_message the exception message that is asserted
|
||||
* @return void
|
||||
* @throws InvalidInputException if {@see $exception} is `null` but an exception is thrown
|
||||
* @throws MalformedRequestException never
|
||||
* @dataProvider check_provider
|
||||
*/
|
||||
public function test_check(string $name, ?string $password, ?string $input, ?string $exception,
|
||||
?string $exception_message): void
|
||||
public function test_check(?string $password, ?string $input, ?string $exception, ?string $exception_message): void
|
||||
{
|
||||
self::setName($name);
|
||||
|
||||
if ($exception !== null)
|
||||
self::expectException($exception);
|
||||
if ($exception_message !== null)
|
||||
|
@ -46,23 +43,24 @@ class EqualsCliPasswordRuleTest extends TestCase
|
|||
/**
|
||||
* Returns the test cases.
|
||||
*
|
||||
* @return array<array{string, string|null, string|null, class-string<Throwable>|null, string|null}> the test cases
|
||||
* @return array<string, array{string|null, string|null, class-string<Throwable>|null, string|null}> the test cases
|
||||
* @see RuleTest::test_check()
|
||||
*/
|
||||
public function check_provider(): array
|
||||
{
|
||||
$hash = "\$2y\$04\$fwXTw7Rjzw0EpU094u4agOBaBNqtCHGc4TMoxfbPrxuqO5tpYyRka"; # Hash of "password"
|
||||
|
||||
$error = IllegalArgumentError::class;
|
||||
$exception = InvalidInputException::class;
|
||||
$error = IllegalStateError::class;
|
||||
$type = InvalidTypeException::class;
|
||||
$value = InvalidValueException::class;
|
||||
|
||||
return [
|
||||
["error if password is not set", null, "input", $error, "The CLI is disabled because the CLI password is not set."],
|
||||
["error if password is blank", " ", "input", $error, "The CLI is disabled because the CLI password is not set."],
|
||||
["error if password is default", EqualsCliPasswordRule::DEFAULT, "input", $error, "The CLI is disabled because the CLI password is set to the default."],
|
||||
["exception if input is not set", $hash, null, $exception, "This operation requires the CLI password."],
|
||||
["exception if input is incorrect", $hash, "incorrect", $exception, "Incorrect CLI password."],
|
||||
["no exception if input is correct", $hash, "password", null, null],
|
||||
"error if password is not set" => [null, "input", $error, "The CLI is disabled because the CLI password is not set."],
|
||||
"error if password is blank" => [" ", "input", $error, "The CLI is disabled because the CLI password is not set."],
|
||||
"error if password is default" => [EqualsCliPasswordRule::DEFAULT, "input", $error, "The CLI is disabled because the CLI password is set to the default."],
|
||||
"exception if input is not set" => [$hash, null, $type, "This operation requires the CLI password."],
|
||||
"exception if input is incorrect" => [$hash, "incorrect", $value, "Incorrect CLI password."],
|
||||
"no exception if input is correct" => [$hash, "password", null, null],
|
||||
];
|
||||
}
|
||||
}
|
|
@ -0,0 +1,47 @@
|
|||
<?php
|
||||
|
||||
namespace com\fwdekker\deathnotifier\validation;
|
||||
|
||||
use InvalidArgumentException;
|
||||
|
||||
|
||||
/**
|
||||
* Unit tests for {@see HasStringLengthRule}.
|
||||
*/
|
||||
class HasStringLengthRuleTest extends RuleTest
|
||||
{
|
||||
public function check_provider(): array
|
||||
{
|
||||
$type = InvalidTypeException::class;
|
||||
$value = InvalidValueException::class;
|
||||
|
||||
return [
|
||||
"exception if input is not set" => [new HasStringLengthRule(2, 3), null, $type, "Required input 'key' not set."],
|
||||
"exception if input is not a string" => [new HasStringLengthRule(2, 3), 221, $type, "Input 'key' should be string, but is integer."],
|
||||
"exception if input is too short" => [new HasStringLengthRule(3, 4), "a", $value, "Use at least 3 characters."],
|
||||
"exception if input is too long" => [new HasStringLengthRule(4, 6), "long-input", $value, "Use at most 6 characters."],
|
||||
"exception if input is too short and there is no maximum" => [new HasStringLengthRule(3, null), "a", $value, "Use at least 3 characters."],
|
||||
"exception if input is too long and there is no minimum" => [new HasStringLengthRule(null, 6), "long-input", $value, "Use at most 6 characters."],
|
||||
"no exception if input is minimum length" => [new HasStringLengthRule(2, 5), "je", null, null],
|
||||
"no exception if input is maximum length" => [new HasStringLengthRule(3, 4), "word", null, null],
|
||||
"no exception if input is within boundaries" => [new HasStringLengthRule(4, 7), "string", null, null],
|
||||
"no exception if input is empty and there is no minimum" => [new HasStringLengthRule(null, 4), "te", null, null],
|
||||
"no exception if input is long and there is no maximum" => [new HasStringLengthRule(2, null), "a-very-long-string", null, null],
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
public function test_constructor_throws_error_if_both_limits_are_null(): void
|
||||
{
|
||||
self::expectException(InvalidArgumentException::class);
|
||||
|
||||
new HasStringLengthRule();
|
||||
}
|
||||
|
||||
public function test_constructor_throws_error_if_minimum_exceeds_maximum(): void
|
||||
{
|
||||
self::expectException(InvalidArgumentException::class);
|
||||
|
||||
new HasStringLengthRule(4, 2);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
<?php
|
||||
|
||||
namespace com\fwdekker\deathnotifier\validation;
|
||||
|
||||
|
||||
/**
|
||||
* Unit tests for {@see IsBooleanRule}.
|
||||
*/
|
||||
class IsBooleanRuleTest extends RuleTest
|
||||
{
|
||||
public function check_provider(): array
|
||||
{
|
||||
$type = InvalidTypeException::class;
|
||||
|
||||
return [
|
||||
"exception if input is not set" => [new IsBooleanRule(), null, $type, "Required input 'key' not set."],
|
||||
"exception if input is integer" => [new IsBooleanRule(), 1, $type, "Input 'key' should be boolean, but is integer."],
|
||||
"exception if input is string" => [new IsBooleanRule(), "true", $type, "Input 'key' should be boolean, but is string."],
|
||||
"no exception if input is `true`" => [new IsBooleanRule(), true, null, null],
|
||||
"no exception if input is `false`" => [new IsBooleanRule(), false, null, null],
|
||||
];
|
||||
}
|
||||
}
|
|
@ -0,0 +1,28 @@
|
|||
<?php
|
||||
|
||||
namespace com\fwdekker\deathnotifier\validation;
|
||||
|
||||
|
||||
/**
|
||||
* Unit tests for {@see IsEmailRule}.
|
||||
*/
|
||||
class IsEmailRuleTest extends RuleTest
|
||||
{
|
||||
public function check_provider(): array
|
||||
{
|
||||
$type = InvalidTypeException::class;
|
||||
$value = InvalidValueException::class;
|
||||
|
||||
return [
|
||||
"exception if input is not set" => [new IsEmailRule(), null, $type, "Required input 'key' not set."],
|
||||
"exception if input is not a string" => [new IsEmailRule(), 491, $type, "Input 'key' should be string, but is integer."],
|
||||
"exception if input starts with space" => [new IsEmailRule(), " valid@example.com", $value, "Remove the spaces at the start."],
|
||||
"exception if input ends with space" => [new IsEmailRule(), "valid@example.com ", $value, "Remove the spaces at the end."],
|
||||
"exception if input is empty" => [new IsEmailRule(), "", $value, "Enter an email address."],
|
||||
"exception if input misses the '@' symbol" => [new IsEmailRule(), "invalid", $value, "Don't forget to add an '@' symbol."],
|
||||
"exception if input misses the domain part" => [new IsEmailRule(), "invalid@", $value, "Add a domain name after the '@'."],
|
||||
"exception if input is invalid in a complex way" => [new IsEmailRule(), "invalid@example", $value, "Enter a valid email address."],
|
||||
"no exception if input is email" => [new IsEmailRule(), "valid@example.com", null, null],
|
||||
];
|
||||
}
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
<?php
|
||||
|
||||
namespace com\fwdekker\deathnotifier\validation;
|
||||
|
||||
|
||||
/**
|
||||
* Unit tests for {@see IsNotBlankRule}.
|
||||
*/
|
||||
class IsNotBlankRuleTest extends RuleTest
|
||||
{
|
||||
public function check_provider(): array
|
||||
{
|
||||
$type = InvalidTypeException::class;
|
||||
$value = InvalidValueException::class;
|
||||
|
||||
return [
|
||||
"exception if input is not set" => [new IsNotBlankRule(), null, $type, "Required input 'key' not set."],
|
||||
"exception if input is not a string" => [new IsNotBlankRule(), 621, $type, "Input 'key' should be string, but is integer."],
|
||||
"exception if input is empty" => [new IsNotBlankRule(), "", $value, "Use at least one character."],
|
||||
"exception if input is whitespace only" => [new IsNotBlankRule(), " ", $value, "Use at least one character other than a space."],
|
||||
"no exception if input is not blank" => [new IsNotBlankRule(), "not-blank", null, null],
|
||||
"no exception if input is not blank but starts with whitespace" => [new IsNotBlankRule(), " not-blank ", null, null],
|
||||
];
|
||||
}
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
<?php
|
||||
|
||||
namespace com\fwdekker\deathnotifier\validation;
|
||||
|
||||
|
||||
/**
|
||||
* Unit tests for {@see IsStringRule}.
|
||||
*/
|
||||
class IsStringRuleTest extends RuleTest
|
||||
{
|
||||
public function check_provider(): array
|
||||
{
|
||||
$type = InvalidTypeException::class;
|
||||
|
||||
return [
|
||||
"exception if input is not set" => [new IsStringRule(), null, $type, "Required input 'key' not set."],
|
||||
"exception if input is not a string" => [new IsStringRule(), 47, $type, "Input 'key' should be string, but is integer."],
|
||||
"no exception if input is the empty string" => [new IsStringRule(), "", null, null],
|
||||
"no exception if input is a blank string" => [new IsStringRule(), " ", null, null],
|
||||
"no exception if input is a string" => [new IsStringRule(), "not-empty", null, null],
|
||||
];
|
||||
}
|
||||
}
|
|
@ -0,0 +1,44 @@
|
|||
<?php /** @noinspection PhpUnhandledExceptionInspection */
|
||||
|
||||
namespace com\fwdekker\deathnotifier\validation;
|
||||
|
||||
|
||||
/**
|
||||
* Unit tests for {@see IsValidCsrfTokenRule}.
|
||||
*/
|
||||
class IsValidCsrfTokenRuleTest extends RuleTest
|
||||
{
|
||||
public function check_provider(): array
|
||||
{
|
||||
$type = InvalidTypeException::class;
|
||||
|
||||
return [
|
||||
"exception if input is not set" => [new IsValidCsrfTokenRule(), null, $type, "Required input 'key' not set."],
|
||||
"exception if input is not a string" => [new IsValidCsrfTokenRule(), 170, $type, "Input 'key' should be string, but is integer."],
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
$_SESSION = [];
|
||||
}
|
||||
|
||||
public function test_exception_if_input_is_incorrect_token(): void
|
||||
{
|
||||
$_SESSION["token"] = "valid";
|
||||
|
||||
self::expectException(InvalidValueException::class);
|
||||
|
||||
(new IsValidCsrfTokenRule())->check(["key" => "invalid"], "key");
|
||||
}
|
||||
|
||||
public function test_no_exception_if_input_is_correct_token(): void
|
||||
{
|
||||
$_SESSION["token"] = "valid";
|
||||
|
||||
(new IsValidCsrfTokenRule())->check(["key" => "valid"], "key");
|
||||
|
||||
self::assertTrue(true);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,56 @@
|
|||
<?php /** @noinspection PhpUnhandledExceptionInspection */
|
||||
|
||||
namespace com\fwdekker\deathnotifier\validation;
|
||||
|
||||
use com\fwdekker\deathnotifier\IllegalArgumentError;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
|
||||
/**
|
||||
* Unit tests for {@see LoginValidator}.
|
||||
*/
|
||||
class LoginValidatorTest extends TestCase
|
||||
{
|
||||
public function test_constructor_throws_exception_if_user_should_be_both_logged_in_and_logged_out(): void
|
||||
{
|
||||
self::expectException(IllegalArgumentError::class);
|
||||
|
||||
new LoginValidator(validate_logged_in: true, validate_logged_out: true);
|
||||
}
|
||||
|
||||
public function test_constructor_throws_exception_if_user_should_be_neither_logged_in_nor_logged_out(): void
|
||||
{
|
||||
self::expectException(IllegalArgumentError::class);
|
||||
|
||||
new LoginValidator(validate_logged_in: false, validate_logged_out: false);
|
||||
}
|
||||
|
||||
|
||||
public function test_check_throws_exception_if_log_in_required_but_not_logged_in(): void
|
||||
{
|
||||
self::expectException(InvalidValueException::class);
|
||||
|
||||
(new LoginValidator(validate_logged_in: true))->check([]);
|
||||
}
|
||||
|
||||
public function test_check_no_exception_if_log_in_required_and_logged_in(): void
|
||||
{
|
||||
(new LoginValidator(validate_logged_in: true))->check(["uuid" => "my-uuid"]);
|
||||
|
||||
self::assertTrue(true);
|
||||
}
|
||||
|
||||
public function test_check_throws_exception_if_log_out_required_but_not_logged_out(): void
|
||||
{
|
||||
self::expectException(InvalidValueException::class);
|
||||
|
||||
(new LoginValidator(validate_logged_out: true))->check(["uuid" => "my-uuid"]);
|
||||
}
|
||||
|
||||
public function test_check_no_exception_if_log_out_required_and_logged_out(): void
|
||||
{
|
||||
(new LoginValidator(validate_logged_out: true))->check([]);
|
||||
|
||||
self::assertTrue(true);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,51 @@
|
|||
<?php /** @noinspection PhpUnhandledExceptionInspection */
|
||||
|
||||
namespace com\fwdekker\deathnotifier\validation;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
|
||||
/**
|
||||
* Unit tests for {@see RuleSet}.
|
||||
*/
|
||||
class RuleSetTest extends TestCase
|
||||
{
|
||||
public function test_no_exception_if_no_rule_lists(): void
|
||||
{
|
||||
$rules = [];
|
||||
$inputs = ["key1" => "value1", "key2" => "value2"];
|
||||
|
||||
(new RuleSet($rules))->check($inputs);
|
||||
|
||||
self::assertTrue(true);
|
||||
}
|
||||
|
||||
public function test_no_exception_if_empty_rule_lists(): void
|
||||
{
|
||||
$rules = ["key1" => [], "key3" => []];
|
||||
$inputs = ["key1" => "value1", "key2" => "value2"];
|
||||
|
||||
(new RuleSet($rules))->check($inputs);
|
||||
|
||||
self::assertTrue(true);
|
||||
}
|
||||
|
||||
public function test_exception_if_single_invalid_rule(): void
|
||||
{
|
||||
$rules = ["key1" => [new IsStringRule()]];
|
||||
$inputs = ["key2" => "value2"];
|
||||
|
||||
self::expectException(InvalidTypeException::class);
|
||||
(new RuleSet($rules))->check($inputs);
|
||||
}
|
||||
|
||||
public function test_exception_for_first_invalid_rule_in_first_rule_list(): void
|
||||
{
|
||||
$rules = ["key1" => [new IsStringRule(), new IsEmailRule()], "key2" => [new IsStringRule()]];
|
||||
$inputs = ["key3" => "value3"];
|
||||
|
||||
self::expectException(InvalidTypeException::class);
|
||||
self::expectExceptionMessage("Required input 'key1' not set.");
|
||||
(new RuleSet($rules))->check($inputs);
|
||||
}
|
||||
}
|
|
@ -1,8 +1,7 @@
|
|||
<?php
|
||||
|
||||
namespace com\fwdekker\deathnotifier\validator;
|
||||
namespace com\fwdekker\deathnotifier\validation;
|
||||
|
||||
use com\fwdekker\deathnotifier\IllegalArgumentError;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Throwable;
|
||||
|
||||
|
@ -15,33 +14,32 @@ abstract class RuleTest extends TestCase
|
|||
/**
|
||||
* Tests the output of {@see Rule::check()}.
|
||||
*
|
||||
* @param string $name the name to give to the test case
|
||||
* @param Rule $rule the rule to check
|
||||
* @param mixed|null $input the user's input
|
||||
* @param class-string<Throwable>|null $exception the exception that is asserted to be thrown
|
||||
* @param string|null $exception_message the exception message that is asserted
|
||||
* @param string|null $exception_message the exception message that is asserted to be thrown
|
||||
* @return void
|
||||
* @throws InvalidInputException if {@see $exception} is `null` but an exception is thrown
|
||||
* @throws InvalidTypeException never
|
||||
* @throws InvalidValueException never
|
||||
* @dataProvider check_provider
|
||||
*/
|
||||
public function test_check(string $name, mixed $input, ?string $exception, ?string $exception_message): void
|
||||
public function test_check(Rule $rule, mixed $input, ?string $exception, ?string $exception_message): void
|
||||
{
|
||||
self::setName($name);
|
||||
|
||||
if ($exception !== null)
|
||||
self::expectException($exception);
|
||||
if ($exception_message !== null)
|
||||
self::expectExceptionMessage($exception_message);
|
||||
|
||||
(new IsEmailRule())->check(["key" => $input], "key");
|
||||
$rule->check(["key" => $input], "key");
|
||||
|
||||
if ($exception === null)
|
||||
if ($exception === null && $exception_message === null)
|
||||
self::assertTrue(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the test cases.
|
||||
*
|
||||
* @return array<array{string, mixed|null, class-string<Throwable>|null, string|null}> the test cases
|
||||
* @return array<string, array{Rule, mixed|null, class-string<Throwable>|null, string|null}> the test cases
|
||||
* @see RuleTest::test_check()
|
||||
*/
|
||||
abstract public function check_provider(): array;
|
|
@ -1,31 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace com\fwdekker\deathnotifier\validator;
|
||||
|
||||
use com\fwdekker\deathnotifier\IllegalArgumentError;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
|
||||
/**
|
||||
* Unit tests for {@see IsEmailRule}.
|
||||
*/
|
||||
class IsEmailRuleTest extends RuleTest
|
||||
{
|
||||
public function check_provider(): array
|
||||
{
|
||||
$error = IllegalArgumentError::class;
|
||||
$exception = InvalidInputException::class;
|
||||
|
||||
return [
|
||||
["error if input is not set", null, $error, "Required input 'key' not set."],
|
||||
["error if input is not string", 491, $error, "Input 'key' should be string, but is integer."],
|
||||
["exception if input starts with space", " valid@example.com", $exception, "Remove the spaces at the start."],
|
||||
["exception if input ends with space", "valid@example.com ", $exception, "Remove the spaces at the end."],
|
||||
["exception if input is empty", "", $exception, "Enter an email address."],
|
||||
["exception if input misses the '@' symbol", "invalid", $exception, "Don't forget to add an '@' symbol."],
|
||||
["exception if input misses the domain part", "invalid@", $exception, "Add a domain name after the '@'."],
|
||||
["exception if input is invalid in a complex way", "invalid@example", $exception, "Enter a valid email address."],
|
||||
["no exception if input is valid", "valid@example.com", null, null],
|
||||
];
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue