diff --git a/doc/Admin Documentation.md b/doc/Admin Documentation.md index 7be6b955..a5c567c0 100644 --- a/doc/Admin Documentation.md +++ b/doc/Admin Documentation.md @@ -191,4 +191,15 @@ Interactive admin configuration: occ twofactorauth:gateway:configure sms ``` +### Plivo +URL: https://www.plivo.com +Stability: Experimental + +Use the HTTPS service provided by plivo.com for sending SMS. + +Interactive admin configuration: +```bash +occ twofactorauth:gateway:configure sms +``` + [User Documentation]: https://nextcloud-twofactor-gateway.readthedocs.io/en/latest/User%20Documentation/ diff --git a/lib/Command/Configure.php b/lib/Command/Configure.php index dc7b95f4..b72f63a4 100644 --- a/lib/Command/Configure.php +++ b/lib/Command/Configure.php @@ -35,6 +35,7 @@ use OCA\TwoFactorGateway\Service\Gateway\SMS\Provider\Sms77IoConfig; use OCA\TwoFactorGateway\Service\Gateway\SMS\Provider\OvhConfig; use OCA\TwoFactorGateway\Service\Gateway\SMS\Provider\WebSmsConfig; +use OCA\TwoFactorGateway\Service\Gateway\SMS\Provider\PlivoConfig; use OCA\TwoFactorGateway\Service\Gateway\SMS\Provider\PuzzelSMSConfig; use OCA\TwoFactorGateway\Service\Gateway\SMS\Provider\HuaweiE3531Config; use OCA\TwoFactorGateway\Service\Gateway\SMS\Provider\SpryngSMSConfig; @@ -108,8 +109,23 @@ private function configureSignal(InputInterface $input, OutputInterface $output) private function configureSms(InputInterface $input, OutputInterface $output) { $helper = $this->getHelper('question'); - - $providerQuestion = new Question('Please choose a SMS provider (websms, playsms, clockworksms, puzzelsms, ecallsms, voipms, huawei_e3531, spryng, sms77io, ovh, clickatellcentral, clicksend): ', 'websms'); + $providerArray = ['websms', + 'playsms', + 'clockworksms', + 'puzzelsms', + 'ecallsms', + 'voipms', + 'huawei_e3531', + 'spryng', + 'sms77io', + 'ovh', + 'clickatellcentral', + 'clicksend', + 'plivo' + ]; + sort($providerArray,SORT_STRING); + $strOfProviders = implode(',', $providerArray); + $providerQuestion = new Question("Please choose a SMS provider ($strOfProviders): ", 'websms'); $provider = $helper->ask($input, $output, $providerQuestion); /** @var SMSConfig $config */ @@ -313,6 +329,29 @@ private function configureSms(InputInterface $input, OutputInterface $output) { $providerConfig->setUser($username); $providerConfig->setApiKey($apiKey); + break; + case 'plivo': + $config->setProvider($provider); + /** @var PlivoConfig $providerConfig */ + $providerConfig = $config->getProvider()->getConfig(); + + $authIdQuestion = new Question('Please enter your plivo authentication id (Auth ID): '); + $authId = $helper->ask($input, $output, $authIdQuestion); + + $authTokenQuestion = new Question('Please enter your plivo authentication token (Auth Token): '); + $authToken = $helper->ask($input, $output, $authTokenQuestion); + + $srcNumberQuestion = new Question("Please enter your plivo phone number (in E.164 format '+12345678901'): "); + $srcNumber = $helper->ask($input, $output, $srcNumberQuestion); + + $callbackUrlQuestion = new Question('Please enter your plivo callback url: '); + $callbackUrl = $helper->ask($input, $output, $callbackUrlQuestion); + + $providerConfig->setValue(PlivoConfig::AUTH_ID_KEY,$authId); + $providerConfig->setValue(PlivoConfig::AUTH_TOKEN_KEY, $authToken); + $providerConfig->setValue(PlivoConfig::CALLBACK_URL, $callbackUrl); + $providerConfig->setValue(PlivoConfig::SRC_NUMBER_KEY, $srcNumber); + break; default: diff --git a/lib/Service/Gateway/SMS/Provider/Plivo.php b/lib/Service/Gateway/SMS/Provider/Plivo.php new file mode 100644 index 00000000..71aa3517 --- /dev/null +++ b/lib/Service/Gateway/SMS/Provider/Plivo.php @@ -0,0 +1,91 @@ + + * + * Plivo - Config for Two-factor Gateway for Plivo + * + * This code 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. + * + * 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, version 3, + * along with this program. If not, see + * + */ + +namespace OCA\TwoFactorGateway\Service\Gateway\SMS\Provider; + +use Exception; +use OCA\TwoFactorGateway\Exception\SmsTransmissionException; +use OCP\Http\Client\IClient; +use OCP\Http\Client\IClientService; +use \OCP\ILogger; + +class Plivo implements IProvider { + public const PROVIDER_ID = 'plivo'; + + /** @var IClient */ + private $client; + + /** @var PlivoConfig */ + private $config; + + private $logger; + + public function __construct(IClientService $clientService, + PlivoConfig $config, ILogger $logger) { + $this->client = $clientService->newClient(); + $this->config = $config; + $this->logger = $logger; + } + + /** + * @param string $identifier + * @param string $message + * + * @throws SmsTransmissionException + */ + public function send(string $identifier, string $message) { + $config = $this->getConfig(); + $authToken = $config->getValue($config::AUTH_TOKEN_KEY); + $authID = $config->getValue($config::AUTH_ID_KEY); + $srcNumber = $config->getValue($config::SRC_NUMBER_KEY); + + $apiParams = [ + 'body' => json_encode([ + 'dst' => $identifier, + 'src' => $srcNumber, + 'text' => $message + ],JSON_FORCE_OBJECT), + 'headers' => [ + 'Content-Type' => "application/json", + 'Authorization' => "Basic " . base64_encode($authID.':'.$authToken) + ] + ]; + + try { + $this->logger->debug("api call: https://api.plivo.com/v1/Account/$authID/Message/" .print_r($apiParams,true)); + $this->client->post("https://api.plivo.com/v1/Account/$authID/Message/", $apiParams); + } catch (Exception $ex) { + $this->logger->logException($ex, [ + 'message' => 'Could not send Plivo message: ' . $ex->getMessage(), + ]); + throw new SmsTransmissionException(); + } + } + + /** + * @return PlivoConfig + */ + public function getConfig(): IProviderConfig { + return $this->config; + } +} diff --git a/lib/Service/Gateway/SMS/Provider/PlivoConfig.php b/lib/Service/Gateway/SMS/Provider/PlivoConfig.php new file mode 100644 index 00000000..83f2af3f --- /dev/null +++ b/lib/Service/Gateway/SMS/Provider/PlivoConfig.php @@ -0,0 +1,76 @@ + + * + * Plivo - Config for Two-factor Gateway for Plivo + * + * This code 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. + * + * 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, version 3, + * along with this program. If not, see + * + */ + +namespace OCA\TwoFactorGateway\Service\Gateway\SMS\Provider; + +use function array_intersect; +use OCA\TwoFactorGateway\AppInfo\Application; +use OCA\TwoFactorGateway\Exception\ConfigurationException; +use OCP\IConfig; + +class PlivoConfig implements IProviderConfig { + + /** @var IConfig */ + private $config; + + public const AUTH_ID_KEY = 'plivo_auth_id'; + public const AUTH_TOKEN_KEY = 'plivo_auth_token'; + public const SRC_NUMBER_KEY = 'plivo_src_number'; + + private const EXPECTED_KEYS = [ + self::AUTH_ID_KEY, + self::AUTH_TOKEN_KEY, + self::SRC_NUMBER_KEY + ]; + + public function __construct(IConfig $config) { + $this->config = $config; + } + + private function getInternalValue(string $key): string { + $val = $this->config->getAppValue(Application::APP_NAME, $key, null); + if (is_null($val)) { + throw new ConfigurationException(); + } + return $val; + } + + public function getValue(string $key): string { + return $this->getInternalValue($key); + } + + public function setValue(string $key, string $value) { + $this->config->setAppValue(Application::APP_NAME, $key, $value); + } + + public function isComplete(): bool { + $set = $this->config->getAppKeys(Application::APP_NAME); + return count(array_intersect($set,self::EXPECTED_KEYS)) === count(self::EXPECTED_KEYS); + } + + public function remove() { + foreach (self::EXPECTED_KEYS as $key) { + $this->config->deleteAppValue(Application::APP_NAME, $key); + } + } +} diff --git a/lib/Service/Gateway/SMS/Provider/ProviderFactory.php b/lib/Service/Gateway/SMS/Provider/ProviderFactory.php index ec499640..6fb5db00 100644 --- a/lib/Service/Gateway/SMS/Provider/ProviderFactory.php +++ b/lib/Service/Gateway/SMS/Provider/ProviderFactory.php @@ -61,6 +61,8 @@ public function getProvider(string $id): IProvider { return $this->container->query(ClickatellCentral::class); case ClickSend::PROVIDER_ID: return $this->container->query(ClickSend::class); + case Plivo::PROVIDER_ID: + return $this->container->query(Plivo::class); default: throw new InvalidSmsProviderException("Provider <$id> does not exist"); }