From e2942b1549d5129574f1633642aba85e0bc9dcb3 Mon Sep 17 00:00:00 2001 From: Jordan Bieder Date: Thu, 16 Jan 2020 16:37:07 +0100 Subject: Ovh SMS gateway integration - Add configuration class - Add Provider class Signed-off-by: Jordan Bieder Integrate Ovh gateway - add php API with composer - install the new dependencies - Add configuration for ovh - Update ProviderFactory for ovh Signed-off-by: Jordan Bieder Debug Ovh sms gateway. Has been tested with the test function Signed-off-by: Jordan Bieder Improve documentation since it seems not that easy Signed-off-by: Jordan Bieder Update Admin Documentation.md Signed-off-by: Jordan Bieder Update Admin Documentation.md Signed-off-by: Jordan Bieder Update Admin Documentation.md Signed-off-by: Jordan Bieder Fix php syntax Signed-off-by: Jordan Bieder Bump css-loader from 3.4.1 to 3.4.2 Bumps [css-loader](https://github.com/webpack-contrib/css-loader) from 3.4.1 to 3.4.2. - [Release notes](https://github.com/webpack-contrib/css-loader/releases) - [Changelog](https://github.com/webpack-contrib/css-loader/blob/master/CHANGELOG.md) - [Commits](https://github.com/webpack-contrib/css-loader/compare/v3.4.1...v3.4.2) Signed-off-by: dependabot-preview[bot] [tx-robot] updated from transifex [tx-robot] updated from transifex Add sms77 to help and fix case selection string Signed-off-by: Olav Seyfarth Update OVH sms provider to remove ovh/ovh dependency Signed-off-by: Jordan Bieder Update OVH sms provider to remove ovh/ovh dependency Update ovh endpoints Signed-off-by: Jordan Bieder Fix typos Signed-off-by: Jordan Bieder Fix typos bis Signed-off-by: Jordan Bieder Fix POST request Signed-off-by: Jordan Bieder Remove ovh/ovh with composer Signed-off-by: Jordan Bieder Stupid commit to get the DCO ?! Signed-off-by: Jordan Bieder Add requested corrections Signed-off-by: Jordan Bieder Fix indentation Signed-off-by: Jordan Bieder --- lib/Service/Gateway/SMS/Provider/Ovh.php | 185 +++++++++++++++++++++ lib/Service/Gateway/SMS/Provider/OvhConfig.php | 109 ++++++++++++ .../Gateway/SMS/Provider/ProviderFactory.php | 2 + 3 files changed, 296 insertions(+) create mode 100644 lib/Service/Gateway/SMS/Provider/Ovh.php create mode 100644 lib/Service/Gateway/SMS/Provider/OvhConfig.php (limited to 'lib/Service') diff --git a/lib/Service/Gateway/SMS/Provider/Ovh.php b/lib/Service/Gateway/SMS/Provider/Ovh.php new file mode 100644 index 0000000..6aec22a --- /dev/null +++ b/lib/Service/Gateway/SMS/Provider/Ovh.php @@ -0,0 +1,185 @@ + + * + * Nextcloud - Two-factor Gateway for Ovh + * + * 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 OCA\TwoFactorGateway\Exception\InvalidSmsProviderException; +use OCP\Http\Client\IClient; +use OCP\Http\Client\IClientService; + +class Ovh implements IProvider { + + const PROVIDER_ID = 'ovh'; + + /** @var IClient */ + private $client; + + /** @var OvhConfig */ + private $config; + + /** + * Url to communicate with Ovh API + * + * @var array + */ + private $endpoints = [ + 'ovh-eu' => 'https://api.ovh.com/1.0', + 'ovh-us' => 'https://api.us.ovhcloud.com/1.0', + 'ovh-ca' => 'https://ca.api.ovh.com/1.0', + 'kimsufi-eu' => 'https://eu.api.kimsufi.com/1.0', + 'kimsufi-ca' => 'https://ca.api.kimsufi.com/1.0', + 'soyoustart-eu' => 'https://eu.api.soyoustart.com/1.0', + 'soyoustart-ca' => 'https://ca.api.soyoustart.com/1.0', + 'runabove-ca' => 'https://api.runabove.com/1.0', + ]; + + /** + * Array of the 4 needed parameters to connect to the API + * @var array + */ + private $attrs = [ + 'AK' => null, + 'AS' => null, + 'CK' => null, + 'endpoint' => null, + 'timedelta' => null + ]; + + + public function __construct(IClientService $clientService, + OvhConfig $config) { + $this->client = $clientService->newClient(); + $this->config = $config; + } + + /** + * @param string $identifier + * @param string $message + * + * @throws SmsTransmissionException + */ + public function send(string $identifier, string $message) { + $config = $this->getConfig(); + $endpoint = $config->getEndpoint(); + $sender = $config->getSender(); + $smsAccount = $config->getAccount(); + + $this->attrs['AK'] = $config->getApplicationKey(); + $this->attrs['AS'] = $config->getApplicationSecret(); + $this->attrs['CK'] = $config->getConsumerKey(); + if (!isset($this->endpoints[$endpoint])) + throw new InvalidSmsProviderException("Endpoint $endpoint not found"); + $this->attrs['endpoint'] = $this->endpoints[$endpoint]; + + $this->getTimeDelta(); + + $header = $this->getHeader('GET',$this->attrs['endpoint'].'/sms'); + $response = $this->client->get($this->attrs['endpoint'].'/sms',[ + 'headers' => $header, + ]); + $smsServices = json_decode($response->getBody(),true); + + $smsAccountFound = false; + foreach ($smsServices as $smsService) { + if ($smsService === $smsAccount) { + $smsAccountFound = true; + break; + } + } + if ($smsAccountFound === false) { + throw new InvalidSmsProviderException("SMS account $smsAccount not found"); + } + $content = [ + "charset"=> "UTF-8", + "message"=> $message, + "noStopClause"=> true, + "priority"=> "high", + "receivers"=> [ $identifier ], + "senderForResponse"=> false, + "sender"=> $sender, + "validityPeriod"=> 3600 + ]; + $body = json_encode($content); + + $header = $this->getHeader('POST',$this->attrs['endpoint']."/sms/$smsAccount/jobs",$body); + $response = $this->client->post($this->attrs['endpoint']."/sms/$smsAccount/jobs",[ + 'headers' => $header, + 'json' => $content, + ]); + $resultPostJob = json_decode($response->getBody(),true); + + if (count($resultPostJob["validReceivers"]) === 0) { + throw new SmsTransmissionException("Bad receiver $identifier"); + } + } + + /** + * @return OvhConfig + */ + public function getConfig(): IProviderConfig { + return $this->config; + } + + /** + * Compute time delta between this server and OVH endpoint + * @throws InvalidSmsProviderException + */ + private function getTimeDelta() { + if (!isset($this->attrs['timedelta'])) { + if (!isset($this->attrs['endpoint'])) + throw new InvalidSmsProviderException('Need to set the endpoint'); + try { + $response = $this->client->get($this->attrs['endpoint'].'/auth/time'); + $serverTimestamp = (int)$response->getBody(); + $this->attrs['timedelta'] = $serverTimestamp - time(); + } + catch (Exception $ex) { + throw new InvalidSmsProviderException('Unable to calculate time delta:'.$ex->getMessage()); + } + } + } + + /** + * Make header for Ovh + * @param string $method The methode use for the query : GET, POST, PUT, DELETE + * @param string $query The fulle URI for the query: https://eu.api.ovh.com/1.0/...... + * @param string $body JSON encoded body content for the POST request + * @return array $header Contains the data for the request need by OVH + */ + private function getHeader($method,$query,$body='') { + $timestamp = time() + $this->attrs['timedelta']; + $prehash = $this->attrs['AS'].'+'.$this->attrs['CK'].'+'.$method.'+'.$query.'+'.$body.'+'.$timestamp; + $header = [ + 'Content-Type' => 'application/json; charset=utf-8', + 'X-Ovh-Application' => $this->attrs['AK'], + 'X-Ovh-Timestamp' => $timestamp, + 'X-Ovh-Signature' => '$1$'.sha1($prehash), + 'X-Ovh-Consumer' => $this->attrs['CK'], + ]; + return $header; + } + + +} diff --git a/lib/Service/Gateway/SMS/Provider/OvhConfig.php b/lib/Service/Gateway/SMS/Provider/OvhConfig.php new file mode 100644 index 0000000..4703bbd --- /dev/null +++ b/lib/Service/Gateway/SMS/Provider/OvhConfig.php @@ -0,0 +1,109 @@ + + * + * Nextcloud - Two-factor Gateway for Ovh + * + * 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 OvhConfig implements IProviderConfig { + + /** @var IConfig */ + private $config; + + public function __construct(IConfig $config) { + $this->config = $config; + } + + private function getOrFail(string $key): string { + $val = $this->config->getAppValue(Application::APP_NAME, $key, null); + if (is_null($val)) { + throw new ConfigurationException(); + } + return $val; + } + + public function getApplicationKey(): string { + return $this->getOrFail('ovh_application_key'); + } + + public function getApplicationSecret(): string { + return $this->getOrFail('ovh_application_secret'); + } + + public function getConsumerKey(): string { + return $this->getOrFail('ovh_consumer_key'); + } + + public function getEndpoint(): string { + return $this->getOrFail('ovh_endpoint'); + } + + public function getAccount(): string { + return $this->getOrFail('ovh_account'); + } + + public function getSender(): string { + return $this->getOrFail('ovh_sender'); + } + + public function setApplicationKey(string $appKey){ + $this->config->setAppValue(Application::APP_NAME, 'ovh_application_key', $appKey); + } + + public function setApplicationSecret(string $appSecret){ + $this->config->setAppValue(Application::APP_NAME, 'ovh_application_secret', $appSecret); + } + + public function setConsumerKey(string $consumerKey){ + $this->config->setAppValue(Application::APP_NAME, 'ovh_consumer_key', $consumerKey); + } + + public function setEndpoint(string $endpoint){ + $this->config->setAppValue(Application::APP_NAME, 'ovh_endpoint', $endpoint); + } + + public function setAccount($account){ + $this->config->setAppValue(Application::APP_NAME, 'ovh_account', $account); + } + + public function setSender($sender){ + $this->config->setAppValue(Application::APP_NAME, 'ovh_sender', $sender); + } + + public function isComplete(): bool { + $set = $this->config->getAppKeys(Application::APP_NAME); + $expected = [ + 'ovh_application_key', + 'ovh_application_secret', + 'ovh_consumer_key', + 'ovh_endpoint', + 'ovh_account', + 'ovh_sender' + ]; + return count(array_intersect($set, $expected)) === count($expected); + } + +} diff --git a/lib/Service/Gateway/SMS/Provider/ProviderFactory.php b/lib/Service/Gateway/SMS/Provider/ProviderFactory.php index b27c341..d3f3614 100644 --- a/lib/Service/Gateway/SMS/Provider/ProviderFactory.php +++ b/lib/Service/Gateway/SMS/Provider/ProviderFactory.php @@ -53,6 +53,8 @@ class ProviderFactory { return $this->container->query(HuaweiE3531::class); case Sms77Io::PROVIDER_ID: return $this->container->query(Sms77Io::class); + case Ovh::PROVIDER_ID: + return $this->container->query(Ovh::class); case SpryngSMS::PROVIDER_ID: return $this->container->query(SpryngSMS::class); default: -- cgit v1.2.3