From df467401ba2273bfbd1d91f7126c88a76ccc1aca Mon Sep 17 00:00:00 2001 From: Robin Appelman Date: Tue, 26 Sep 2017 17:11:58 +0200 Subject: [PATCH] Add api clients for talking to remote clouds Signed-off-by: Robin Appelman --- lib/composer/composer/autoload_classmap.php | 6 + lib/composer/composer/autoload_static.php | 6 + lib/private/Remote/Api/ApiBase.php | 96 ++++++++++++++ lib/private/Remote/Api/NotFoundException.php | 27 ++++ lib/private/Remote/Api/OCS.php | 66 ++++++++++ lib/private/Remote/Credentials.php | 53 ++++++++ lib/private/Remote/Instance.php | 127 +++++++++++++++++++ lib/private/Remote/User.php | 124 ++++++++++++++++++ 8 files changed, 505 insertions(+) create mode 100644 lib/private/Remote/Api/ApiBase.php create mode 100644 lib/private/Remote/Api/NotFoundException.php create mode 100644 lib/private/Remote/Api/OCS.php create mode 100644 lib/private/Remote/Credentials.php create mode 100644 lib/private/Remote/Instance.php create mode 100644 lib/private/Remote/User.php diff --git a/lib/composer/composer/autoload_classmap.php b/lib/composer/composer/autoload_classmap.php index 50bd6d33274f0..02e7bef9a9780 100644 --- a/lib/composer/composer/autoload_classmap.php +++ b/lib/composer/composer/autoload_classmap.php @@ -754,6 +754,12 @@ 'OC\\Preview\\WatcherConnector' => $baseDir . '/lib/private/Preview/WatcherConnector.php', 'OC\\Preview\\XBitmap' => $baseDir . '/lib/private/Preview/XBitmap.php', 'OC\\RedisFactory' => $baseDir . '/lib/private/RedisFactory.php', + 'OC\\Remote\\Api\\ApiBase' => $baseDir . '/lib/private/Remote/Api/ApiBase.php', + 'OC\\Remote\\Api\\NotFoundException' => $baseDir . '/lib/private/Remote/Api/NotFoundException.php', + 'OC\\Remote\\Api\\OCS' => $baseDir . '/lib/private/Remote/Api/OCS.php', + 'OC\\Remote\\Credentials' => $baseDir . '/lib/private/Remote/Credentials.php', + 'OC\\Remote\\Instance' => $baseDir . '/lib/private/Remote/Instance.php', + 'OC\\Remote\\User' => $baseDir . '/lib/private/Remote/User.php', 'OC\\Repair' => $baseDir . '/lib/private/Repair.php', 'OC\\RepairException' => $baseDir . '/lib/private/RepairException.php', 'OC\\Repair\\CleanTags' => $baseDir . '/lib/private/Repair/CleanTags.php', diff --git a/lib/composer/composer/autoload_static.php b/lib/composer/composer/autoload_static.php index 1828957b32aaf..66350841bafdb 100644 --- a/lib/composer/composer/autoload_static.php +++ b/lib/composer/composer/autoload_static.php @@ -784,6 +784,12 @@ class ComposerStaticInit53792487c5a8370acc0b06b1a864ff4c 'OC\\Preview\\WatcherConnector' => __DIR__ . '/../../..' . '/lib/private/Preview/WatcherConnector.php', 'OC\\Preview\\XBitmap' => __DIR__ . '/../../..' . '/lib/private/Preview/XBitmap.php', 'OC\\RedisFactory' => __DIR__ . '/../../..' . '/lib/private/RedisFactory.php', + 'OC\\Remote\\Api\\ApiBase' => __DIR__ . '/../../..' . '/lib/private/Remote/Api/ApiBase.php', + 'OC\\Remote\\Api\\NotFoundException' => __DIR__ . '/../../..' . '/lib/private/Remote/Api/NotFoundException.php', + 'OC\\Remote\\Api\\OCS' => __DIR__ . '/../../..' . '/lib/private/Remote/Api/OCS.php', + 'OC\\Remote\\Credentials' => __DIR__ . '/../../..' . '/lib/private/Remote/Credentials.php', + 'OC\\Remote\\Instance' => __DIR__ . '/../../..' . '/lib/private/Remote/Instance.php', + 'OC\\Remote\\User' => __DIR__ . '/../../..' . '/lib/private/Remote/User.php', 'OC\\Repair' => __DIR__ . '/../../..' . '/lib/private/Repair.php', 'OC\\RepairException' => __DIR__ . '/../../..' . '/lib/private/RepairException.php', 'OC\\Repair\\CleanTags' => __DIR__ . '/../../..' . '/lib/private/Repair/CleanTags.php', diff --git a/lib/private/Remote/Api/ApiBase.php b/lib/private/Remote/Api/ApiBase.php new file mode 100644 index 0000000000000..907d88a11d943 --- /dev/null +++ b/lib/private/Remote/Api/ApiBase.php @@ -0,0 +1,96 @@ + + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * 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 . + * + */ + +namespace OC\Remote\Api; + +use OC\Remote\Credentials; +use OC\Remote\Instance; +use OCP\Http\Client\IClientService; + +class ApiBase { + /** @var Instance */ + private $instance; + /** @var Credentials */ + private $credentials; + /** @var IClientService */ + private $clientService; + + public function __construct(Instance $instance, Credentials $credentials, IClientService $clientService) { + $this->instance = $instance; + $this->credentials = $credentials; + $this->clientService = $clientService; + } + + protected function getHttpClient() { + return $this->clientService->newClient(); + } + + protected function addDefaultHeaders(array $headers) { + return array_merge([ + 'OCS-APIREQUEST' => 'true', + 'Accept' => 'application/json' + ], $headers); + } + + /** + * @param string $method + * @param string $url + * @param array $body + * @param array $query + * @param array $headers + * @return resource|string + */ + protected function request($method, $url, array $body = [], array $query = [], array $headers = []) { + $fullUrl = trim($this->instance->getFullUrl(), '/') . '/' . $url; + $options = [ + 'query' => $query, + 'headers' => $this->addDefaultHeaders($headers), + 'auth' => [$this->credentials->getUsername(), $this->credentials->getPassword()] + ]; + if ($body) { + $options['body'] = $body; + } + + $client = $this->getHttpClient(); + + switch ($method) { + case 'get': + $response = $client->get($fullUrl, $options); + break; + case 'post': + $response = $client->post($fullUrl, $options); + break; + case 'put': + $response = $client->put($fullUrl, $options); + break; + case 'delete': + $response = $client->delete($fullUrl, $options); + break; + case 'options': + $response = $client->options($fullUrl, $options); + break; + default: + throw new \InvalidArgumentException('Invalid method ' . $method); + } + + return $response->getBody(); + } +} diff --git a/lib/private/Remote/Api/NotFoundException.php b/lib/private/Remote/Api/NotFoundException.php new file mode 100644 index 0000000000000..e660beb70d065 --- /dev/null +++ b/lib/private/Remote/Api/NotFoundException.php @@ -0,0 +1,27 @@ + + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * 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 . + * + */ + +namespace OC\Remote\Api; + + +class NotFoundException extends \Exception { + +} diff --git a/lib/private/Remote/Api/OCS.php b/lib/private/Remote/Api/OCS.php new file mode 100644 index 0000000000000..d7027ad3f4b41 --- /dev/null +++ b/lib/private/Remote/Api/OCS.php @@ -0,0 +1,66 @@ + + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * 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 . + * + */ + +namespace OC\Remote\Api; + + +use OC\ForbiddenException; +use OC\Remote\User; +use OCP\API; + +class OCS extends ApiBase { + /** + * @param string $method + * @param string $url + * @param array $body + * @param array $query + * @param array $headers + * @return array + * @throws ForbiddenException + * @throws NotFoundException + * @throws \Exception + */ + protected function request($method, $url, array $body = [], array $query = [], array $headers = []) { + $response = json_decode(parent::request($method, '/ocs/v2.php/' . $url, $body, $query, $headers), true); + if (!isset($result['ocs']) || !isset($result['ocs']['meta'])) { + throw new \Exception('Invalid ocs response'); + } + if ($response['ocs']['meta']['statuscode'] === API::RESPOND_UNAUTHORISED) { + throw new ForbiddenException(); + } + if ($response['ocs']['meta']['statuscode'] === API::RESPOND_NOT_FOUND) { + throw new NotFoundException(); + } + if ($response['ocs']['meta']['status'] !== 'ok') { + throw new \Exception('Unknown ocs error ' . $response['ocs']['meta']['message']); + } + + return $response['ocs']['data']; + } + + public function getUser($userId) { + return new User($this->request('get', 'cloud/users/' . $userId)); + } + + public function getCapabilities() { + return $this->request('get', 'cloud/capabilities'); + } +} diff --git a/lib/private/Remote/Credentials.php b/lib/private/Remote/Credentials.php new file mode 100644 index 0000000000000..3537df3fdc07b --- /dev/null +++ b/lib/private/Remote/Credentials.php @@ -0,0 +1,53 @@ + + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * 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 . + * + */ + +namespace OC\Remote; + + +class Credentials { + /** @var string */ + private $user; + /** @var string */ + private $password; + + /** + * @param string $user + * @param string $password + */ + public function __construct($user, $password) { + $this->user = $user; + $this->password = $password; + } + + /** + * @return string + */ + public function getUsername() { + return $this->user; + } + + /** + * @return string + */ + public function getPassword() { + return $this->password; + } +} diff --git a/lib/private/Remote/Instance.php b/lib/private/Remote/Instance.php new file mode 100644 index 0000000000000..3e8f22f4df414 --- /dev/null +++ b/lib/private/Remote/Instance.php @@ -0,0 +1,127 @@ + + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * 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 . + * + */ + +namespace OC\Remote; + +use OCP\Http\Client\IClientService; +use OCP\ICache; + +/** + * Provides some basic info about a remote Nextcloud instance + */ +class Instance { + /** @var string */ + private $url; + + /** @var ICache */ + private $cache; + + /** @var IClientService */ + private $clientService; + + private $status; + + /** + * @param string $url + * @param ICache $cache + * @param IClientService $clientService + */ + public function __construct($url, ICache $cache, IClientService $clientService) { + $url = str_replace('https://', '', $url); + $this->url = str_replace('http://', '', $url); + $this->cache = $cache; + $this->clientService = $clientService; + } + + /** + * @return string The url of the remote server without protocol + */ + public function getUrl() { + return $this->url; + } + + /** + * @return string The of of the remote server with protocol + */ + public function getFullUrl() { + return $this->getProtocol() . '://' . $this->getUrl(); + } + + /** + * @return string The full version string in '13.1.2.3' format + */ + public function getVersion() { + $status = $this->getStatus(); + return $status['version']; + } + + /** + * @return string 'http' or 'https' + */ + public function getProtocol() { + $status = $this->getStatus(); + return $status['protocol']; + } + + /** + * Check that the remote server is installed and not in maintenance mode + * + * @return bool + */ + public function isActive() { + $status = $this->getStatus(); + return $status['installed'] && !$status['maintenance']; + } + + private function getStatus() { + if ($this->status) { + return $this->status; + } + $key = 'remote/' . $this->url . '/status'; + $status = $this->cache->get($key); + if (!$status) { + $response = $this->downloadStatus('https://' . $this->getUrl() . '/status.php'); + $protocol = 'https'; + if (!$response) { + $response = $this->downloadStatus('http://' . $this->getUrl() . '/status.php'); + $protocol = 'http'; + } + $status = json_decode($response, true); + if ($status) { + $status['protocol'] = $protocol; + } + if ($status) { + $this->cache->set($key, $status, 5 * 60); + $this->status = $status; + } + } + return $status; + } + + private function downloadStatus($url) { + try { + $request = $this->clientService->newClient()->get($url); + return $request->getBody(); + } catch (\Exception $e) { + return false; + } + } +} diff --git a/lib/private/Remote/User.php b/lib/private/Remote/User.php new file mode 100644 index 0000000000000..1fd0521f60d25 --- /dev/null +++ b/lib/private/Remote/User.php @@ -0,0 +1,124 @@ + + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * 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 . + * + */ + +namespace OC\Remote; + + +class User { + /** @var array */ + private $data; + + public function __construct(array $data) { + $this->data = $data; + } + + + /** + * @return string + */ + public function getUserId() { + return $this->data['id']; + } + + /** + * @return string + */ + public function getEmail() { + return $this->data['email']; + } + + /** + * @return string + */ + public function getDisplayName() { + return $this->data['displayname']; + } + + /** + * @return string + */ + public function getPhone() { + return $this->data['phone']; + } + + /** + * @return string + */ + public function getAddress() { + return $this->data['address']; + } + + /** + * @return string + */ + public function getWebsite() { + return $this->data['website']; + } + + /** + * @return string + */ + public function getTwitter() { + return isset($this->data['twitter']) ? $this->data['twitter'] : ''; + } + + /** + * @return string[] + */ + public function getGroups() { + return $this->data['groups']; + } + + /** + * @return string + */ + public function getLanguage() { + return $this->data['language']; + } + + /** + * @return int + */ + public function getUsedSpace() { + return $this->data['quota']['used']; + } + + /** + * @return int + */ + public function getFreeSpace() { + return $this->data['quota']['free']; + } + + /** + * @return int + */ + public function getTotalSpace() { + return $this->data['quota']['total']; + } + + /** + * @return int + */ + public function getQuota() { + return $this->data['quota']['quota']; + } +}