Skip to content

Commit

Permalink
Create AuthenticationFailure exception
Browse files Browse the repository at this point in the history
Refactors the AuthenticationPlugin::showFailure() to receive the new
exception and move the showFailure calls to the Authentication
middleware.

Signed-off-by: Maurício Meneghini Fauth <mauricio@fauth.dev>
  • Loading branch information
MauricioFauth committed May 9, 2024
1 parent 2a58575 commit cfdaedb
Show file tree
Hide file tree
Showing 14 changed files with 237 additions and 129 deletions.
5 changes: 3 additions & 2 deletions docs/faq.rst
Original file line number Diff line number Diff line change
Expand Up @@ -2235,9 +2235,10 @@ logs. Currently there are two variables available:
User name of currently active user (they do not have to be logged in).
``userStatus``
Status of currently active user, one of ``ok`` (user is logged in),
``mysql-denied`` (MySQL denied user login), ``allow-denied`` (user denied
``server-denied`` (database server denied user login), ``allow-denied`` (user denied
by allow/deny rules), ``root-denied`` (root is denied in configuration),
``empty-denied`` (empty password is denied).
``empty-denied`` (empty password is denied),
``no-activity`` (automatically logged out due to inactivity).

``LogFormat`` directive for Apache can look like following:

Expand Down
78 changes: 78 additions & 0 deletions src/Exceptions/AuthenticationFailure.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
<?php

declare(strict_types=1);

namespace PhpMyAdmin\Exceptions;

use RuntimeException;
use Throwable;

use function __;

final class AuthenticationFailure extends RuntimeException
{
public const SERVER_DENIED = 'server-denied';
public const ALLOW_DENIED = 'allow-denied';
public const ROOT_DENIED = 'root-denied';
public const EMPTY_DENIED = 'empty-denied';
public const NO_ACTIVITY = 'no-activity';

/** @psalm-param self::* $failureType */
public function __construct(
public readonly string $failureType,
string $message = '',
int $code = 0,
Throwable|null $previous = null,
) {
parent::__construct($message, $code, $previous);
}

/**
* Database server denied user login
*/
public static function serverDenied(): self
{
return new self(self::SERVER_DENIED, __('Cannot log in to the database server.'));
}

/**
* User denied by allow/deny rules
*/
public static function allowDenied(): self
{
return new self(self::ALLOW_DENIED, __('Access denied!'));
}

/**
* User 'root' is denied in configuration
*/
public static function rootDenied(): self
{
return new self(self::ROOT_DENIED, __('Access denied!'));
}

/**
* Empty password is denied
*/
public static function emptyDenied(): self
{
return new self(
self::EMPTY_DENIED,
__('Login without a password is forbidden by configuration (see AllowNoPassword).'),
);
}

/**
* Automatically logged out due to inactivity
*/
public static function noActivity(): self
{
return new self(
self::NO_ACTIVITY,
__(
'You have been automatically logged out due to inactivity of %s seconds.'
. ' Once you log in again, you should be able to resume the work where you left off.',
),
);
}
}
25 changes: 16 additions & 9 deletions src/Http/Middleware/Authentication.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,13 @@
use PhpMyAdmin\Container\ContainerBuilder;
use PhpMyAdmin\DatabaseInterface;
use PhpMyAdmin\Dbal\ConnectionType;
use PhpMyAdmin\Exceptions\AuthenticationFailure;
use PhpMyAdmin\Exceptions\AuthenticationPluginException;
use PhpMyAdmin\Exceptions\ExitException;
use PhpMyAdmin\Http\Factory\ResponseFactory;
use PhpMyAdmin\Http\ServerRequest;
use PhpMyAdmin\LanguageManager;
use PhpMyAdmin\Logging;
use PhpMyAdmin\Plugins\AuthenticationPlugin;
use PhpMyAdmin\Plugins\AuthenticationPluginFactory;
use PhpMyAdmin\ResponseRenderer;
use PhpMyAdmin\Template;
Expand Down Expand Up @@ -60,7 +60,12 @@ public function process(ServerRequestInterface $request, RequestHandlerInterface
}

try {
$authPlugin->authenticate();
try {
$authPlugin->authenticate();
} catch (AuthenticationFailure $exception) {
$authPlugin->showFailure($exception);
}

$currentServer = new Server(Config::getInstance()->selectedServer);

/* Enable LOAD DATA LOCAL INFILE for LDI plugin */
Expand All @@ -71,7 +76,11 @@ public function process(ServerRequestInterface $request, RequestHandlerInterface
// phpcs:enable
}

$this->connectToDatabaseServer(DatabaseInterface::getInstance(), $authPlugin, $currentServer);
try {
$this->connectToDatabaseServer(DatabaseInterface::getInstance(), $currentServer);
} catch (AuthenticationFailure $exception) {
$authPlugin->showFailure($exception);
}

// Relation should only be initialized after the connection is successful
/** @var Relation $relation */
Expand All @@ -94,11 +103,9 @@ public function process(ServerRequestInterface $request, RequestHandlerInterface
return $handler->handle($request);
}

private function connectToDatabaseServer(
DatabaseInterface $dbi,
AuthenticationPlugin $auth,
Server $currentServer,
): void {
/** @throws AuthenticationFailure */
private function connectToDatabaseServer(DatabaseInterface $dbi, Server $currentServer): void
{
/**
* Try to connect MySQL with the control user profile (will be used to get the privileges list for the current
* user but the true user link must be open after this one, so it would be default one for all the scripts).
Expand All @@ -111,7 +118,7 @@ private function connectToDatabaseServer(
// Connects to the server (validates user's login)
$userConnection = $dbi->connect($currentServer, ConnectionType::User);
if ($userConnection === null) {
$auth->showFailure('mysql-denied');
throw AuthenticationFailure::serverDenied();
}

if ($controlConnection !== null) {
Expand Down
11 changes: 5 additions & 6 deletions src/Plugins/Auth/AuthenticationConfig.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
use PhpMyAdmin\Config;
use PhpMyAdmin\DatabaseInterface;
use PhpMyAdmin\Error\ErrorHandler;
use PhpMyAdmin\Exceptions\AuthenticationFailure;
use PhpMyAdmin\Html\Generator;
use PhpMyAdmin\Plugins\AuthenticationPlugin;
use PhpMyAdmin\ResponseRenderer;
Expand Down Expand Up @@ -65,12 +66,10 @@ public function readCredentials(): bool

/**
* User is not allowed to login to MySQL -> authentication failed
*
* @param string $failure String describing why authentication has failed
*/
public function showFailure(string $failure): never
public function showFailure(AuthenticationFailure $failure): never
{
parent::showFailure($failure);
$this->logFailure($failure);

$connError = DatabaseInterface::getInstance()->getError();
if ($connError === '' || $connError === '0') {
Expand All @@ -95,8 +94,8 @@ public function showFailure(string $failure): never
<tr>
<td>';
$config = Config::getInstance();
if ($failure === 'allow-denied') {
trigger_error(__('Access denied!'), E_USER_NOTICE);
if ($failure->failureType === AuthenticationFailure::ALLOW_DENIED) {
trigger_error($failure->getMessage(), E_USER_NOTICE);
} else {
// Check whether user has configured something
if ($config->sourceMtime == 0) {
Expand Down
13 changes: 6 additions & 7 deletions src/Plugins/Auth/AuthenticationCookie.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
use PhpMyAdmin\Core;
use PhpMyAdmin\Current;
use PhpMyAdmin\Error\ErrorHandler;
use PhpMyAdmin\Exceptions\AuthenticationFailure;
use PhpMyAdmin\Exceptions\SessionHandlerException;
use PhpMyAdmin\LanguageManager;
use PhpMyAdmin\Message;
Expand Down Expand Up @@ -206,6 +207,8 @@ public function showLoginForm(): never
* it returns true if all seems ok which usually leads to auth_set_user()
*
* it directly switches to showFailure() if user inactivity timeout is reached
*
* @throws AuthenticationFailure
*/
public function readCredentials(): bool
{
Expand Down Expand Up @@ -371,7 +374,7 @@ public function readCredentials(): bool
SessionCache::remove('table_priv');
SessionCache::remove('proc_priv');

$this->showFailure('no-activity');
throw AuthenticationFailure::noActivity();
}

// check password cookie
Expand Down Expand Up @@ -539,14 +542,10 @@ public function storePasswordCookie(string $password): void
*
* prepares error message and switches to showLoginForm() which display the error
* and the login form
*
* @param string $failure String describing why authentication has failed
*/
public function showFailure(string $failure): never
public function showFailure(AuthenticationFailure $failure): never
{
$GLOBALS['conn_error'] ??= null;

parent::showFailure($failure);
$this->logFailure($failure);

// Deletes password cookie and displays the login form
Config::getInstance()->removeCookie('pmaAuth-' . Current::$server);
Expand Down
7 changes: 3 additions & 4 deletions src/Plugins/Auth/AuthenticationHttp.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
use PhpMyAdmin\Config;
use PhpMyAdmin\Core;
use PhpMyAdmin\DatabaseInterface;
use PhpMyAdmin\Exceptions\AuthenticationFailure;
use PhpMyAdmin\LanguageManager;
use PhpMyAdmin\Message;
use PhpMyAdmin\Plugins\AuthenticationPlugin;
Expand Down Expand Up @@ -178,12 +179,10 @@ public function readCredentials(): bool

/**
* User is not allowed to login to MySQL -> authentication failed
*
* @param string $failure String describing why authentication has failed
*/
public function showFailure(string $failure): never
public function showFailure(AuthenticationFailure $failure): never
{
parent::showFailure($failure);
$this->logFailure($failure);

$error = DatabaseInterface::getInstance()->getError();
if ($error && $GLOBALS['errno'] != 1045) {
Expand Down
7 changes: 3 additions & 4 deletions src/Plugins/Auth/AuthenticationSignon.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
namespace PhpMyAdmin\Plugins\Auth;

use PhpMyAdmin\Config;
use PhpMyAdmin\Exceptions\AuthenticationFailure;
use PhpMyAdmin\LanguageManager;
use PhpMyAdmin\Plugins\AuthenticationPlugin;
use PhpMyAdmin\ResponseRenderer;
Expand Down Expand Up @@ -233,12 +234,10 @@ public function readCredentials(): bool

/**
* User is not allowed to login to MySQL -> authentication failed
*
* @param string $failure String describing why authentication has failed
*/
public function showFailure(string $failure): never
public function showFailure(AuthenticationFailure $failure): never
{
parent::showFailure($failure);
$this->logFailure($failure);

/* Session name */
$sessionName = Config::getInstance()->selectedServer['SignonSession'];
Expand Down

0 comments on commit cfdaedb

Please sign in to comment.