mirror of
https://github.com/SuiteCRM/SuiteCRM-Core.git
synced 2025-09-02 08:09:19 +08:00
Add encoding to Totp Secret and Backup Codes
- Remove double call for backup codes
This commit is contained in:
parent
f829346938
commit
bab7d9a202
7 changed files with 195 additions and 25 deletions
|
@ -192,6 +192,14 @@ services:
|
|||
|
||||
security.listener.json_login_ldap.main: '@App\Security\Ldap\AppCheckLdapCredentialsListener'
|
||||
|
||||
App\Security\TwoFactor\EventListener\TotpEncryptionListener:
|
||||
arguments:
|
||||
- '@App\Security\TwoFactor\LegacyHandler\BlowfishCodeHandler'
|
||||
tags:
|
||||
- { name: doctrine.event_listener, event: prePersist, entity: App\Module\Users\Entity\User }
|
||||
- { name: doctrine.event_listener, event: preUpdate, entity: App\Module\Users\Entity\User }
|
||||
- { name: doctrine.event_listener, event: postLoad, entity: App\Module\Users\Entity\User }
|
||||
|
||||
App\Security\Ldap\AppCheckLdapCredentialsListener:
|
||||
class: App\Security\Ldap\AppCheckLdapCredentialsListener
|
||||
tags:
|
||||
|
|
|
@ -97,7 +97,6 @@ export class TwoFactorComponent implements OnInit {
|
|||
next: (response) => {
|
||||
this.qrCodeUrl = response?.url;
|
||||
this.qrCodeSvg = response?.svg;
|
||||
this.backupCodes = response?.backupCodes;
|
||||
this.areRecoveryCodesGenerated.set(true);
|
||||
this.isQrCodeGenerated.set(true);
|
||||
},
|
||||
|
|
|
@ -31,7 +31,6 @@ namespace App\Authentication\Controller;
|
|||
use App\Authentication\LegacyHandler\UserHandler;
|
||||
use App\Data\LegacyHandler\PreparedStatementHandler;
|
||||
use App\Engine\LegacyHandler\CacheManagerHandler;
|
||||
use App\Security\TwoFactor\BackupCodeGenerator;
|
||||
use Doctrine\DBAL\Exception;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Endroid\QrCode\Builder\Builder;
|
||||
|
@ -77,7 +76,6 @@ class SecurityController extends AbstractController
|
|||
private $entityManager;
|
||||
private PreparedStatementHandler $preparedStatementHandler;
|
||||
|
||||
private BackupCodeGenerator $backupCodeGenerator;
|
||||
|
||||
private UserHandler $userHandler;
|
||||
|
||||
|
@ -92,7 +90,6 @@ class SecurityController extends AbstractController
|
|||
RequestStack $requestStack,
|
||||
EntityManagerInterface $entityManager,
|
||||
PreparedStatementHandler $preparedStatementHandler,
|
||||
BackupCodeGenerator $backupCodeGenerator,
|
||||
UserHandler $userHandler,
|
||||
CacheManagerHandler $cacheManagerHandler
|
||||
)
|
||||
|
@ -101,7 +98,6 @@ class SecurityController extends AbstractController
|
|||
$this->requestStack = $requestStack;
|
||||
$this->entityManager = $entityManager;
|
||||
$this->preparedStatementHandler = $preparedStatementHandler;
|
||||
$this->backupCodeGenerator = $backupCodeGenerator;
|
||||
$this->userHandler = $userHandler;
|
||||
$this->cacheManagerHandler = $cacheManagerHandler;
|
||||
}
|
||||
|
@ -155,9 +151,7 @@ class SecurityController extends AbstractController
|
|||
|
||||
$user->setTotpSecret($secret);
|
||||
|
||||
$backupCodes = $this->backupCodeGenerator->generate();
|
||||
|
||||
$this->setupBackupCodes($user,$backupCodes);
|
||||
$qrCodeUrl = $totpAuthenticator->getQRContent($user);
|
||||
|
||||
$this->preparedStatementHandler->update(
|
||||
'UPDATE users SET totp_secret = :totp_secret WHERE id = :id',
|
||||
|
@ -167,12 +161,9 @@ class SecurityController extends AbstractController
|
|||
|
||||
$this->entityManager->flush();
|
||||
|
||||
$qrCodeUrl = $totpAuthenticator->getQRContent($user);
|
||||
|
||||
$response = [
|
||||
'url' => $qrCodeUrl,
|
||||
'svg' => $this->displayQRCode($qrCodeUrl),
|
||||
'backupCodes' => $backupCodes
|
||||
];
|
||||
|
||||
return new Response(json_encode($response), Response::HTTP_OK);
|
||||
|
@ -184,6 +175,8 @@ class SecurityController extends AbstractController
|
|||
$id = $user->getId();
|
||||
|
||||
$this->userHandler->setUserPreference('is_two_factor_enabled', false);
|
||||
$user->setTotpSecret(null);
|
||||
$user->setBackupCodes(null);
|
||||
|
||||
$this->preparedStatementHandler->update(
|
||||
'UPDATE users SET totp_secret = NULL, is_totp_enabled = 0, backup_codes = NULL WHERE id = :id',
|
||||
|
@ -371,12 +364,4 @@ class SecurityController extends AbstractController
|
|||
|
||||
return $result->getString();
|
||||
}
|
||||
|
||||
protected function setupBackupCodes($user, $backupCodes): void
|
||||
{
|
||||
$this->preparedStatementHandler->update("UPDATE users SET backup_codes = :backup_codes WHERE id = :id",
|
||||
['id' => $user->getId(), 'backup_codes' => $backupCodes],
|
||||
[['param' => 'id', 'type' => 'string'], ['param' => 'backup_codes', 'type' => 'json']]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -109,8 +109,9 @@ class GenerateBackupCodesHandler extends LegacyHandler implements ProcessHandler
|
|||
*/
|
||||
public function run(Process $process): void
|
||||
{
|
||||
$user = $this->security->getToken()->getUser();
|
||||
$user = $this->security->getToken()?->getUser();
|
||||
$backupCodes = $this->backupCodeGenerator->generate();
|
||||
$user?->setBackupCodes($backupCodes);
|
||||
$this->setupBackupCodes($user, $backupCodes);
|
||||
|
||||
|
||||
|
|
|
@ -0,0 +1,85 @@
|
|||
<?php
|
||||
/**
|
||||
* SuiteCRM is a customer relationship management program developed by SalesAgility Ltd.
|
||||
* Copyright (C) 2025 SalesAgility Ltd.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU Affero General Public License version 3 as published by the
|
||||
* Free Software Foundation with the addition of the following permission added
|
||||
* to Section 15 as permitted in Section 7(a): FOR ANY PART OF THE COVERED WORK
|
||||
* IN WHICH THE COPYRIGHT IS OWNED BY SALESAGILITY, SALESAGILITY DISCLAIMS THE
|
||||
* WARRANTY OF NON INFRINGEMENT OF THIRD PARTY RIGHTS.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
* FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
|
||||
* details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License
|
||||
* version 3, these Appropriate Legal Notices must retain the display of the
|
||||
* "Supercharged by SuiteCRM" logo. If the display of the logos is not reasonably
|
||||
* feasible for technical reasons, the Appropriate Legal Notices must display
|
||||
* the words "Supercharged by SuiteCRM".
|
||||
*/
|
||||
|
||||
namespace App\Security\TwoFactor\EventListener;
|
||||
|
||||
use App\Security\TwoFactor\LegacyHandler\BlowfishCodeHandler;
|
||||
use Doctrine\Persistence\Event\LifecycleEventArgs;
|
||||
|
||||
class TotpEncryptionListener
|
||||
{
|
||||
protected BlowfishCodeHandler $blowfish;
|
||||
|
||||
public function __construct(
|
||||
BlowfishCodeHandler $blowfish
|
||||
)
|
||||
{
|
||||
$this->blowfish = $blowfish;
|
||||
}
|
||||
|
||||
public function prePersist(LifecycleEventArgs $args): void
|
||||
{
|
||||
$this->encodeTotpFields($args);
|
||||
}
|
||||
|
||||
public function preUpdate(LifecycleEventArgs $args): void
|
||||
{
|
||||
$this->encodeTotpFields($args);
|
||||
}
|
||||
|
||||
public function postLoad(LifecycleEventArgs $args): void
|
||||
{
|
||||
$user = $args->getObject();
|
||||
$backupCodes = $user->getBackupCodes();
|
||||
$totpSecret = $user->getTotpSecret();
|
||||
|
||||
if (!empty($backupCodes)) {
|
||||
$user->setBackupCodes($this->blowfish->decode('Users', $backupCodes));
|
||||
}
|
||||
if (!empty($totpSecret)) {
|
||||
$user->setTotpSecret($this->blowfish->decode('Users', $totpSecret));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param LifecycleEventArgs $args
|
||||
* @return void
|
||||
*/
|
||||
public function encodeTotpFields(LifecycleEventArgs $args): void
|
||||
{
|
||||
$user = $args->getObject();
|
||||
$backupCodes = $user->getBackupCodes();
|
||||
$totpSecret = $user->getTotpSecret();
|
||||
|
||||
if (!empty($backupCodes)) {
|
||||
$user->setBackupCodes($this->blowfish->encode('Users', $backupCodes));
|
||||
}
|
||||
if (!empty($totpSecret)) {
|
||||
$user->setTotpSecret($this->blowfish->encode('Users', $totpSecret));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,89 @@
|
|||
<?php
|
||||
/**
|
||||
* SuiteCRM is a customer relationship management program developed by SalesAgility Ltd.
|
||||
* Copyright (C) 2025 SalesAgility Ltd.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU Affero General Public License version 3 as published by the
|
||||
* Free Software Foundation with the addition of the following permission added
|
||||
* to Section 15 as permitted in Section 7(a): FOR ANY PART OF THE COVERED WORK
|
||||
* IN WHICH THE COPYRIGHT IS OWNED BY SALESAGILITY, SALESAGILITY DISCLAIMS THE
|
||||
* WARRANTY OF NON INFRINGEMENT OF THIRD PARTY RIGHTS.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
* FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
|
||||
* details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License
|
||||
* version 3, these Appropriate Legal Notices must retain the display of the
|
||||
* "Supercharged by SuiteCRM" logo. If the display of the logos is not reasonably
|
||||
* feasible for technical reasons, the Appropriate Legal Notices must display
|
||||
* the words "Supercharged by SuiteCRM".
|
||||
*/
|
||||
|
||||
namespace App\Security\TwoFactor\LegacyHandler;
|
||||
|
||||
use App\Engine\LegacyHandler\LegacyHandler;
|
||||
|
||||
class BlowfishCodeHandler extends LegacyHandler {
|
||||
|
||||
public const HANDLER_KEY = 'blowfish-code';
|
||||
|
||||
public function getHandlerKey(): string
|
||||
{
|
||||
return self::HANDLER_KEY;
|
||||
}
|
||||
|
||||
public function getKey(string $key): string {
|
||||
return blowfishGetKey($key);
|
||||
}
|
||||
|
||||
public function encode($key, $value): array|string
|
||||
{
|
||||
|
||||
$this->init();
|
||||
|
||||
$key = $this->getKey($key);
|
||||
|
||||
if (!is_array($value)) {
|
||||
$value = blowfishEncode($key, $value);
|
||||
$this->close();
|
||||
return $value;
|
||||
}
|
||||
|
||||
$result = [];
|
||||
|
||||
foreach ($value as $i => $item) {
|
||||
$result[$i] = blowfishEncode($key, $item);
|
||||
}
|
||||
|
||||
$this->close();
|
||||
return $result;
|
||||
}
|
||||
|
||||
public function decode($key, $value) {
|
||||
|
||||
$this->init();
|
||||
|
||||
$key = $this->getKey($key);
|
||||
|
||||
if (!is_array($value)) {
|
||||
$value = blowfishDecode($key, $value);
|
||||
$this->close();
|
||||
return $value;
|
||||
}
|
||||
|
||||
$result = [];
|
||||
|
||||
foreach ($value as $i => $item) {
|
||||
$result[$i] = blowfishDecode($key, $item);
|
||||
}
|
||||
|
||||
$this->close();
|
||||
return $result;
|
||||
}
|
||||
}
|
|
@ -1170,7 +1170,7 @@ class User implements UserInterface, EquatableInterface, PasswordAuthenticatedUs
|
|||
|
||||
public function getBackupCodes(): array
|
||||
{
|
||||
return $this->backupCodes;
|
||||
return $this->backupCodes ?? [];
|
||||
}
|
||||
|
||||
public function setBackupCodes(?array $backupCodes): self
|
||||
|
@ -1272,7 +1272,6 @@ class User implements UserInterface, EquatableInterface, PasswordAuthenticatedUs
|
|||
|
||||
public function getTotpAuthenticationConfiguration(): ?TotpConfigurationInterface
|
||||
{
|
||||
// You could persist the other configuration options in the user entity to make it individual per user.
|
||||
return new TotpConfiguration($this->getTotpSecret(), TotpConfiguration::ALGORITHM_SHA1, 30, 6);
|
||||
}
|
||||
|
||||
|
@ -1306,10 +1305,13 @@ class User implements UserInterface, EquatableInterface, PasswordAuthenticatedUs
|
|||
public function isBackupCode(string $code): bool
|
||||
{
|
||||
$correctCode = false;
|
||||
if (in_array($code, $this->getBackupCodes())){
|
||||
$backupCodes = $this->getBackupCodes();
|
||||
|
||||
if (in_array($code, $backupCodes, true)){
|
||||
$correctCode = true;
|
||||
$this->invalidateBackupCode($code);
|
||||
}
|
||||
|
||||
return $correctCode;
|
||||
}
|
||||
|
||||
|
@ -1318,10 +1320,11 @@ class User implements UserInterface, EquatableInterface, PasswordAuthenticatedUs
|
|||
*/
|
||||
public function invalidateBackupCode(string $code): void
|
||||
{
|
||||
$key = array_search($code, $this->backupCodes);
|
||||
$key = array_search($code, $this->getBackupCodes(), true);
|
||||
if ($key !== false){
|
||||
unset($this->backupCodes[$key]);
|
||||
unset($this->getBackupCodes()[$key]);
|
||||
}
|
||||
|
||||
$this->backupCodes = array_values($this->backupCodes);
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue