This commit is contained in:
Yuri Kuznetsov
2021-09-16 17:35:32 +03:00
parent ef13bffcb9
commit 9add3fa538
33 changed files with 1917 additions and 2 deletions

View File

@@ -0,0 +1,80 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM - Open Source CRM application.
* Copyright (C) 2014-2021 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
* Website: https://www.espocrm.com
*
* EspoCRM is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* EspoCRM 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with EspoCRM. If not, see http://www.gnu.org/licenses/.
*
* The interactive user interfaces in modified source and object code versions
* of this program must display Appropriate Legal Notices, as required under
* Section 5 of the GNU General Public License version 3.
*
* In accordance with Section 7(b) of the GNU General Public License version 3,
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
namespace Espo\Controllers;
use Espo\Core\Exceptions\Forbidden;
use Espo\Core\Exceptions\BadRequest;
use Espo\Core\Api\Request;
use Espo\Services\TwoFactorSms as Service;
use Espo\Entities\User;
class TwoFactorSms
{
private $service;
public function __construct(Service $service, User $user)
{
$this->service = $service;
$this->user = $user;
if (
!$this->user->isAdmin() &&
!$this->user->isRegular()
) {
throw new Forbidden();
}
}
public function postActionSendCode(Request $request): bool
{
$data = $request->getParsedBody();
$id = $data->id ?? null;
$phoneNumber = $data->phoneNumber ?? null;
if (!$id) {
throw new BadRequest("No 'id'.");
}
if (!$phoneNumber) {
throw new BadRequest("No 'phoneNumber'.");
}
if (!$this->user->isAdmin() && $id !== $this->user->getId()) {
throw new Forbidden();
}
$this->service->sendCode($id, $phoneNumber);
return true;
}
}

View File

@@ -0,0 +1,104 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM - Open Source CRM application.
* Copyright (C) 2014-2021 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
* Website: https://www.espocrm.com
*
* EspoCRM is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* EspoCRM 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with EspoCRM. If not, see http://www.gnu.org/licenses/.
*
* The interactive user interfaces in modified source and object code versions
* of this program must display Appropriate Legal Notices, as required under
* Section 5 of the GNU General Public License version 3.
*
* In accordance with Section 7(b) of the GNU General Public License version 3,
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
namespace Espo\Core\Authentication\TwoFactor\Sms;
use Espo\ORM\EntityManager;
use Espo\Entities\User;
use Espo\Entities\UserData;
use Espo\Repositories\UserData as UserDataRepository;
use Espo\Core\Authentication\TwoFactor\Login;
use Espo\Core\Authentication\Result;
use Espo\Core\Authentication\ResultData;
use Espo\Core\Authentication\FailReason;
use Espo\Core\Api\Request;
class SmsLogin implements Login
{
private $entityManager;
private $util;
public function __construct(EntityManager $entityManager, Util $util)
{
$this->entityManager = $entityManager;
$this->util = $util;
}
public function login(Result $result, Request $request): Result
{
$code = $request->getHeader('Espo-Authorization-Code');
if (!$code) {
$this->util->sendCode($result->getLoggedUser());
return Result::secondStepRequired($result->getUser(), $this->getLoginData());
}
$loggedUser = $result->getLoggedUser();
if ($this->verifyCode($loggedUser, $code)) {
return $result;
}
return Result::fail(FailReason::CODE_NOT_VERIFIED);
}
private function getLoginData(): ResultData
{
return ResultData::createWithMessage('enterCodeSentBySms');
}
private function verifyCode(User $user, string $code): bool
{
$userData = $this->getUserDataRepository()->getByUserId($user->getId());
if (!$userData) {
return false;
}
if (!$userData->get('auth2FA')) {
return false;
}
if ($userData->get('auth2FAMethod') !== 'Sms') {
return false;
}
return $this->util->verifyCode($user, $code);
}
private function getUserDataRepository(): UserDataRepository
{
return $this->entityManager->getRepository(UserData::ENTITY_TYPE);
}
}

View File

@@ -0,0 +1,71 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM - Open Source CRM application.
* Copyright (C) 2014-2021 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
* Website: https://www.espocrm.com
*
* EspoCRM is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* EspoCRM 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with EspoCRM. If not, see http://www.gnu.org/licenses/.
*
* The interactive user interfaces in modified source and object code versions
* of this program must display Appropriate Legal Notices, as required under
* Section 5 of the GNU General Public License version 3.
*
* In accordance with Section 7(b) of the GNU General Public License version 3,
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
namespace Espo\Core\Authentication\TwoFactor\Sms;
use Espo\Entities\User;
use Espo\Core\Authentication\TwoFactor\UserSetup;
use Espo\Core\Exceptions\Error;
use stdClass;
class SmsUserSetup implements UserSetup
{
private $util;
public function __construct(Util $util)
{
$this->util = $util;
}
public function getData(User $user): stdClass
{
return (object) [
'phoneNumberList' => $user->getPhoneNumberGroup()->getNumberList(),
];
}
public function verifyData(User $user, stdClass $payloadData): bool
{
$code = $payloadData->code ?? null;
if ($code === null) {
throw new Error("No code.");
}
$codeModified = str_replace(' ', '', trim($code));
if (!$codeModified) {
return false;
}
return $this->util->verifyCode($user, $codeModified);
}
}

View File

@@ -0,0 +1,329 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM - Open Source CRM application.
* Copyright (C) 2014-2021 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
* Website: https://www.espocrm.com
*
* EspoCRM is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* EspoCRM 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with EspoCRM. If not, see http://www.gnu.org/licenses/.
*
* The interactive user interfaces in modified source and object code versions
* of this program must display Appropriate Legal Notices, as required under
* Section 5 of the GNU General Public License version 3.
*
* In accordance with Section 7(b) of the GNU General Public License version 3,
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
namespace Espo\Core\Authentication\TwoFactor\Sms;
use Espo\Core\Exceptions\Error;
use Espo\Core\Exceptions\Forbidden;
use Espo\Core\Utils\Config;
use Espo\Core\Sms\SmsSender;
use Espo\Core\Sms\SmsFactory;
use Espo\Core\Utils\Language;
use Espo\Core\Field\DateTime;
use Espo\ORM\EntityManager;
use Espo\ORM\Query\Part\Condition as Cond;
use Espo\Entities\User;
use Espo\Entities\Sms;
use Espo\Entities\TwoFactorCode;
use Espo\Entities\UserData;
use Espo\Repositories\UserData as UserDataRepository;
use const STR_PAD_LEFT;
class Util
{
/**
* A lifetime of a code.
*/
private const CODE_LIFETIME_PERIOD = '10 minutes';
/*
* A max number of attempts to try a single code.
*/
private const CODE_ATTEMPTS_COUNT = 5;
/**
* A length of a code.
*/
private const CODE_LENGTH = 6;
/**
* A max number of codes tried by a user in a period defined by `CODE_LIMIT_PERIOD`.
*/
private const CODE_LIMIT = 5;
/**
* A period for limiting trying to too many codes.
*/
private const CODE_LIMIT_PERIOD = '20 minutes';
private $entityManager;
private $config;
private $smsSender;
private $language;
private $smsFactory;
public function __construct(
EntityManager $entityManager,
Config $config,
SmsSender $smsSender,
Language $language,
SmsFactory $smsFactory
) {
$this->entityManager = $entityManager;
$this->config = $config;
$this->smsSender = $smsSender;
$this->language = $language;
$this->smsFactory = $smsFactory;
}
public function storePhoneNumber(User $user, string $phoneNumber): void
{
$this->checkPhoneNumberIsUsers($user, $phoneNumber);
$userData = $this->getUserDataRepository()->getByUserId($user->getId());
$userData->set('auth2FASmsPhoneNumber', $phoneNumber);
$this->entityManager->saveEntity($userData);
}
public function verifyCode(User $user, string $code): bool
{
$codeEntity = $this->findCodeEntity($user);
if (!$codeEntity) {
return false;
}
if ($codeEntity->getAttemptsLeft() <= 1) {
$this->decrementAttemptsLeft($codeEntity);
$this->inactivateExistingCodeRecords($user);
return false;
}
if ($codeEntity->getCode() !== $code) {
$this->decrementAttemptsLeft($codeEntity);
return false;
}
if (!$this->isCodeValidByLifetime($codeEntity)) {
$this->inactivateExistingCodeRecords($user);
return false;
}
$this->inactivateExistingCodeRecords($user);
return true;
}
public function sendCode(User $user, ?string $phoneNumber = null): void
{
if ($phoneNumber === null) {
$phoneNumber = $this->getPhoneNumber($user);
}
$this->checkPhoneNumberIsUsers($user, $phoneNumber);
$this->checkCodeLimit($user);
$code = $this->generateCode();
$this->inactivateExistingCodeRecords($user);
$this->createCodeRecord($user, $code);
$sms = $this->createSms($user, $code, $phoneNumber);
$this->smsSender->send($sms);
}
private function isCodeValidByLifetime(TwoFactorCode $codeEntity): bool
{
$period = $this->config->get('auth2FASmsCodeLifetimePeriod') ?? self::CODE_LIFETIME_PERIOD;
$validUntil = $codeEntity->getCreatedAt()->modify($period);
if (DateTime::createNow()->diff($validUntil)->invert) {
return false;
}
return true;
}
private function findCodeEntity(User $user): ?TwoFactorCode
{
return $this->entityManager
->getRDBRepository(TwoFactorCode::ENTITY_TYPE)
->where([
'method' => 'Sms',
'userId' => $user->getId(),
'isActive' => true,
])
->findOne();
}
private function getPhoneNumber(User $user): string
{
$userData = $this->getUserDataRepository()->getByUserId($user->getId());
if (!$userData) {
throw new Error("UserData not found.");
}
$phoneNumber = $userData->get('auth2FASmsPhoneNumber');
if ($phoneNumber) {
return $phoneNumber;
}
if ($user->getPhoneNumberGroup()->getCount() === 0) {
throw new Error("User does not have phone number.");
}
return $user->getPhoneNumberGroup()->getPrimary()->getNumber();
}
private function checkPhoneNumberIsUsers(User $user, string $phoneNumber): void
{
$userNumberList = array_map(
function (string $item) {
return strtolower($item);
},
$user->getPhoneNumberGroup()->getNumberList()
);
if (!in_array(strtolower($phoneNumber), $userNumberList)) {
throw new Forbidden("Phone number is not one of user's.");
}
}
private function checkCodeLimit(User $user): void
{
$limit = $this->config->get('auth2FASmsCodeLimit') ?? self::CODE_LIMIT;
$period = $this->config->get('auth2FASmsCodeLimitPeriod') ?? self::CODE_LIMIT_PERIOD;
$from = DateTime::createNow()
->modify('-' . $period)
->getString();
$count = $this->entityManager
->getRDBRepository(TwoFactorCode::ENTITY_TYPE)
->where(
Cond::and(
Cond::equal(Cond::column('method'), 'Sms'),
Cond::equal(Cond::column('userId'), $user->getId()),
Cond::greaterOrEqual(Cond::column('createdAt'), $from),
Cond::lessOrEqual(Cond::column('attemptsLeft'), 0),
)
)
->count();
if ($count >= $limit) {
throw new Forbidden("Max code count exceeded.");
}
}
private function generateCode(): string
{
$codeLength = $this->config->get('auth2FASmsCodeLength') ?? self::CODE_LENGTH;
$max = pow(10, $codeLength) - 1;
return str_pad(
(string) random_int(0, $max),
$codeLength,
'0',
STR_PAD_LEFT
);
}
private function createSms(User $user, string $code, string $phoneNumber): Sms
{
$fromNumber = $this->config->get('outboundSmsFromNumber');
$bodyTpl = $this->language->translate('yourAuthenticationCode', 'messages', 'User');
$body = str_replace('{code}', $code, $bodyTpl);
$sms = $this->smsFactory->create();
$sms->setFromNumber($fromNumber);
$sms->setBody($body);
$sms->addToNumber($phoneNumber);
return $sms;
}
private function inactivateExistingCodeRecords(User $user): void
{
$query = $this->entityManager
->getQueryBuilder()
->update()
->in(TwoFactorCode::ENTITY_TYPE)
->where([
'userId' => $user->getId(),
'method' => 'Sms',
])
->set([
'isActive' => false,
])
->build();
$this->entityManager
->getQueryExecutor()
->execute($query);
}
private function createCodeRecord(User $user, string $code): void
{
$this->entityManager->createEntity(TwoFactorCode::ENTITY_TYPE, [
'code' => $code,
'userId' => $user->getId(),
'method' => 'Sms',
'attemptsLeft' => $this->getCodeAttemptsCount(),
]);
}
private function getUserDataRepository(): UserDataRepository
{
return $this->entityManager->getRepository(UserData::ENTITY_TYPE);
}
private function decrementAttemptsLeft(TwoFactorCode $codeEntity): void
{
$codeEntity->decrementAttemptsLeft();
$this->entityManager->saveEntity($codeEntity);
}
private function getCodeAttemptsCount(): int
{
return $this->config->get('auth2FASmsCodeAttemptsCount') ?? self::CODE_ATTEMPTS_COUNT;
}
}

View File

@@ -217,5 +217,10 @@ class DefaultBinding implements BindingProcessor
'Espo\\Core\\WebSocket\\Sender',
'Espo\\Core\\WebSocket\\SenderFactory'
);
$binder->bindFactory(
'Espo\\Core\\Sms\\Sender',
'Espo\\Core\\Sms\\SenderFactory'
);
}
}

View File

@@ -0,0 +1,96 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM - Open Source CRM application.
* Copyright (C) 2014-2021 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
* Website: https://www.espocrm.com
*
* EspoCRM is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* EspoCRM 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with EspoCRM. If not, see http://www.gnu.org/licenses/.
*
* The interactive user interfaces in modified source and object code versions
* of this program must display Appropriate Legal Notices, as required under
* Section 5 of the GNU General Public License version 3.
*
* In accordance with Section 7(b) of the GNU General Public License version 3,
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
namespace Espo\Core\Formula\Functions\ExtGroup\SmsGroup;
use Espo\Core\Formula\Functions\BaseFunction;
use Espo\Core\Formula\ArgumentList;
use Espo\Core\Sms\SmsSender;
use Espo\Entities\Sms;
use Espo\Core\Di;
use Exception;
class SendType extends BaseFunction implements
Di\EntityManagerAware,
Di\InjectableFactoryAware
{
use Di\EntityManagerSetter;
use Di\InjectableFactorySetter;
public function process(ArgumentList $args)
{
if (count($args) < 1) {
$this->throwTooFewArguments(1);
}
$evaluatedArgs = $this->evaluate($args);
$id = $evaluatedArgs[0];
if (!$id || !is_string($id)) {
$this->throwBadArgumentType(1, 'string');
}
/** @var Sms $sms */
$sms = $this->entityManager->getEntity(Sms::ENTITY_TYPE, $id);
if (!$sms) {
$this->log("Sms '{$id}' does not exist.");
return false;
}
if ($sms->getStatus() === Sms::STATUS_SENT) {
$this->log("Can't send SMS that has 'Sent' status.");
return false;
}
try {
$this->createSender()->send($sms);
}
catch (Exception $e) {
$message = $e->getMessage();
$this->log("Error while sending SMS. Message: {$message}." , 'error');
return false;
}
return true;
}
private function createSender(): SmsSender
{
return $this->injectableFactory->create(SmsSender::class);
}
}

View File

@@ -0,0 +1,38 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM - Open Source CRM application.
* Copyright (C) 2014-2021 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
* Website: https://www.espocrm.com
*
* EspoCRM is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* EspoCRM 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with EspoCRM. If not, see http://www.gnu.org/licenses/.
*
* The interactive user interfaces in modified source and object code versions
* of this program must display Appropriate Legal Notices, as required under
* Section 5 of the GNU General Public License version 3.
*
* In accordance with Section 7(b) of the GNU General Public License version 3,
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
namespace Espo\Core\Sms;
/**
* An SMS sender.
*/
interface Sender
{
public function send(Sms $sms): void;
}

View File

@@ -0,0 +1,74 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM - Open Source CRM application.
* Copyright (C) 2014-2021 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
* Website: https://www.espocrm.com
*
* EspoCRM is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* EspoCRM 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with EspoCRM. If not, see http://www.gnu.org/licenses/.
*
* The interactive user interfaces in modified source and object code versions
* of this program must display Appropriate Legal Notices, as required under
* Section 5 of the GNU General Public License version 3.
*
* In accordance with Section 7(b) of the GNU General Public License version 3,
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
namespace Espo\Core\Sms;
use Espo\Core\Binding\Factory;
use Espo\Core\Utils\Config;
use Espo\Core\Utils\Metadata;
use Espo\Core\InjectableFactory;
use RuntimeException;
class SenderFactory implements Factory
{
private $config;
private $metadata;
private $injectableFactory;
public function __construct(
Config $config,
Metadata $metadata,
InjectableFactory $injectableFactory
) {
$this->config = $config;
$this->metadata = $metadata;
$this->injectableFactory = $injectableFactory;
}
public function create(): Sender
{
$provider = $this->config->get('smsProvider');
if (!$provider) {
throw new RuntimeException("No `smsProvider` in config.");
}
$className = $this->metadata->get(['app', 'smsProviders', $provider, 'senderClassName']);
if (!$className) {
throw new RuntimeException("No `senderClassName` for '{$provider}' provider.");
}
return $this->injectableFactory->create($className);
}
}

View File

@@ -0,0 +1,47 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM - Open Source CRM application.
* Copyright (C) 2014-2021 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
* Website: https://www.espocrm.com
*
* EspoCRM is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* EspoCRM 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with EspoCRM. If not, see http://www.gnu.org/licenses/.
*
* The interactive user interfaces in modified source and object code versions
* of this program must display Appropriate Legal Notices, as required under
* Section 5 of the GNU General Public License version 3.
*
* In accordance with Section 7(b) of the GNU General Public License version 3,
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
namespace Espo\Core\Sms;
/**
* An SMS message.
*/
interface Sms
{
public function getBody(): string;
public function getFromNumber(): ?string;
public function getFromName(): ?string;
/**
* @return string[]
*/
public function getToNumberList(): array;
}

View File

@@ -0,0 +1,54 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM - Open Source CRM application.
* Copyright (C) 2014-2021 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
* Website: https://www.espocrm.com
*
* EspoCRM is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* EspoCRM 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with EspoCRM. If not, see http://www.gnu.org/licenses/.
*
* The interactive user interfaces in modified source and object code versions
* of this program must display Appropriate Legal Notices, as required under
* Section 5 of the GNU General Public License version 3.
*
* In accordance with Section 7(b) of the GNU General Public License version 3,
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
namespace Espo\Core\Sms;
use Espo\ORM\EntityManager;
use Espo\Entities\Sms as SmsEntity;
/**
* Creates SMS instances.
*/
class SmsFactory
{
private $entityManager;
public function __construct(EntityManager $entityManager)
{
$this->entityManager = $entityManager;
}
/**
* Create an SMS instance.
*/
public function create(): SmsEntity
{
return $this->entityManager->getNewEntity(SmsEntity::ENTITY_TYPE);
}
}

View File

@@ -0,0 +1,60 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM - Open Source CRM application.
* Copyright (C) 2014-2021 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
* Website: https://www.espocrm.com
*
* EspoCRM is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* EspoCRM 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with EspoCRM. If not, see http://www.gnu.org/licenses/.
*
* The interactive user interfaces in modified source and object code versions
* of this program must display Appropriate Legal Notices, as required under
* Section 5 of the GNU General Public License version 3.
*
* In accordance with Section 7(b) of the GNU General Public License version 3,
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
namespace Espo\Core\Sms;
use Espo\Entities\Sms as SmsEntity;
use Espo\Core\Utils\Config;
class SmsSender
{
private $sender;
private $config;
public function __construct(Sender $sender, Config $config)
{
$this->sender = $sender;
$this->config = $config;
}
public function send(SmsEntity $sms): void
{
$systemFromNumber = $this->config->get('outboundSmsFromNumber');
if ($sms->getFromNumber() === null && $systemFromNumber) {
$sms->setFromNumber($systemFromNumber);
}
$this->sender->send($sms);
$sms->setAsSent();
}
}

View File

@@ -0,0 +1,57 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM - Open Source CRM application.
* Copyright (C) 2014-2021 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
* Website: https://www.espocrm.com
*
* EspoCRM is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* EspoCRM 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with EspoCRM. If not, see http://www.gnu.org/licenses/.
*
* The interactive user interfaces in modified source and object code versions
* of this program must display Appropriate Legal Notices, as required under
* Section 5 of the GNU General Public License version 3.
*
* In accordance with Section 7(b) of the GNU General Public License version 3,
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
namespace Espo\Core\Utils\Database\Orm\Relations;
use Espo\Core\Utils\Util;
class SmsPhoneNumber extends HasMany
{
protected function load($linkName, $entityName)
{
$parentRelation = parent::load($linkName, $entityName);
$foreignEntityName = $this->getForeignEntityName();
$relation = [
$entityName => [
'relations' => [
$linkName => [
'midKeys' => [
lcfirst($entityName) . 'Id',
lcfirst($foreignEntityName) . 'Id',
],
],
],
],
];
return Util::merge($parentRelation, $relation);
}
}

View File

@@ -0,0 +1,134 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM - Open Source CRM application.
* Copyright (C) 2014-2021 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
* Website: https://www.espocrm.com
*
* EspoCRM is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* EspoCRM 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with EspoCRM. If not, see http://www.gnu.org/licenses/.
*
* The interactive user interfaces in modified source and object code versions
* of this program must display Appropriate Legal Notices, as required under
* Section 5 of the GNU General Public License version 3.
*
* In accordance with Section 7(b) of the GNU General Public License version 3,
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
namespace Espo\Entities;
use Espo\Core\ORM\Entity;
use Espo\Core\Sms\Sms as SmsInterface;
use Espo\Core\Field\DateTime;
use Espo\Repositories\Sms as SmsRepository;
class Sms extends Entity implements SmsInterface
{
public const ENTITY_TYPE = 'Sms';
public const STATUS_ARCHIVED = 'Archived';
public const STATUS_SENT = 'Sent';
public const STATUS_SENDING = 'Sending';
public const STATUS_DRAFT = 'Draft';
public function getDateSent(): ?DateTime
{
return $this->getValueObject('dateTime');
}
public function getCreatedAt(): ?DateTime
{
return $this->getValueObject('createdAt');
}
public function getBody(): string
{
return $this->get('body') ?? '';
}
public function getStatus(): ?string
{
return $this->get('status');
}
public function setBody(?string $body): void
{
$this->set('body', $body);
}
public function setFromNumber(?string $number): void
{
$this->set('from', $number);
}
public function addToNumber(string $number): void
{
$list = $this->getToNumberList();
$list[] = $number;
$this->set('to', implode(';', $list));
}
public function getFromNumber(): ?string
{
if (!$this->hasInContainer('from') && !$this->isNew()) {
$this->getSmsRepository()->loadFromField($this);
}
return $this->get('from');
}
public function getFromName(): ?string
{
return $this->get('fromName');
}
/**
* @return string[]
*/
public function getToNumberList(): array
{
if (!$this->hasInContainer('to') && !$this->isNew()) {
$this->getSmsRepository()->loadToField($this);
}
$value = $this->get('to');
if (!$value) {
return [];
}
return explode(';', $value);
}
public function setAsSent(): void
{
$this->set('status', self::STATUS_SENT);
if (!$this->get('dateSent')) {
$this->set('dateSent', DateTime::createNow()->getString());
}
}
private function getSmsRepository(): SmsRepository
{
return $this->entityManager->getRepository(self::ENTITY_TYPE);
}
}

View File

@@ -0,0 +1,111 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM - Open Source CRM application.
* Copyright (C) 2014-2021 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
* Website: https://www.espocrm.com
*
* EspoCRM is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* EspoCRM 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with EspoCRM. If not, see http://www.gnu.org/licenses/.
*
* The interactive user interfaces in modified source and object code versions
* of this program must display Appropriate Legal Notices, as required under
* Section 5 of the GNU General Public License version 3.
*
* In accordance with Section 7(b) of the GNU General Public License version 3,
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
namespace Espo\Hooks\Sms;
use Espo\ORM\EntityManager;
use Espo\Entities\Sms;
use Espo\Entities\PhoneNumber;
use Espo\Repositories\PhoneNumber as PhoneNumberRepository;
use Espo\ORM\Entity;
class Numbers
{
private $entityManager;
public function __construct(EntityManager $entityManager)
{
$this->entityManager = $entityManager;
}
public function beforeSave(Entity $entity): void
{
$this->processNumbers($entity);
}
private function processNumbers(Sms $entity): void
{
if ($entity->has('from')) {
$this->processFrom($entity);
}
if ($entity->has('to')) {
$this->processTo($entity);
}
}
private function processFrom(Sms $entity): void
{
$from = $entity->get('from');
$entity->set('fromPhoneNumberId', null);
$entity->set('fromEmailAddressName', null);
if (!$from) {
return;
}
$numberIds = $this->getPhoneNumberRepository()->getIds([$from]);
if (!count($numberIds)) {
return;
}
$entity->set('fromEmailAddressId', $numberIds[0]);
$entity->set('fromEmailAddressName', $from);
}
private function processTo(Sms $entity): void
{
$entity->setLinkMultipleIdList('toPhoneNumbers', []);
$to = $entity->get('to');
if ($to === null || !$to) {
return;
}
$numberList = array_map(
function (string $item): string {
return trim($item);
},
explode(';', $to)
);
$numberIds = $this->getPhoneNumberRepository()->getIds($numberList);
$entity->setLinkMultipleIdList('toPhoneNumbers', $numberIds);
}
private function getPhoneNumberRepository(): PhoneNumberRepository
{
return $this->entityManager->getRepository(PhoneNumber::ENTITY_TYPE);
}
}

View File

@@ -0,0 +1,84 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM - Open Source CRM application.
* Copyright (C) 2014-2021 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
* Website: https://www.espocrm.com
*
* EspoCRM is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* EspoCRM 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with EspoCRM. If not, see http://www.gnu.org/licenses/.
*
* The interactive user interfaces in modified source and object code versions
* of this program must display Appropriate Legal Notices, as required under
* Section 5 of the GNU General Public License version 3.
*
* In accordance with Section 7(b) of the GNU General Public License version 3,
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
namespace Espo\Repositories;
use Espo\Entities\Sms as SmsEntity;
use Espo\Entities\PhoneNumber;
use Espo\Core\Repositories\Database;
class Sms extends Database
{
public function loadFromField(SmsEntity $entity): void
{
if ($entity->get('fromPhoneNumberName')) {
$entity->set('from', $entity->get('fromPhoneNumberName'));
return;
}
$numberId = $entity->get('fromPhoneNumberId');
if ($numberId) {
$phoneNumber = $this->entityManager
->getRepository(PhoneNumber::ENTITY_TYPE)
->getById($numberId);
if ($phoneNumber) {
$entity->set('from', $phoneNumber->get('name'));
return;
}
}
$entity->set('from', null);
}
public function loadToField(SmsEntity $entity): void
{
$entity->loadLinkMultipleField('toPhoneNumbers');
$names = $entity->get('toPhoneNumbersNames');
if (empty($names)) {
$entity->set('to', null);
return;
}
$list = [];
foreach ($names as $address) {
$list[] = $address;
}
$entity->set('to', implode(';', $list));
}
}

View File

@@ -205,6 +205,7 @@ return [
'passwordRecoveryRequestDelay',
'thumbImageCacheDisabled',
'emailReminderPortionSize',
'outboundSmsFromNumber',
'latestVersion',
],
'superAdminItems' => [

View File

@@ -5,6 +5,7 @@
"System": "System",
"Users": "Users",
"Email": "Email",
"Messaging": "Messaging",
"Data": "Data",
"Customization": "Customization",
"Available Fields": "Available Fields",
@@ -278,6 +279,7 @@
"dashboardTemplates": "Deploy dashboards to users.",
"layoutSets": "Collections of layouts that can be assigned to teams & portals.",
"jobsSettings": "Job processing settings. Jobs execute tasks in the background.",
"sms": "SMS settings.",
"pdfTemplates": "Templates for printing to PDF."
},
"keywords": {

View File

@@ -13,6 +13,8 @@
"currencyList": "Currency List",
"language": "Language",
"companyLogo": "Company Logo",
"smsProvider": "SMS Provider",
"outboundSmsFromNumber": "SMS From Number",
"smtpServer": "Server",
"smtpPort": "Port",
"smtpAuth": "Auth",
@@ -155,7 +157,8 @@
},
"auth2FAMethodList": {
"Totp": "TOTP",
"Email": "Email"
"Email": "Email",
"Sms": "SMS"
}
},
"tooltips": {

View File

@@ -109,8 +109,11 @@
"userNameExists": "User Name already exists",
"wrongCode": "Wrong code",
"codeIsRequired": "Code is required",
"yourAuthenticationCode": "Your authentication code: {code}.",
"choose2FaSmsPhoneNumber": "Select a phone number that will be used for 2FA.",
"choose2FaEmailAddress": "Select an email address that will be used for 2FA. It's highly recommended to use a non-primary email address.",
"enterCodeSentInEmail": "Enter the code sent to your email address.",
"enterCodeSentBySms": "Enter the code sent by SMS to your phone number.",
"enterTotpCode": "Enter a code from your authenticator app.",
"verifyTotpCode": "Scan the QR-code with your mobile authenticator app. If you have a trouble with scanning, you can enter the secret manually. After that you will see a 6-digit code in your application. Enter this code in the field below.",
"generateAndSendNewPassword": "A new password will be generated and sent to the user's email address.",

View File

@@ -0,0 +1,8 @@
[
{
"rows": [
[{"name": "smsProvider"}, false],
[{"name": "outboundSmsFromNumber"}, false]
]
}
]

View File

@@ -169,7 +169,7 @@
"order": 10
},
"email":{
"label": "Email",
"label": "Messaging",
"itemList": [
{
"url": "#Admin/outboundEmails",
@@ -208,6 +208,13 @@
"label": "Email Templates",
"iconClass": "fas fa-envelope-square",
"description": "emailTemplates"
},
{
"url": "#Admin/sms",
"label": "SMS",
"iconClass": "fas fa-paper-plane",
"description": "sms",
"recordView": "views/admin/sms"
}
],
"order": 15

View File

@@ -14,5 +14,13 @@
"userApplyView": "views/user-security/modals/two-factor-email",
"loginClassName": "Espo\\Core\\Authentication\\TwoFactor\\Email\\EmailLogin",
"userSetupClassName": "Espo\\Core\\Authentication\\TwoFactor\\Email\\EmailUserSetup"
},
"Sms": {
"settings": {
"isAvailable": true
},
"userApplyView": "views/user-security/modals/two-factor-sms",
"loginClassName": "Espo\\Core\\Authentication\\TwoFactor\\Sms\\SmsLogin",
"userSetupClassName": "Espo\\Core\\Authentication\\TwoFactor\\Sms\\SmsUserSetup"
}
}

View File

@@ -15,6 +15,9 @@
"params": {
"awsS3Storage": {
"level": "system"
},
"smsProvider": {
"level": "admin"
}
}
}

View File

@@ -378,6 +378,11 @@
"insertText": "ext\\email\\send(EMAIL_ID)",
"returnType": "bool"
},
{
"name": "ext\\sms\\send",
"insertText": "ext\\sms\\send(SMS_ID)",
"returnType": "bool"
},
{
"name": "ext\\email\\applyTemplate",
"insertText": "ext\\email\\applyTemplate(EMAIL_ID, EMAIL_TEMPLATE_ID)"

View File

@@ -25,6 +25,7 @@
["app", "webSocket", "messagers"],
["app", "config"],
["app", "rebuild"],
["app", "smsProviders", "__ANY__", "senderClassName"],
["selectDefs"],
["recordDefs"],
["pdfDefs"],

View File

@@ -0,0 +1,3 @@
{
}

View File

@@ -635,6 +635,14 @@
},
"awsS3Storage": {
"type": "jsonObject"
},
"outboundSmsFromNumber": {
"type": "varchar",
"trim": true
},
"smsProvider": {
"type": "enum",
"view": "views/settings/fields/sms-provider"
}
}
}

View File

@@ -0,0 +1,147 @@
{
"fields": {
"from": {
"type": "varchar",
"notStorable": true,
"required": true,
"textFilterDisabled": true
},
"fromName": {
"type": "varchar"
},
"to": {
"type": "varchar",
"notStorable": true,
"required": true,
"textFilterDisabled": true
},
"fromPhoneNumber": {
"type": "link",
"textFilterDisabled": true
},
"toPhoneNumbers": {
"type": "linkMultiple"
},
"body": {
"type": "text"
},
"status": {
"type": "enum",
"options": ["Draft", "Sending", "Sent", "Archived", "Failed"],
"default": "Archived",
"clientReadOnly": true,
"style": {
"Draft": "warning",
"Failed": "danger",
"Sending": "warning"
}
},
"parent": {
"type": "linkParent"
},
"dateSent": {
"type": "datetime"
},
"createdAt": {
"type": "datetime",
"readOnly": true
},
"modifiedAt": {
"type": "datetime",
"readOnly": true
},
"createdBy": {
"type": "link",
"readOnly": true,
"view": "views/fields/user"
},
"modifiedBy": {
"type": "link",
"readOnly": true,
"view": "views/fields/user"
},
"replied": {
"type": "link",
"noJoin": true,
"readOnly": true,
"view": "views/email/fields/replied"
},
"replies": {
"type": "linkMultiple",
"readOnly": true,
"orderBy": "dateSent",
"view": "views/email/fields/replies"
},
"teams": {
"type": "linkMultiple",
"view": "views/fields/teams"
}
},
"links": {
"createdBy": {
"type": "belongsTo",
"entity": "User"
},
"modifiedBy": {
"type": "belongsTo",
"entity": "User"
},
"teams": {
"type": "hasMany",
"entity": "Team",
"relationName": "entityTeam"
},
"parent": {
"type": "belongsToParent",
"entityList": [
"Account",
"Contact",
"Lead",
"Opportunity"
],
"foreign": "emails"
},
"replied": {
"type": "belongsTo",
"entity": "Sms",
"foreign": "replies",
"foreignName": "id"
},
"replies": {
"type": "hasMany",
"entity": "Sms",
"foreign": "replied"
},
"fromPhoneNumber": {
"type": "belongsTo",
"entity": "PhoneNumber"
},
"toPhoneNumbers": {
"type": "hasMany",
"entity": "PhoneNumber",
"relationName": "smsPhoneNumber",
"conditions": {
"addressType": "to"
},
"additionalColumns": {
"addressType": {
"type": "varchar",
"len": "4"
}
}
}
},
"collection": {
"orderBy": "dateSent",
"order": "desc",
"textFilterFields": ["name", "body"]
},
"indexes": {
"dateSent": {
"columns": ["dateSent", "deleted"]
},
"dateSentStatus": {
"columns": ["dateSent", "status", "deleted"]
}
}
}

View File

@@ -0,0 +1,90 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM - Open Source CRM application.
* Copyright (C) 2014-2021 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
* Website: https://www.espocrm.com
*
* EspoCRM is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* EspoCRM 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with EspoCRM. If not, see http://www.gnu.org/licenses/.
*
* The interactive user interfaces in modified source and object code versions
* of this program must display Appropriate Legal Notices, as required under
* Section 5 of the GNU General Public License version 3.
*
* In accordance with Section 7(b) of the GNU General Public License version 3,
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
namespace Espo\Services;
use Espo\Core\Exceptions\Forbidden;
use Espo\Core\Exceptions\NotFound;
use Espo\Core\Utils\Config;
use Espo\Core\Authentication\TwoFactor\Sms\Util;
use Espo\ORM\EntityManager;
use Espo\Entities\User;
class TwoFactorSms
{
private $util;
private $user;
private $entityManager;
private $config;
public function __construct(Util $util, User $user, EntityManager $entityManager, Config $config)
{
$this->util = $util;
$this->user = $user;
$this->entityManager = $entityManager;
$this->config = $config;
}
public function sendCode(string $userId, string $phoneNumber): void
{
if (!$this->user->isAdmin() && $userId !== $this->user->getId()) {
throw new Forbidden();
}
$this->checkAllowed();
$user = $this->entityManager->getEntity(User::ENTITY_TYPE, $userId);
if (!$user) {
throw new NotFound();
}
$this->util->sendCode($user, $phoneNumber);
$this->util->storePhoneNumber($user, $phoneNumber);
}
private function checkAllowed(): void
{
if (!$this->config->get('auth2FA')) {
throw new Forbidden("2FA is not enabled.");
}
$methodList = $this->config->get('auth2FAMethodList') ?? [];
if (!in_array('Sms', $methodList)) {
throw new Forbidden("Sms 2FA is not allowed.");
}
}
}

View File

@@ -0,0 +1,15 @@
<div class="panel no-side-margin">
<div class="panel-body">
<p class="p-info">
{{translate 'choose2FaSmsPhoneNumber' category='messages' scope='User'}}
</p>
<p class="p-button">
<button class="btn btn-default" data-action="sendCode">{{translate 'Send Code' scope='User'}}</button>
</p>
<p class="p-info-after hidden">
{{translate 'enterCodeSentBySms' category='messages' scope='User'}}
</p>
</div>
</div>
<div class="record">{{{record}}}</div>

View File

@@ -0,0 +1,36 @@
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM - Open Source CRM application.
* Copyright (C) 2014-2021 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
* Website: https://www.espocrm.com
*
* EspoCRM is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* EspoCRM 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with EspoCRM. If not, see http://www.gnu.org/licenses/.
*
* The interactive user interfaces in modified source and object code versions
* of this program must display Appropriate Legal Notices, as required under
* Section 5 of the GNU General Public License version 3.
*
* In accordance with Section 7(b) of the GNU General Public License version 3,
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
define('views/admin/sms', 'views/settings/record/edit', function (Dep) {
return Dep.extend({
layoutName: 'sms',
});
});

View File

@@ -0,0 +1,39 @@
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM - Open Source CRM application.
* Copyright (C) 2014-2021 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
* Website: https://www.espocrm.com
*
* EspoCRM is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* EspoCRM 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with EspoCRM. If not, see http://www.gnu.org/licenses/.
*
* The interactive user interfaces in modified source and object code versions
* of this program must display Appropriate Legal Notices, as required under
* Section 5 of the GNU General Public License version 3.
*
* In accordance with Section 7(b) of the GNU General Public License version 3,
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
define('views/settings/fields/sms-provider', 'views/fields/enum', function (Dep) {
return Dep.extend({
setupOptions: function () {
this.params.options = Object.keys(
this.getMetadata().get(['app', 'smsProviders']) || {}
);
},
});
});

View File

@@ -0,0 +1,192 @@
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM - Open Source CRM application.
* Copyright (C) 2014-2021 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
* Website: https://www.espocrm.com
*
* EspoCRM is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* EspoCRM 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with EspoCRM. If not, see http://www.gnu.org/licenses/.
*
* The interactive user interfaces in modified source and object code versions
* of this program must display Appropriate Legal Notices, as required under
* Section 5 of the GNU General Public License version 3.
*
* In accordance with Section 7(b) of the GNU General Public License version 3,
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
define('views/user-security/modals/two-factor-sms',
['views/modal', 'model'],
function (Dep, Model) {
return Dep.extend({
template: 'user-security/modals/two-factor-sms',
className: 'dialog dialog-record',
events: {
'click [data-action="sendCode"]': function () {
this.actionSendCode();
},
},
setup: function () {
this.buttonList = [
{
name: 'apply',
label: 'Apply',
style: 'danger',
hidden: true,
},
{
name: 'cancel',
label: 'Cancel',
}
];
this.headerHtml = false;
let codeLength = this.getConfig().get('auth2FASmsCodeLength') || 7;
let model = new Model();
model.name = 'UserSecurity';
model.set('phoneNumber', null);
model.setDefs({
fields: {
'code': {
type: 'varchar',
required: true,
maxLength: codeLength,
},
'phoneNumber': {
type: 'enum',
required: true,
},
}
});
this.internalModel = model;
this.wait(
Espo.Ajax
.postRequest('UserSecurity/action/getTwoFactorUserSetupData', {
id: this.model.id,
password: this.model.get('password'),
auth2FAMethod: this.model.get('auth2FAMethod'),
reset: this.options.reset,
})
.then(data => {
this.phoneNumberList = data.phoneNumberList;
this.createView('record', 'views/record/edit-for-modal', {
scope: 'None',
el: this.getSelector() + ' .record',
model: model,
detailLayout: [
{
rows: [
[
{
name: 'phoneNumber',
labelText: this.translate('phoneNumber', 'fields', 'User'),
},
false
],
[
{
name: 'code',
labelText: this.translate('Code', 'labels', 'User'),
},
false
],
]
}
],
}, view => {
view.setFieldOptionList('phoneNumber', this.phoneNumberList);
if (this.phoneNumberList.length) {
model.set('phoneNumber', this.phoneNumberList[0]);
}
view.hideField('code');
});
})
);
},
afterRender: function () {
this.$sendCode = this.$el.find('[data-action="sendCode"]');
this.$pInfo = this.$el.find('p.p-info');
this.$pButton = this.$el.find('p.p-button');
this.$pInfoAfter = this.$el.find('p.p-info-after');
},
actionSendCode: function () {
this.$sendCode.attr('disabled', 'disabled').addClass('disabled');
Espo.Ajax
.postRequest('TwoFactorSms/action/sendCode', {
id: this.model.id,
phoneNumber: this.internalModel.get('phoneNumber'),
})
.then(() => {
this.showButton('apply');
this.$pInfo.addClass('hidden');
this.$pButton.addClass('hidden');
this.$pInfoAfter.removeClass('hidden');
this.getView('record').setFieldReadOnly('phoneNumber');
this.getView('record').showField('code');
})
.catch(() => {
this.$sendCode.removeAttr('disabled').removeClass('disabled');
});
},
actionApply: function () {
let data = this.getView('record').processFetch();
if (!data) {
return;
}
this.model.set('code', data.code);
this.hideButton('apply');
this.hideButton('cancel');
Espo.Ui.notify(this.translate('pleaseWait', 'messages'));
this.model
.save()
.then(() => {
Espo.Ui.notify(false);
this.trigger('done');
})
.catch(() => {
this.showButton('apply');
this.showButton('cancel');
});
},
});
});