[TikiWiki-commits] [Git][tikiwiki/tiki][27.x] [FIX] Add TwoFactorAuth library to fix class not found in tiki-login.php
"Elifeleti Mukisa Dan \(@Danelif\) via TikiWiki-cvs" <[email protected]>
| Newsgroups | gmane.comp.cms.tiki.cvs |
|---|---|
| Message-ID | <6a213da276d09_381905208191c7@gitlab-sidekiq-low-urgency-cpu-bound-v2-6564bf9646-fnd85.mail> |
Elifeleti Mukisa Dan pushed to branch 27.x at Tiki Wiki CMS Groupware / Tiki
Commits:
c3845871 by Elifeleti Mukisa Dan at 2026-06-04T08:49:22+00:00
[FIX] Add TwoFactorAuth library to fix class not found in tiki-login.php
---
* [FIX] Add twofactor classes to fix class not found
See merge request tikiwiki/tiki!10438
- - - - -
6 changed files:
- + lib/core/TwoFactorAuth/Email2FA.php
- + lib/core/TwoFactorAuth/Exception/Exception.php
- + lib/core/TwoFactorAuth/Exception/TwoFactorAuthException.php
- + lib/core/TwoFactorAuth/Google2FA.php
- + lib/core/TwoFactorAuth/TwoFactorAuth.php
- + lib/core/TwoFactorAuth/TwoFactorAuthInterface.php
Changes:
=====================================
lib/core/TwoFactorAuth/Email2FA.php
=====================================
@@ -0,0 +1,163 @@
+<?php
+
+// (c) Copyright by authors of the Tiki Wiki CMS Groupware Project
+//
+// All Rights Reserved. See copyright.txt for details and a complete list of authors.
+// Licensed under the GNU LESSER GENERAL PUBLIC LICENSE. See license.txt for details.
+namespace Tiki\TwoFactorAuth;
+
+use Exception;
+use Symfony\Component\HttpFoundation\Session\Session;
+use Tiki\TwoFactorAuth\Exception\TwoFactorAuthException;
+use TikiDb;
+use TikiLib;
+use TikiMail;
+
+class Email2FA implements TwoFactorAuthInterface
+{
+ private $twoFATable;
+ private $userlib;
+ private $session;
+ private $crypt;
+
+ public function __construct()
+ {
+ $this->twoFATable = TikiDb::get()->table('tiki_2fa_email_tokens');
+ $this->userlib = TikiLib::lib('user');
+ $this->session = new Session();
+ $this->crypt = TikiLib::lib('crypt');
+ }
+
+ public function generateCode($user, $isEmail = true)
+ {
+ global $prefs;
+
+ $lastRequestTime = $this->session->get('last_2fa_request_time') ?? 0;
+ if (time() - $lastRequestTime < 60) {
+ $errMsg = tr('Please wait about 60 seconds before requesting a new 2fa token.');
+ throw new TwoFactorAuthException($errMsg);
+ }
+
+ $userInfo = $this->userlib->get_user_info($user);
+ if (empty($userInfo)) {
+ $errMsg = tr('User does not exist.');
+ throw new TwoFactorAuthException($errMsg);
+ }
+
+ $tokenLength = intval($prefs['twoFactorAuthEmailTokenLength'] ?? 6);
+ $token = $this->generateRandomString($tokenLength, $prefs['twoFactorAuthEmailTokenChars'] ?? '');
+
+ if ($isEmail) {
+ try {
+ $mail = new TikiMail();
+ $mail->setSubject(tr('Your 2FA Token'));
+ $mail->setText(tr('Your token is: ') . $token);
+ if (empty($userInfo['email'])) {
+ $errMsg = tr('User email is not set.');
+ throw new TwoFactorAuthException($errMsg);
+ }
+ $mail->send([$userInfo['email']]);
+ } catch (Exception $e) {
+ $errMsg = tr('Failed to send email: ' . $e->getMessage());
+ throw new TwoFactorAuthException($errMsg);
+ }
+ }
+
+ $userId = $userInfo['userId'];
+ $hashedToken = $this->crypt->encryptData($token);
+
+ $insertDetails = [
+ 'userId' => $userId,
+ 'token' => $hashedToken,
+ 'type' => 'email',
+ 'attempts' => 0,
+ 'created' => time()
+ ];
+
+ $isInserted = $this->twoFATable->insertOrUpdate($insertDetails, ['userId' => $userId]);
+
+ if (! $isInserted) {
+ $errMsg = tr('Failed to insert 2fa token.');
+ throw new TwoFactorAuthException($errMsg);
+ }
+
+ $this->session->set('last_2fa_request_time', time());
+
+ return ! $isEmail ? $token : true;
+ }
+
+ public function validateCode($user, $code = null)
+ {
+ if (empty($code)) {
+ $errMsg = tr('You have enabled 2FA and 2FA code is required. So, you should login with 2FA.');
+ throw new TwoFactorAuthException($errMsg);
+ }
+
+ global $prefs;
+
+ $userInfo = $this->userlib->get_user_info($user);
+ if (empty($userInfo)) {
+ $errMsg = tr('User does not exist.');
+ throw new TwoFactorAuthException($errMsg);
+ }
+
+ $tokenInfo = $this->twoFATable->fetchFullRow(['userId' => $userInfo['userId']]);
+ if (empty($tokenInfo)) {
+ $errMsg = tr('2FA token info does not exist.');
+ throw new TwoFactorAuthException($errMsg);
+ }
+
+ if (intval($tokenInfo['attempts']) >= 3) {
+ $errMsg = tr('Attempt limit exceeded. Please request a new 2fa token.');
+ throw new TwoFactorAuthException($errMsg);
+ }
+
+ $hashedTokenFromDb = $tokenInfo['token'];
+ $hashedTokenFromClient = $this->crypt->encryptData($code);
+ $attempts = $tokenInfo['attempts'];
+ $created = intval($tokenInfo['created']);
+ $tokenTTL = intval($prefs['twoFactorAuthEmailTokenTTL'] ?? 30) * 60;
+
+ if (time() - $created > $tokenTTL) {
+ $errMsg = tr('2FA token has expired. Please request a new one from login page.');
+ throw new TwoFactorAuthException($errMsg);
+ }
+
+ if ($hashedTokenFromDb !== $hashedTokenFromClient) {
+ $attempts = $tokenInfo['attempts'] + 1;
+ $this->twoFATable->update(['attempts' => $attempts], ['userId' => $userInfo['userId']]);
+ return false;
+ }
+
+ return true;
+ }
+
+ private function generateRandomString($length = 6, $chars = '')
+ {
+ if (! empty($chars)) {
+ $list = [];
+ if (preg_match_all('/(.)-(.)/', $chars, $m)) {
+ foreach ($m[0] as $k => $_) {
+ $class = '';
+ for ($i = ord($m[1][$k]); $i <= max(ord($m[1][$k]), ord($m[2][$k])); $i++) {
+ $class .= chr($i);
+ }
+ $list[] = $class;
+ }
+ $chars = str_replace($m[0], '', $chars);
+ }
+ if ($chars) {
+ $list[] = $chars;
+ }
+ } else {
+ $list = ['aeiou', 'AEIOU', 'bcdfghjklmnpqrstvwxyz', 'BCDFGHJKLMNPQRSTVWXYZ', '0123456789'];
+ }
+ shuffle($list);
+ $randomString = '';
+ for ($i = 0; $i < $length; $i++) {
+ $ch = $list[$i % count($list)];
+ $randomString .= $ch[rand(0, strlen($ch) - 1)];
+ }
+ return $randomString;
+ }
+}
=====================================
lib/core/TwoFactorAuth/Exception/Exception.php
=====================================
@@ -0,0 +1,17 @@
+<?php
+
+// (c) Copyright by authors of the Tiki Wiki CMS Groupware Project
+//
+// All Rights Reserved. See copyright.txt for details and a complete list of authors.
+// Licensed under the GNU LESSER GENERAL PUBLIC LICENSE. See license.txt for details.
+namespace Tiki\TwoFactorAuth\Exception;
+
+use Exception as BaseException;
+
+class Exception extends BaseException
+{
+ public function __construct(string $message, ?BaseException $previous = null, int $code = 0)
+ {
+ parent::__construct($message, $code, $previous);
+ }
+}
=====================================
lib/core/TwoFactorAuth/Exception/TwoFactorAuthException.php
=====================================
@@ -0,0 +1,15 @@
+<?php
+
+// (c) Copyright by authors of the Tiki Wiki CMS Groupware Project
+//
+// All Rights Reserved. See copyright.txt for details and a complete list of authors.
+// Licensed under the GNU LESSER GENERAL PUBLIC LICENSE. See license.txt for details.
+namespace Tiki\TwoFactorAuth\Exception;
+
+class TwoFactorAuthException extends Exception
+{
+ public function __construct(string $message, ?Exception $previous = null, int $code = 0)
+ {
+ parent::__construct($message, $previous, $code);
+ }
+}
=====================================
lib/core/TwoFactorAuth/Google2FA.php
=====================================
@@ -0,0 +1,46 @@
+<?php
+
+// (c) Copyright by authors of the Tiki Wiki CMS Groupware Project
+//
+// All Rights Reserved. See copyright.txt for details and a complete list of authors.
+// Licensed under the GNU LESSER GENERAL PUBLIC LICENSE. See license.txt for details.
+namespace Tiki\TwoFactorAuth;
+
+use TikiLib;
+use PragmaRX\Google2FA\Google2FA as PragmaGoogle2FA;
+use Tiki\TwoFactorAuth\Exception\TwoFactorAuthException;
+
+class Google2FA implements TwoFactorAuthInterface
+{
+ private $google2fa;
+ private $userlib;
+
+ public function __construct()
+ {
+ $this->google2fa = new PragmaGoogle2FA();
+ $this->userlib = TikiLib::lib('user');
+ }
+
+ public function generateCode($user, $isEmail = true)
+ {
+ // For Google2FA, code generation is typically done on the client-side
+ return true;
+ }
+
+ public function validateCode($user, $code = null)
+ {
+ if (empty($code)) {
+ $errMsg = tr('You have enabled 2FA and 2FA code is required. So, you should login with 2FA.');
+ throw new TwoFactorAuthException($errMsg);
+ }
+
+ $twoFactorSecret = $this->userlib->get_2_factor_secret($user);
+ $result = $this->google2fa->verifyKey($twoFactorSecret, $code, 2);
+
+ if (! $result) {
+ $this->userlib->handleUnsuccessfulLogin($user);
+ }
+
+ return $result;
+ }
+}
=====================================
lib/core/TwoFactorAuth/TwoFactorAuth.php
=====================================
@@ -0,0 +1,96 @@
+<?php
+
+// (c) Copyright by authors of the Tiki Wiki CMS Groupware Project
+//
+// All Rights Reserved. See copyright.txt for details and a complete list of authors.
+// Licensed under the GNU LESSER GENERAL PUBLIC LICENSE. See license.txt for details.
+namespace Tiki\TwoFactorAuth;
+
+use Tiki\TwoFactorAuth\Exception\TwoFactorAuthException;
+use TikiLib;
+
+class TwoFactorAuth
+{
+ /** @var string string representation for google 2FA */
+ public const GOOGLE_2FA = 'google2FA';
+
+ /** @var string string representation for email 2FA */
+ public const EMAIL_2FA = 'email2FA';
+
+ /** @var string The default 2FA type */
+ public const DEFAULT_2FA = self::GOOGLE_2FA;
+
+ /** @var string[] The list of available 2FA types */
+ public const AVAILABLE_2FA_TYPES = [self::GOOGLE_2FA, self::EMAIL_2FA];
+
+ public static function getTwoFactorAuthTypeEnabled(): string
+ {
+ global $prefs;
+
+ // If not set or empty, always default to the default type
+ return $prefs['twoFactorAuthType'] ?: self::DEFAULT_2FA;
+ }
+
+ public static function getTwoFactorAuth()
+ {
+ return self::getTwoFactorAuthByType(self::getTwoFactorAuthTypeEnabled());
+ }
+
+ public static function getTwoFactorAuthByType($type)
+ {
+ $authType = ucfirst($type);
+ $class = "\\Tiki\\TwoFactorAuth\\$authType";
+
+ if (! in_array($type, self::AVAILABLE_2FA_TYPES, true) || ! class_exists($class)) {
+ $errMsg = tr('Two factor auth type not found: ' . $type . ', Supported types are: ' . implode(', ', self::AVAILABLE_2FA_TYPES));
+ throw new TwoFactorAuthException($errMsg);
+ }
+
+ if (! in_array(TwoFactorAuthInterface::class, class_implements($class), true)) {
+ $errMsg = tr('The class ' . $class . ' does not implement the required TwoFactorAuthInterface.');
+ throw new TwoFactorAuthException($errMsg);
+ }
+
+ $twoFactorAuth = new $class();
+
+ return $twoFactorAuth;
+ }
+
+ public static function isMFARequired($user)
+ {
+ global $prefs, $userlib;
+
+ $mfaIntervalDaysPrefs = intval($prefs['twoFactorAuthIntervalDays']);
+ $requireMfa = false;
+
+ if ($prefs['twoFactorAuth'] == 'y') {
+ $userInfo = $userlib->get_user_info($user);
+ if (! empty($userInfo['twoFactorSecret'])) {
+ $lastMfaDateDb = intval($userInfo['last_mfa_date']);
+ if ($mfaIntervalDaysPrefs > 0) {
+ if (empty($lastMfaDateDb) || (time() - $lastMfaDateDb) > ($mfaIntervalDaysPrefs * 86400)) {
+ $requireMfa = true;
+ }
+ } else {
+ $requireMfa = true;
+ }
+ }
+ }
+
+ return $requireMfa;
+ }
+
+ public static function get2FactorSecret($user)
+ {
+ $userlib = TikiLib::lib('user');
+ $twoFAType = self::getTwoFactorAuthTypeEnabled();
+
+ if ($twoFAType === self::GOOGLE_2FA) {
+ return $userlib->get_2_factor_secret($user);
+ } elseif ($twoFAType === self::EMAIL_2FA) {
+ return true;
+ } else {
+ throw new TwoFactorAuthException(tr('Unsupported 2FA type: ' . $twoFAType));
+ }
+ }
+}
=====================================
lib/core/TwoFactorAuth/TwoFactorAuthInterface.php
=====================================
@@ -0,0 +1,13 @@
+<?php
+
+// (c) Copyright by authors of the Tiki Wiki CMS Groupware Project
+//
+// All Rights Reserved. See copyright.txt for details and a complete list of authors.
+// Licensed under the GNU LESSER GENERAL PUBLIC LICENSE. See license.txt for details.
+namespace Tiki\TwoFactorAuth;
+
+interface TwoFactorAuthInterface
+{
+ public function generateCode($user, $isEmail = true);
+ public function validateCode($user, $code = null);
+}
View it on GitLab: https://gitlab.com/tikiwiki/tiki/-/commit/c3845871da1e388815ab94aabf85ff10de1d4a83
--
View it on GitLab: https://gitlab.com/tikiwiki/tiki/-/commit/c3845871da1e388815ab94aabf85ff10de1d4a83
You're receiving this email because of your account on gitlab.com. Manage all notifications: https://gitlab.com/-/profile/notifications | Help: https://gitlab.com/help
_______________________________________________
TikiWiki-cvs mailing list
[email protected]
https://lists.sourceforge.net/lists/listinfo/tikiwiki-cvs