mirror of
https://github.com/processwire/processwire.git
synced 2025-08-23 06:44:38 +02:00
Update ProcessLogin to support a configuration option to specify roles that should be prompted to enable two-factor authentication
This commit is contained in:
@@ -323,7 +323,9 @@ class Tfa extends WireData implements Module, ConfigurableModule {
|
|||||||
$f->attr('id', 'Inputfield_login_submit');
|
$f->attr('id', 'Inputfield_login_submit');
|
||||||
$form->add($f);
|
$form->add($f);
|
||||||
|
|
||||||
$form->appendMarkup = "<p><a href='./'>" . $this->_('Cancel') . "</a></p>";
|
$form->appendMarkup =
|
||||||
|
"<p><a href='./'>" . $this->_('Cancel') . "</a></p>" .
|
||||||
|
"<script>if(typeof jQuery!='undefined') jQuery(document).ready(function(){jQuery('#login_name').focus();});</script>";
|
||||||
$this->authCodeForm = $form;
|
$this->authCodeForm = $form;
|
||||||
|
|
||||||
return $form;
|
return $form;
|
||||||
@@ -809,6 +811,8 @@ class Tfa extends WireData implements Module, ConfigurableModule {
|
|||||||
|
|
||||||
/** @var Modules $modules */
|
/** @var Modules $modules */
|
||||||
$modules = $event->wire('modules');
|
$modules = $event->wire('modules');
|
||||||
|
/** @var Sanitizer $sanitizer */
|
||||||
|
$sanitizer = $this->wire('sanitizer');
|
||||||
$user = $this->getUser();
|
$user = $this->getUser();
|
||||||
|
|
||||||
if($user->isGuest()) {
|
if($user->isGuest()) {
|
||||||
@@ -816,10 +820,11 @@ class Tfa extends WireData implements Module, ConfigurableModule {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$tfaTitle = $modules->getModuleInfoProperty($this, 'title');
|
||||||
$settings = $this->getUserSettings($user);
|
$settings = $this->getUserSettings($user);
|
||||||
$enabled = $this->enabledForUser($user, $settings);
|
$enabled = $this->enabledForUser($user, $settings);
|
||||||
$fieldset = $modules->get('InputfieldFieldset');
|
$fieldset = $modules->get('InputfieldFieldset');
|
||||||
$fieldset->label = $modules->getModuleInfoProperty($this, 'title');
|
$fieldset->label = $tfaTitle;
|
||||||
$fieldset->showIf = "$this->userFieldName=" . $this->className();
|
$fieldset->showIf = "$this->userFieldName=" . $this->className();
|
||||||
|
|
||||||
if($enabled) {
|
if($enabled) {
|
||||||
@@ -829,11 +834,17 @@ class Tfa extends WireData implements Module, ConfigurableModule {
|
|||||||
$this->_('Two factor authentication enabled!') . ' ' .
|
$this->_('Two factor authentication enabled!') . ' ' .
|
||||||
$this->_('To disable or change settings, select the “None” option above and save.');
|
$this->_('To disable or change settings, select the “None” option above and save.');
|
||||||
$fieldset->collapsed = Inputfield::collapsedYes;
|
$fieldset->collapsed = Inputfield::collapsedYes;
|
||||||
|
$this->wire('session')->removeFor('_user', 'requireTfa'); // set by ProcessLogin
|
||||||
} else {
|
} else {
|
||||||
/** @var InputfieldFieldset $fieldset */
|
/** @var InputfieldFieldset $fieldset */
|
||||||
$this->getUserSettingsInputfields($user, $fieldset, $settings);
|
$this->getUserSettingsInputfields($user, $fieldset, $settings);
|
||||||
if(!$this->wire('input')->requestMethod('POST')) {
|
if(!$this->wire('input')->requestMethod('POST')) {
|
||||||
$this->warning($this->_('Please configure your two-factor authentication settings'));
|
$this->warning(
|
||||||
|
'<strong>' . $sanitizer->entities1($this->_('Please configure')) . '</strong> ' .
|
||||||
|
wireIconMarkup('angle-right') . ' ' .
|
||||||
|
"<a href='#wrap_Inputfield_tfa_type'>" . $sanitizer->entities1($tfaTitle) . "</a>",
|
||||||
|
Notice::allowMarkup
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -6,7 +6,7 @@
|
|||||||
* This file is designed for inclusion by /site/templates/admin.php template and all the variables
|
* This file is designed for inclusion by /site/templates/admin.php template and all the variables
|
||||||
* it references are from your template namespace.
|
* it references are from your template namespace.
|
||||||
*
|
*
|
||||||
* Copyright 2016 by Ryan Cramer
|
* Copyright 2018 by Ryan Cramer
|
||||||
*
|
*
|
||||||
* @var Config $config
|
* @var Config $config
|
||||||
* @var User $user
|
* @var User $user
|
||||||
@@ -66,6 +66,25 @@ function _checkForHttpHostError(Config $config) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if two factor authentication is being required and display warning with link to configure
|
||||||
|
*
|
||||||
|
* @param Session $session
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
function _checkForTwoFactorAuth(Session $session) {
|
||||||
|
$tfaUrl = $session->getFor('_user', 'requireTfa'); // contains URL to configure TFA
|
||||||
|
if(!$tfaUrl || strpos($tfaUrl, $session->wire('page')->url()) === 0) return;
|
||||||
|
$sanitizer = $session->wire('sanitizer');
|
||||||
|
$session->wire('user')->warning(
|
||||||
|
'<strong>' . $sanitizer->entities1(__('Action required')) . '</strong> ' .
|
||||||
|
wireIconMarkup('angle-right') . ' ' .
|
||||||
|
"<a href='$tfaUrl'>" . $sanitizer->entities1(__('Enable two-factor authentication')) . " </a>",
|
||||||
|
Notice::allowMarkup
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// notify superuser if there is an http host error
|
// notify superuser if there is an http host error
|
||||||
if($user->isSuperuser()) _checkForHttpHostError($config);
|
if($user->isSuperuser()) _checkForHttpHostError($config);
|
||||||
|
|
||||||
@@ -81,17 +100,20 @@ $breadcrumbs = $wire->wire('breadcrumbs', new Breadcrumbs());
|
|||||||
foreach($page->parents() as $p) {
|
foreach($page->parents() as $p) {
|
||||||
if($p->id > 1) $breadcrumbs->add(new Breadcrumb($p->url, $p->get("title|name")));
|
if($p->id > 1) $breadcrumbs->add(new Breadcrumb($p->url, $p->get("title|name")));
|
||||||
}
|
}
|
||||||
|
|
||||||
$controller = null;
|
$controller = null;
|
||||||
$content = '';
|
$content = '';
|
||||||
|
$ajax = $config->ajax;
|
||||||
|
$modal = $input->get('modal') ? true : false;
|
||||||
|
$demo = $config->demo;
|
||||||
|
|
||||||
// enable modules to output their own ajax responses if they choose to
|
// enable modules to output their own ajax responses if they choose to
|
||||||
if($config->ajax) ob_start();
|
if($ajax) ob_start();
|
||||||
|
|
||||||
if($page->process && $page->process != 'ProcessPageView') {
|
if($page->process && $page->process != 'ProcessPageView') {
|
||||||
try {
|
try {
|
||||||
|
|
||||||
if($config->demo && !in_array($page->process, array('ProcessLogin'))) {
|
if($demo && !in_array($page->process, array('ProcessLogin'))) {
|
||||||
if(count($_POST)) $wire->error("Features that use POST variables are disabled in this demo");
|
if(count($_POST)) $wire->error("Features that use POST variables are disabled in this demo");
|
||||||
foreach($_POST as $k => $v) unset($_POST[$k]);
|
foreach($_POST as $k => $v) unset($_POST[$k]);
|
||||||
foreach($_FILES as $k => $v) unset($_FILES[$k]);
|
foreach($_FILES as $k => $v) unset($_FILES[$k]);
|
||||||
@@ -109,10 +131,13 @@ if($page->process && $page->process != 'ProcessPageView') {
|
|||||||
/** @noinspection PhpIncludeInspection */
|
/** @noinspection PhpIncludeInspection */
|
||||||
include_once($initFile);
|
include_once($initFile);
|
||||||
}
|
}
|
||||||
if($input->get('modal')) $session->addHookBefore('redirect', null, '_hookSessionRedirectModal');
|
if($modal) $session->addHookBefore('redirect', null, '_hookSessionRedirectModal');
|
||||||
$content = $controller->execute();
|
$content = $controller->execute();
|
||||||
$process = $controller->wire('process');
|
$process = $controller->wire('process');
|
||||||
|
|
||||||
|
if(!$ajax && !$modal && !$demo && $user->isLoggedin()) _checkForTwoFactorAuth($session);
|
||||||
|
if($process) {} // ignore
|
||||||
|
|
||||||
} catch(Wire404Exception $e) {
|
} catch(Wire404Exception $e) {
|
||||||
$wire->error($e->getMessage());
|
$wire->error($e->getMessage());
|
||||||
|
|
||||||
@@ -154,7 +179,7 @@ if($page->process && $page->process != 'ProcessPageView') {
|
|||||||
$content = '<p>' . __('This page has no process assigned.') . '</p>';
|
$content = '<p>' . __('This page has no process assigned.') . '</p>';
|
||||||
}
|
}
|
||||||
|
|
||||||
if($config->ajax) {
|
if($ajax) {
|
||||||
// enable modules to output their own ajax responses if they choose to
|
// enable modules to output their own ajax responses if they choose to
|
||||||
if(!$content) $content = ob_get_contents();
|
if(!$content) $content = ob_get_contents();
|
||||||
ob_end_clean();
|
ob_end_clean();
|
||||||
@@ -175,5 +200,6 @@ if($controller && $controller->isAjax()) {
|
|||||||
/** @noinspection PhpIncludeInspection */
|
/** @noinspection PhpIncludeInspection */
|
||||||
require($adminThemeFile);
|
require($adminThemeFile);
|
||||||
$session->removeNotices();
|
$session->removeNotices();
|
||||||
|
if($content) {} // ignore
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -12,6 +12,7 @@
|
|||||||
* https://processwire.com
|
* https://processwire.com
|
||||||
*
|
*
|
||||||
* @property bool $allowForgot Whether the ProcessForgotPassword module is installed.
|
* @property bool $allowForgot Whether the ProcessForgotPassword module is installed.
|
||||||
|
* @property array $tfaRecRoleIDs Role IDs where admin prompts/recommends them to enable TFA.
|
||||||
*
|
*
|
||||||
* @method void beforeLogin() #pw-hooker
|
* @method void beforeLogin() #pw-hooker
|
||||||
* @method void afterLogin() #pw-hooker
|
* @method void afterLogin() #pw-hooker
|
||||||
@@ -25,15 +26,16 @@
|
|||||||
* @method void loginFailed($name) #pw-hooker
|
* @method void loginFailed($name) #pw-hooker
|
||||||
* @method void loginSuccess(User $user) #pw-hooker
|
* @method void loginSuccess(User $user) #pw-hooker
|
||||||
*
|
*
|
||||||
|
*
|
||||||
*/
|
*/
|
||||||
|
|
||||||
class ProcessLogin extends Process {
|
class ProcessLogin extends Process implements ConfigurableModule {
|
||||||
|
|
||||||
public static function getModuleInfo() {
|
public static function getModuleInfo() {
|
||||||
return array(
|
return array(
|
||||||
'title' => 'Login',
|
'title' => 'Login',
|
||||||
'summary' => 'Login to ProcessWire',
|
'summary' => 'Login to ProcessWire',
|
||||||
'version' => 105,
|
'version' => 106,
|
||||||
'permanent' => true,
|
'permanent' => true,
|
||||||
'permission' => 'page-view',
|
'permission' => 'page-view',
|
||||||
);
|
);
|
||||||
@@ -93,6 +95,23 @@ class ProcessLogin extends Process {
|
|||||||
*/
|
*/
|
||||||
protected $logoutURL = '';
|
protected $logoutURL = '';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Did user login with two factor authentication?
|
||||||
|
*
|
||||||
|
* @var bool
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
protected $tfaLoginSuccess = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Construct
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public function __construct() {
|
||||||
|
$this->set('tfaRecRoleIDs', array());
|
||||||
|
parent::__construct();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Build the login form
|
* Build the login form
|
||||||
*
|
*
|
||||||
@@ -178,6 +197,7 @@ class ProcessLogin extends Process {
|
|||||||
if($tfa && $tfa->active()) {
|
if($tfa && $tfa->active()) {
|
||||||
// two factor authentication
|
// two factor authentication
|
||||||
if($tfa->success()) {
|
if($tfa->success()) {
|
||||||
|
$this->tfaLoginSuccess = true;
|
||||||
$this->loginSuccess($this->wire('user'));
|
$this->loginSuccess($this->wire('user'));
|
||||||
$this->afterLoginRedirect('./');
|
$this->afterLoginRedirect('./');
|
||||||
} else {
|
} else {
|
||||||
@@ -601,19 +621,90 @@ class ProcessLogin extends Process {
|
|||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
protected function ___loginSuccess(User $user) {
|
protected function ___loginSuccess(User $user) {
|
||||||
|
|
||||||
/** @var Session $session */
|
/** @var Session $session */
|
||||||
$session = $this->wire('session');
|
$session = $this->wire('session');
|
||||||
$this->wire('session')->message($user->name . ' - ' . $this->_("Successful login"));
|
$session->message($user->name . ' - ' . $this->_("Successful login"));
|
||||||
|
|
||||||
if($this->isAdmin) {
|
if($this->isAdmin) {
|
||||||
$copyVars = $session->getFor($this, 'copyVars');
|
$copyVars = $session->getFor($this, 'copyVars');
|
||||||
if(!is_array($copyVars)) $copyVars = array();
|
if(!is_array($copyVars)) $copyVars = array();
|
||||||
foreach($copyVars as $key => $value) {
|
foreach($copyVars as $key => $value) {
|
||||||
$session->set($key, $value);
|
$session->set($key, $value);
|
||||||
}
|
}
|
||||||
|
|
||||||
$session->remove('error');
|
$session->remove('error');
|
||||||
$session->removeFor($this, 'copyVars');
|
$session->removeFor($this, 'copyVars');
|
||||||
$this->afterLogin();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if(count($this->tfaRecRoleIDs) && !$this->tfaLoginSuccess) {
|
||||||
|
// determine if Tfa module is installed and user has role requiring Tfa
|
||||||
|
$requireTfa = false;
|
||||||
|
if(count($this->wire('modules')->findByPrefix('Tfa'))) {
|
||||||
|
foreach($this->tfaRecRoleIDs as $roleID) {
|
||||||
|
$role = $this->wire('roles')->get((int) $roleID);
|
||||||
|
if($role && $user->hasRole($role)) {
|
||||||
|
$requireTfa = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if($requireTfa) {
|
||||||
|
$url = $this->wire('config')->urls('admin') . 'profile/#wrap_Inputfield_tfa_type';
|
||||||
|
$session->setFor('_user', 'requireTfa', $url);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if($this->isAdmin) $this->afterLogin();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configure module settings
|
||||||
|
*
|
||||||
|
* @param InputfieldWrapper $inputfields
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public function getModuleConfigInputfields(InputfieldWrapper $inputfields) {
|
||||||
|
|
||||||
|
/** @var Modules $modules */
|
||||||
|
$modules = $this->wire('modules');
|
||||||
|
|
||||||
|
/** @var InputfieldFieldset $fieldset */
|
||||||
|
$fieldset = $modules->get('InputfieldFieldset');
|
||||||
|
$fieldset->label = $this->_('Two-factor authentication');
|
||||||
|
$fieldset->icon = 'user-secret';
|
||||||
|
$inputfields->add($fieldset);
|
||||||
|
$tfaModules = $modules->findByPrefix('Tfa');
|
||||||
|
|
||||||
|
if(count($tfaModules)) {
|
||||||
|
$items = array();
|
||||||
|
foreach($tfaModules as $name) {
|
||||||
|
$items[] = "[$name](" . $modules->getModuleEditUrl($name) . ")";
|
||||||
|
}
|
||||||
|
$fieldset->description = $this->_('Found the following Tfa modules:') . ' ' . implode(', ', $items);
|
||||||
|
/** @var InputfieldCheckboxes $f */
|
||||||
|
$f = $modules->get('InputfieldCheckboxes');
|
||||||
|
$f->attr('name', 'tfaRecRoleIDs');
|
||||||
|
$f->icon = 'gears';
|
||||||
|
$f->label = $this->_('Strongly suggest two-factor authentication for these roles');
|
||||||
|
$f->description =
|
||||||
|
$this->_('After logging in to the admin, ProcessWire will prompt users in the roles you select here to use two-factor authentication for their accounts.');
|
||||||
|
foreach($this->wire('roles') as $role) {
|
||||||
|
if($role->name == 'guest') continue;
|
||||||
|
$f->addOption($role->id, $role->name);
|
||||||
|
}
|
||||||
|
$f->attr('value', $this->get('tfaRecRoleIDs'));
|
||||||
|
$fieldset->add($f);
|
||||||
|
|
||||||
|
} else {
|
||||||
|
$fieldset->description = $this->_('To configure this you must first install one or more Tfa modules and then return here.');
|
||||||
|
}
|
||||||
|
|
||||||
|
$fieldset->appendMarkup =
|
||||||
|
"<p><a target='_blank' href='https://modules.processwire.com/categories/tfa/'>" .
|
||||||
|
$this->_('Tfa modules in the ProcessWire modules directory') . ' ' .
|
||||||
|
wireIconMarkup('external-link') . "</a></p>";
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user