mirror of
https://github.com/Icinga/icingaweb2.git
synced 2025-07-31 01:34:09 +02:00
Add: 2FA login challenge without proper validation
This commit is contained in:
parent
a1d36202dc
commit
dd2eefa50f
@ -8,10 +8,12 @@ use Icinga\Application\Icinga;
|
|||||||
use Icinga\Application\Logger;
|
use Icinga\Application\Logger;
|
||||||
use Icinga\Common\Database;
|
use Icinga\Common\Database;
|
||||||
use Icinga\Exception\AuthenticationException;
|
use Icinga\Exception\AuthenticationException;
|
||||||
|
use Icinga\Forms\Authentication\Challenge2FAForm;
|
||||||
use Icinga\Forms\Authentication\LoginForm;
|
use Icinga\Forms\Authentication\LoginForm;
|
||||||
use Icinga\Web\Controller;
|
use Icinga\Web\Controller;
|
||||||
use Icinga\Web\Helper\CookieHelper;
|
use Icinga\Web\Helper\CookieHelper;
|
||||||
use Icinga\Web\RememberMe;
|
use Icinga\Web\RememberMe;
|
||||||
|
use Icinga\Web\Session;
|
||||||
use Icinga\Web\Url;
|
use Icinga\Web\Url;
|
||||||
use RuntimeException;
|
use RuntimeException;
|
||||||
|
|
||||||
@ -41,7 +43,14 @@ class AuthenticationController extends Controller
|
|||||||
if (($requiresSetup = $icinga->requiresSetup()) && $icinga->setupTokenExists()) {
|
if (($requiresSetup = $icinga->requiresSetup()) && $icinga->setupTokenExists()) {
|
||||||
$this->redirectNow(Url::fromPath('setup'));
|
$this->redirectNow(Url::fromPath('setup'));
|
||||||
}
|
}
|
||||||
$form = new LoginForm();
|
|
||||||
|
$user = $this->Auth()->getUser();
|
||||||
|
$form = ($user !== null
|
||||||
|
&& $user->getTwoFactorEnabled()
|
||||||
|
&& Session::getSession()->get('must_challenge_2fa_token', false) === true)
|
||||||
|
? new Challenge2FAForm()
|
||||||
|
: new LoginForm();
|
||||||
|
|
||||||
|
|
||||||
if (RememberMe::hasCookie() && $this->hasDb()) {
|
if (RememberMe::hasCookie() && $this->hasDb()) {
|
||||||
$authenticated = false;
|
$authenticated = false;
|
||||||
@ -91,13 +100,8 @@ class AuthenticationController extends Controller
|
|||||||
->sendResponse();
|
->sendResponse();
|
||||||
exit;
|
exit;
|
||||||
}
|
}
|
||||||
// FORM DOES NOT REDIRECT, IF USER HAS 2FA ENABLED and token hasn't been challenged
|
|
||||||
$form->handleRequest();
|
$form->handleRequest();
|
||||||
}
|
}
|
||||||
// if ($user->has2FA() && irgendwas_mit_session()) {
|
|
||||||
// // 2 FA form erstellen und zeigen und handeln
|
|
||||||
// in der session speichern ob der token gepasst hat
|
|
||||||
// }
|
|
||||||
$this->view->form = $form;
|
$this->view->form = $form;
|
||||||
$this->view->defaultTitle = $this->translate('Icinga Web 2 Login');
|
$this->view->defaultTitle = $this->translate('Icinga Web 2 Login');
|
||||||
$this->view->requiresSetup = $requiresSetup;
|
$this->view->requiresSetup = $requiresSetup;
|
||||||
|
71
application/forms/Authentication/Challenge2FAForm.php
Normal file
71
application/forms/Authentication/Challenge2FAForm.php
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Icinga\Forms\Authentication;
|
||||||
|
|
||||||
|
use Icinga\Application\Hook\AuthenticationHook;
|
||||||
|
use Icinga\Authentication\Auth;
|
||||||
|
use Icinga\Web\Session;
|
||||||
|
use Icinga\Web\Url;
|
||||||
|
|
||||||
|
class Challenge2FAForm extends LoginForm
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function init()
|
||||||
|
{
|
||||||
|
$this->setRequiredCue(null);
|
||||||
|
$this->setName('form_challenge_2fa');
|
||||||
|
$this->setSubmitLabel($this->translate('Verify'));
|
||||||
|
$this->setProgressLabel($this->translate('Verifying'));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function createElements(array $formData)
|
||||||
|
{
|
||||||
|
$this->addElement(
|
||||||
|
'text',
|
||||||
|
'code',
|
||||||
|
[
|
||||||
|
'autocapitalize' => 'off',
|
||||||
|
'class' => 'autofocus content-centered',
|
||||||
|
'placeholder' => $this->translate('Please enter your 2FA code'),
|
||||||
|
'required' => true,
|
||||||
|
'autocomplete' => 'off',
|
||||||
|
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->addElement(
|
||||||
|
'hidden',
|
||||||
|
'redirect',
|
||||||
|
[
|
||||||
|
'value' => Url::fromRequest()->getParam('redirect')
|
||||||
|
]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function onSuccess()
|
||||||
|
{
|
||||||
|
// TODO: Implement proper 2FA code validation
|
||||||
|
if ($_POST['code'] == 666) {
|
||||||
|
|
||||||
|
$auth = Auth::getInstance();
|
||||||
|
$user = $auth->getUser();
|
||||||
|
Session::getSession()->set('challenged_successful_2fa_token', true);
|
||||||
|
Session::getSession()->delete('must_challenge_2fa_token');
|
||||||
|
|
||||||
|
$auth->setAuthenticated($user);
|
||||||
|
|
||||||
|
AuthenticationHook::triggerLogin($user);
|
||||||
|
$this->getResponse()->setRerenderLayout(true);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->getElement('code')->addError($this->translate('Code is invalid!'));
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
@ -14,6 +14,7 @@ use Icinga\Exception\Http\HttpBadRequestException;
|
|||||||
use Icinga\User;
|
use Icinga\User;
|
||||||
use Icinga\Web\Form;
|
use Icinga\Web\Form;
|
||||||
use Icinga\Web\RememberMe;
|
use Icinga\Web\RememberMe;
|
||||||
|
use Icinga\Web\Session;
|
||||||
use Icinga\Web\Url;
|
use Icinga\Web\Url;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -164,14 +165,16 @@ class LoginForm extends Form
|
|||||||
|
|
||||||
// If user has 2FA enabled and the token hasn't been validated, redirect to login again, so that
|
// If user has 2FA enabled and the token hasn't been validated, redirect to login again, so that
|
||||||
// the token is challenged.
|
// the token is challenged.
|
||||||
$redirect = $this->getElement('redirect');
|
if ($user->getTwoFactorEnabled() && ! $user->getTwoFactorSuccessful()) {
|
||||||
$old = $redirect->getValue();
|
$redirect = $this->getElement('redirect');
|
||||||
$new = [];
|
$redirect->setValue(
|
||||||
if ($old) {
|
Url::fromPath('authentication/login',
|
||||||
$new['redirect'] = $old;
|
['redirect' => $redirect->getValue()])->getRelativeUrl()
|
||||||
|
);
|
||||||
|
Session::getSession()->set('must_challenge_2fa_token', true);
|
||||||
|
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
$redirect->setValue(Url::fromPath('authentication/login', $new)->getRelativeUrl());
|
|
||||||
return true;
|
|
||||||
|
|
||||||
$this->getResponse()->setRerenderLayout(true);
|
$this->getResponse()->setRerenderLayout(true);
|
||||||
return true;
|
return true;
|
||||||
|
@ -87,10 +87,10 @@ class Auth
|
|||||||
*/
|
*/
|
||||||
public function isAuthenticated()
|
public function isAuthenticated()
|
||||||
{
|
{
|
||||||
// return false just for testing. isAuthenticated must return false if the user is authentiacted but has 2FA enabled and the token hasn't been challenged yet.
|
|
||||||
return false;
|
|
||||||
|
|
||||||
if ($this->user !== null) {
|
if ($this->user !== null) {
|
||||||
|
if ($this->user->getTwoFactorEnabled() && ! $this->user->getTwoFactorSuccessful()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
$this->authenticateFromSession();
|
$this->authenticateFromSession();
|
||||||
@ -98,7 +98,8 @@ class Auth
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// real 2fa check from above must happen here
|
// 2fa check from must happen here, to apply the 2fa challenge for external users as well
|
||||||
|
// but the session authentication would also get the 2fa challenge
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -136,7 +137,9 @@ class Auth
|
|||||||
}
|
}
|
||||||
|
|
||||||
// don't log if 2fa hasn't been challenged yet
|
// don't log if 2fa hasn't been challenged yet
|
||||||
AuditHook::logActivity('login', 'User logged in');
|
if (!$user->getTwoFactorEnabled() || $user->getTwoFactorSuccessful()) {
|
||||||
|
AuditHook::logActivity('login', 'User logged in');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -457,6 +460,8 @@ class Auth
|
|||||||
$admissionLoader = new AdmissionLoader();
|
$admissionLoader = new AdmissionLoader();
|
||||||
$admissionLoader->applyRoles($user);
|
$admissionLoader->applyRoles($user);
|
||||||
|
|
||||||
// Set 2FA status from the user preferences in the user obect
|
// Set 2FA status from the user preferences & session in the user object
|
||||||
|
$user->setTwoFactorEnabled($preferences->getValue('icingaweb', 'enabled_2fa') == 1);
|
||||||
|
$user->setTwoFactorSuccessful(Session::getSession()->get('challenged_successful_2fa_token', false));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -122,6 +122,20 @@ class User
|
|||||||
*/
|
*/
|
||||||
protected $isHttpUser = false;
|
protected $isHttpUser = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether the user has 2FA enabled
|
||||||
|
*
|
||||||
|
* @var bool
|
||||||
|
*/
|
||||||
|
protected $twoFactorEnabled = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether the user has successfully completed 2FA
|
||||||
|
*
|
||||||
|
* @var bool
|
||||||
|
*/
|
||||||
|
protected $twoFactorSuccessful = false;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a user object given the provided information
|
* Creates a user object given the provided information
|
||||||
*
|
*
|
||||||
@ -646,4 +660,51 @@ class User
|
|||||||
|
|
||||||
return $navigation;
|
return $navigation;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get whether the user has 2FA enabled
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function getTwoFactorEnabled(): bool
|
||||||
|
{
|
||||||
|
return $this->twoFactorEnabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set whether the user has 2FA enabled
|
||||||
|
*
|
||||||
|
* @param bool $enabled
|
||||||
|
*
|
||||||
|
* @return $this
|
||||||
|
*/
|
||||||
|
public function setTwoFactorEnabled(bool $enabled)
|
||||||
|
{
|
||||||
|
$this->twoFactorEnabled = $enabled;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get whether the user has successfully completed 2FA
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function getTwoFactorSuccessful(): bool
|
||||||
|
{
|
||||||
|
return $this->twoFactorSuccessful;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set whether the user has successfully completed 2FA
|
||||||
|
*
|
||||||
|
* @param bool $successful
|
||||||
|
*
|
||||||
|
* @return $this
|
||||||
|
*/
|
||||||
|
public function setTwoFactorSuccessful(bool $successful): self
|
||||||
|
{
|
||||||
|
$this->twoFactorSuccessful = $successful;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user