2026-05-06 02:23:03 -04:00
|
|
|
<?php
|
|
|
|
|
|
|
|
|
|
// SPDX-FileCopyrightText: 2026 Icinga GmbH <https://icinga.com>
|
|
|
|
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
|
|
|
|
|
|
|
|
namespace Icinga\Authentication;
|
|
|
|
|
|
|
|
|
|
use Icinga\User;
|
|
|
|
|
use Icinga\Web\Session;
|
|
|
|
|
use Icinga\Web\Session\SessionNamespace;
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* In-session state for a pending two-factor authentication challenge
|
|
|
|
|
*/
|
|
|
|
|
class TwoFactorState
|
|
|
|
|
{
|
|
|
|
|
/** @var string Session namespace name to isolate all 2FA state */
|
|
|
|
|
protected const SESSION_NAMESPACE = 'twofactor';
|
|
|
|
|
|
|
|
|
|
/** @var string Session key storing the challenged User */
|
|
|
|
|
protected const SESSION_CHALLENGED_USER = 'challenged_user';
|
|
|
|
|
|
2026-05-08 04:49:16 -04:00
|
|
|
/** @var string Session key storing the raw remember-me cookie */
|
|
|
|
|
protected const SESSION_REMEMBER_ME_COOKIE_DATA = 'remember_me_cookie_data';
|
2026-05-06 02:23:03 -04:00
|
|
|
|
|
|
|
|
/** @var SessionNamespace Session namespace scoping the challenge state */
|
|
|
|
|
protected SessionNamespace $session;
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Create a new TwoFactorState
|
|
|
|
|
*/
|
|
|
|
|
public function __construct()
|
|
|
|
|
{
|
|
|
|
|
$this->session = Session::getSession()->getNamespace(static::SESSION_NAMESPACE);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Store the user for whom two-factor verification was challenged
|
|
|
|
|
*
|
|
|
|
|
* @param User $user
|
|
|
|
|
*
|
|
|
|
|
* @return void
|
|
|
|
|
*/
|
|
|
|
|
public function challenge(User $user): void
|
|
|
|
|
{
|
|
|
|
|
$this->session->set(static::SESSION_CHALLENGED_USER, $user);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Clear all challenge state from the session
|
|
|
|
|
*
|
|
|
|
|
* Call after successful verification.
|
|
|
|
|
*
|
|
|
|
|
* @return void
|
|
|
|
|
*/
|
|
|
|
|
public function completeChallenge(): void
|
|
|
|
|
{
|
|
|
|
|
$this->session->delete(static::SESSION_CHALLENGED_USER);
|
2026-05-08 04:49:16 -04:00
|
|
|
$this->session->delete(static::SESSION_REMEMBER_ME_COOKIE_DATA);
|
2026-05-06 02:23:03 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Check whether a two-factor challenge is pending
|
|
|
|
|
*
|
|
|
|
|
* @return bool
|
|
|
|
|
*/
|
|
|
|
|
public function isChallenged(): bool
|
|
|
|
|
{
|
|
|
|
|
return $this->getChallengedUser() !== null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
2026-05-08 04:49:16 -04:00
|
|
|
* Set the remember-me cookie value to issue after a successful challenge
|
2026-05-06 02:23:03 -04:00
|
|
|
*
|
2026-05-08 04:49:16 -04:00
|
|
|
* @param string $cookieData
|
2026-05-06 02:23:03 -04:00
|
|
|
*
|
|
|
|
|
* @return void
|
|
|
|
|
*/
|
2026-05-08 04:49:16 -04:00
|
|
|
public function setRememberMeCookieData(string $cookieData): void
|
2026-05-06 02:23:03 -04:00
|
|
|
{
|
2026-05-08 04:49:16 -04:00
|
|
|
$this->session->set(static::SESSION_REMEMBER_ME_COOKIE_DATA, $cookieData);
|
2026-05-06 02:23:03 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
2026-05-08 04:49:16 -04:00
|
|
|
* Get the stored remember-me cookie value
|
2026-05-06 02:23:03 -04:00
|
|
|
*
|
2026-05-08 04:49:16 -04:00
|
|
|
* @return ?string null if no value was set
|
2026-05-06 02:23:03 -04:00
|
|
|
*/
|
2026-05-08 04:49:16 -04:00
|
|
|
public function getRememberMeCookieData(): ?string
|
2026-05-06 02:23:03 -04:00
|
|
|
{
|
2026-05-08 04:49:16 -04:00
|
|
|
return $this->session->get(static::SESSION_REMEMBER_ME_COOKIE_DATA);
|
2026-05-06 02:23:03 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Get the user for whom two-factor verification was challenged
|
|
|
|
|
*
|
|
|
|
|
* @return ?User null if no challenge is active
|
|
|
|
|
*/
|
|
|
|
|
public function getChallengedUser(): ?User
|
|
|
|
|
{
|
|
|
|
|
return $this->session->get(static::SESSION_CHALLENGED_USER);
|
|
|
|
|
}
|
|
|
|
|
}
|