Skip to content

Commit

Permalink
2fa with email driver enabled by default to all users
Browse files Browse the repository at this point in the history
  • Loading branch information
yurabakhtin committed Nov 19, 2020
0 parents commit f72717c
Show file tree
Hide file tree
Showing 14 changed files with 664 additions and 0 deletions.
45 changes: 45 additions & 0 deletions Events.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
<?php

/**
* @link https://www.humhub.org/
* @copyright Copyright (c) 2020 HumHub GmbH & Co. KG
* @license https://www.humhub.com/licences
*/

namespace humhub\modules\twofa;

use humhub\modules\twofa\helpers\TwofaHelper;
use Yii;

class Events
{
/**
* Check if current User has been verified by 2fa if it is required
*
* @param $event
* @return false|\yii\console\Response|\yii\web\Response
*/
public static function onBeforeAction($event)
{
if (Yii::$app->request->isAjax) {
// TODO: maybe it should be restricted better, but we don't need to call this for PollController from live module indeed
return false;
}

if (TwofaHelper::isVerifyingRequired() &&
!Yii::$app->getModule('twofa')->isTwofaCheckUrl()) {
return Yii::$app->getResponse()->redirect(Yii::$app->getModule('twofa')->checkRoute);
}
}

/**
* Set flag after login to user who need 2fa
*
* @param $event
* @throws \Throwable
*/
public static function onAfterLogin($event)
{
TwofaHelper::enableVerifying();
}
}
35 changes: 35 additions & 0 deletions Module.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
<?php

/**
* @link https://www.humhub.org/
* @copyright Copyright (c) 2020 HumHub GmbH & Co. KG
* @license https://www.humhub.com/licences
*/

namespace humhub\modules\twofa;

use humhub\modules\twofa\drivers\EmailDriver;
use Yii;

class Module extends \humhub\components\Module
{
/**
* @var array Drivers
*/
public $drivers = [
EmailDriver::class,
];

/**
* @var string Route to check user for two-factor authentication
*/
public $checkRoute = '/twofa/check';

/**
* @return bool Check if current page is already URL of 2fa
*/
public function isTwofaCheckUrl()
{
return Yii::$app->requestedRoute === trim($this->checkRoute, '/');
}
}
21 changes: 21 additions & 0 deletions config.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<?php

/**
* @link https://www.humhub.org/
* @copyright Copyright (c) 2020 HumHub GmbH & Co. KG
* @license https://www.humhub.com/licences
*/

use humhub\modules\twofa\Events;
use humhub\modules\user\controllers\AuthController;
use yii\web\Controller;

return [
'id' => 'twofa',
'class' => 'humhub\modules\twofa\Module',
'namespace' => 'humhub\modules\twofa',
'events' => [
[AuthController::class, AuthController::EVENT_AFTER_LOGIN, [Events::class, 'onAfterLogin']],
[Controller::class, Controller::EVENT_BEFORE_ACTION, [Events::class, 'onBeforeAction']],
],
];
43 changes: 43 additions & 0 deletions controllers/CheckController.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
<?php

/**
* @link https://www.humhub.org/
* @copyright Copyright (c) 2020 HumHub GmbH & Co. KG
* @license https://www.humhub.com/licences
*/

namespace humhub\modules\twofa\controllers;

use humhub\components\Controller;
use humhub\modules\twofa\helpers\TwofaHelper;
use humhub\modules\twofa\models\CheckCode;
use Yii;

class CheckController extends Controller
{

public $layout = "@twofa/views/layouts/main";

/**
* Renders a form to check user after log in by two-factor authentication
*
* @return string
*/
public function actionIndex()
{
Yii::$app->getModule('live')->isActive = false;

$model = new CheckCode();

if ($model->load(Yii::$app->request->post()) && $model->validate()) {
TwofaHelper::disableVerifying();

$this->view->success(Yii::t('TwofaModule.base', 'Two-factor authentication code is validated!'));
return $this->goHome();
}

return $this->render('index', ['model' => $model]);
}

}

2 changes: 2 additions & 0 deletions docs/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Changelog
=========
47 changes: 47 additions & 0 deletions drivers/BaseDriver.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
<?php

/**
* @link https://www.humhub.org/
* @copyright Copyright (c) 2020 HumHub GmbH & Co. KG
* @license https://www.humhub.com/licences
*/

namespace humhub\modules\twofa\drivers;

use Yii;

abstract class BaseDriver
{
/**
* @var string Last generated code
*/
private $code;

/**
* Return driver name
*
* @return string
*/
abstract public function getName();

/**
* Send new generated code
*
* @return bool true on success sending
*/
abstract public function send();

/**
* Get code, Generate random code on first call
*
* @return string
*/
public function getCode()
{
if (!isset($this->code)) {
$this->code = Yii::$app->security->generateRandomString(6);
}

return $this->code;
}
}
54 changes: 54 additions & 0 deletions drivers/EmailDriver.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
<?php

/**
* @link https://www.humhub.org/
* @copyright Copyright (c) 2020 HumHub GmbH & Co. KG
* @license https://www.humhub.com/licences
*/

namespace humhub\modules\twofa\drivers;

use humhub\modules\user\models\User;
use yii\mail\BaseMessage;
use Yii;

class EmailDriver extends BaseDriver
{
/**
* @inheritdoc
*/
public function getName()
{
return Yii::t('TwofaModule.base', 'E-mail');
}

/**
* @inheritdoc
*/
public function send()
{
/** @var User $user */
$user = Yii::$app->user->getIdentity();
if (!$user) {
return false;
}

// Switch to users language - if specified
if ($user->language !== '') {
Yii::$app->language = $user->language;
}

/** @var BaseMessage $mail */
$mail = Yii::$app->mailer->compose([
'html' => '@twofa/views/mails/VerifyingCode',
'text' => '@twofa/views/mails/plaintext/VerifyingCode'
], [
'user' => $user,
'code' => $this->getCode(),
]);
$mail->setTo($user->email);
$mail->setSubject(Yii::t('TwofaModule.base', 'Two-Factor Authentication'));

return $mail->send();
}
}
125 changes: 125 additions & 0 deletions helpers/TwofaHelper.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
<?php

/**
* @link https://www.humhub.org/
* @copyright Copyright (c) 2020 HumHub GmbH & Co. KG
* @license https://www.humhub.com/licences
*/

namespace humhub\modules\twofa\helpers;

use humhub\modules\content\components\ContentContainerSettingsManager;
use humhub\modules\twofa\drivers\BaseDriver;
use humhub\modules\twofa\drivers\EmailDriver;
use humhub\modules\user\models\User;
use humhub\modules\user\Module;
use Yii;

class TwofaHelper
{
const USER_SETTING = 'twofaDriver';
const CODE_SETTING = 'twofaCode';

/**
* Get settings manager of current User
*
* @return ContentContainerSettingsManager|false
*/
public static function getSettings()
{
/** @var User $user */
$user = Yii::$app->user->getIdentity();
/** @var Module $module */
$module = Yii::$app->getModule('user');

return $user ? $module->settings->contentContainer($user) : false;
}

/**
* Get 2fa Driver for current User
*
* @return BaseDriver|false
*/
public static function getDriver()
{
/** @var BaseDriver $driverClass */
// TODO: Implement new user setting who should be checked by 2fa
// $driverClass = self::getSettings()->get(self::USER_SETTING);
$driverClass = EmailDriver::class; // Use temporary Email Driver by default enabled for all users

if (in_array($driverClass, Yii::$app->getModule('twofa')->drivers )) {
$driverClass = '\\' . $driverClass;
return new $driverClass();
}

return false;
}

/**
* Get verifying code of current User
*
* @return string|null
*/
public static function getCode()
{
return ($settings = self::getSettings()) ? $settings->get(self::CODE_SETTING) : null;
}

/**
* Enable verifying by 2fa for current User
*
* @return bool true on success enabling
*/
public static function enableVerifying()
{
$settings = self::getSettings();
$driver = self::getDriver();

if (!$settings || !$driver) {
// Current User may be not logged in
// OR Wrong driver OR current User has no enabled 2fa
return false;
}

if (!$driver->send()) {
// Impossible to send a verifying code by Driver
return false;
}

// TODO: Inform user about way of sending the verifying code

// Store the sending verifying code in DB to use this as flag to display a form to check the code
$settings->set(self::CODE_SETTING, md5($driver->getCode()));

return true;
}

/**
* Disable verifying by 2fa for current User
*/
public static function disableVerifying()
{
if ($settings = self::getSettings()) {
$settings->delete(self::CODE_SETTING);
}
}

/**
* Check if verifying by 2fa is required for current User
*/
public static function isVerifyingRequired()
{
return self::getCode() !== null;
}

/**
* Check the requested code is valid for current User
*
* @param $code
* @return bool
*/
public static function isValidCode($code)
{
return md5($code) === self::getCode();
}
}
Loading

0 comments on commit f72717c

Please sign in to comment.