diff options
author | Michael Grosser <development@stp-ip.net> | 2016-11-02 17:00:24 +0000 |
---|---|---|
committer | GitHub <noreply@github.com> | 2016-11-02 17:00:24 +0000 |
commit | e81d04cd8d2ac0de3d06d4586550469384c5d91a (patch) | |
tree | 0e243d105db3e00f1d3f899b9ecfbd73f3d98235 /lib | |
parent | 23dd62b653e42201468f9bed369a5993dea876b0 (diff) | |
parent | 0eeef26a8e2515b802015fe09bef7909aa183491 (diff) | |
download | nextcloud-server-e81d04cd8d2ac0de3d06d4586550469384c5d91a.tar.gz nextcloud-server-e81d04cd8d2ac0de3d06d4586550469384c5d91a.zip |
Merge pull request #1940 from nextcloud/new-appstore
Use new appstore API
Diffstat (limited to 'lib')
-rw-r--r-- | lib/composer/composer/autoload_classmap.php | 6 | ||||
-rw-r--r-- | lib/composer/composer/autoload_static.php | 6 | ||||
-rw-r--r-- | lib/private/App/AppStore/Fetcher/AppFetcher.php | 56 | ||||
-rw-r--r-- | lib/private/App/AppStore/Fetcher/CategoryFetcher.php | 45 | ||||
-rw-r--r-- | lib/private/App/AppStore/Fetcher/Fetcher.php | 92 | ||||
-rw-r--r-- | lib/private/App/AppStore/Version/Version.php | 52 | ||||
-rw-r--r-- | lib/private/App/AppStore/Version/VersionParser.php | 83 | ||||
-rw-r--r-- | lib/private/App/DependencyAnalyzer.php | 9 | ||||
-rw-r--r-- | lib/private/Archive/Archive.php | 21 | ||||
-rw-r--r-- | lib/private/Installer.php | 526 | ||||
-rw-r--r-- | lib/private/OCSClient.php | 351 | ||||
-rw-r--r-- | lib/private/Server.php | 32 | ||||
-rw-r--r-- | lib/private/Updater.php | 15 | ||||
-rw-r--r-- | lib/private/legacy/app.php | 245 | ||||
-rw-r--r-- | lib/private/legacy/util.php | 1 |
15 files changed, 649 insertions, 891 deletions
diff --git a/lib/composer/composer/autoload_classmap.php b/lib/composer/composer/autoload_classmap.php index 532a6f39848..ddd531868d4 100644 --- a/lib/composer/composer/autoload_classmap.php +++ b/lib/composer/composer/autoload_classmap.php @@ -279,6 +279,11 @@ return array( 'OC\\AppFramework\\Utility\\TimeFactory' => $baseDir . '/lib/private/AppFramework/Utility/TimeFactory.php', 'OC\\AppHelper' => $baseDir . '/lib/private/AppHelper.php', 'OC\\App\\AppManager' => $baseDir . '/lib/private/App/AppManager.php', + 'OC\\App\\AppStore\\Fetcher\\AppFetcher' => $baseDir . '/lib/private/App/AppStore/Fetcher/AppFetcher.php', + 'OC\\App\\AppStore\\Fetcher\\CategoryFetcher' => $baseDir . '/lib/private/App/AppStore/Fetcher/CategoryFetcher.php', + 'OC\\App\\AppStore\\Fetcher\\Fetcher' => $baseDir . '/lib/private/App/AppStore/Fetcher/Fetcher.php', + 'OC\\App\\AppStore\\Version\\Version' => $baseDir . '/lib/private/App/AppStore/Version/Version.php', + 'OC\\App\\AppStore\\Version\\VersionParser' => $baseDir . '/lib/private/App/AppStore/Version/VersionParser.php', 'OC\\App\\CodeChecker\\AbstractCheck' => $baseDir . '/lib/private/App/CodeChecker/AbstractCheck.php', 'OC\\App\\CodeChecker\\CodeChecker' => $baseDir . '/lib/private/App/CodeChecker/CodeChecker.php', 'OC\\App\\CodeChecker\\DeprecationCheck' => $baseDir . '/lib/private/App/CodeChecker/DeprecationCheck.php', @@ -602,7 +607,6 @@ return array( 'OC\\Notification\\Action' => $baseDir . '/lib/private/Notification/Action.php', 'OC\\Notification\\Manager' => $baseDir . '/lib/private/Notification/Manager.php', 'OC\\Notification\\Notification' => $baseDir . '/lib/private/Notification/Notification.php', - 'OC\\OCSClient' => $baseDir . '/lib/private/OCSClient.php', 'OC\\OCS\\CoreCapabilities' => $baseDir . '/lib/private/OCS/CoreCapabilities.php', 'OC\\OCS\\Exception' => $baseDir . '/lib/private/OCS/Exception.php', 'OC\\OCS\\Person' => $baseDir . '/lib/private/OCS/Person.php', diff --git a/lib/composer/composer/autoload_static.php b/lib/composer/composer/autoload_static.php index c0a3e9b50c6..99a3c3d540e 100644 --- a/lib/composer/composer/autoload_static.php +++ b/lib/composer/composer/autoload_static.php @@ -309,6 +309,11 @@ class ComposerStaticInit53792487c5a8370acc0b06b1a864ff4c 'OC\\AppFramework\\Utility\\TimeFactory' => __DIR__ . '/../../..' . '/lib/private/AppFramework/Utility/TimeFactory.php', 'OC\\AppHelper' => __DIR__ . '/../../..' . '/lib/private/AppHelper.php', 'OC\\App\\AppManager' => __DIR__ . '/../../..' . '/lib/private/App/AppManager.php', + 'OC\\App\\AppStore\\Fetcher\\AppFetcher' => __DIR__ . '/../../..' . '/lib/private/App/AppStore/Fetcher/AppFetcher.php', + 'OC\\App\\AppStore\\Fetcher\\CategoryFetcher' => __DIR__ . '/../../..' . '/lib/private/App/AppStore/Fetcher/CategoryFetcher.php', + 'OC\\App\\AppStore\\Fetcher\\Fetcher' => __DIR__ . '/../../..' . '/lib/private/App/AppStore/Fetcher/Fetcher.php', + 'OC\\App\\AppStore\\Version\\Version' => __DIR__ . '/../../..' . '/lib/private/App/AppStore/Version/Version.php', + 'OC\\App\\AppStore\\Version\\VersionParser' => __DIR__ . '/../../..' . '/lib/private/App/AppStore/Version/VersionParser.php', 'OC\\App\\CodeChecker\\AbstractCheck' => __DIR__ . '/../../..' . '/lib/private/App/CodeChecker/AbstractCheck.php', 'OC\\App\\CodeChecker\\CodeChecker' => __DIR__ . '/../../..' . '/lib/private/App/CodeChecker/CodeChecker.php', 'OC\\App\\CodeChecker\\DeprecationCheck' => __DIR__ . '/../../..' . '/lib/private/App/CodeChecker/DeprecationCheck.php', @@ -632,7 +637,6 @@ class ComposerStaticInit53792487c5a8370acc0b06b1a864ff4c 'OC\\Notification\\Action' => __DIR__ . '/../../..' . '/lib/private/Notification/Action.php', 'OC\\Notification\\Manager' => __DIR__ . '/../../..' . '/lib/private/Notification/Manager.php', 'OC\\Notification\\Notification' => __DIR__ . '/../../..' . '/lib/private/Notification/Notification.php', - 'OC\\OCSClient' => __DIR__ . '/../../..' . '/lib/private/OCSClient.php', 'OC\\OCS\\CoreCapabilities' => __DIR__ . '/../../..' . '/lib/private/OCS/CoreCapabilities.php', 'OC\\OCS\\Exception' => __DIR__ . '/../../..' . '/lib/private/OCS/Exception.php', 'OC\\OCS\\Person' => __DIR__ . '/../../..' . '/lib/private/OCS/Person.php', diff --git a/lib/private/App/AppStore/Fetcher/AppFetcher.php b/lib/private/App/AppStore/Fetcher/AppFetcher.php new file mode 100644 index 00000000000..19e61d416a0 --- /dev/null +++ b/lib/private/App/AppStore/Fetcher/AppFetcher.php @@ -0,0 +1,56 @@ +<?php +/** + * @copyright Copyright (c) 2016 Lukas Reschke <lukas@statuscode.ch> + * + * @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 <http://www.gnu.org/licenses/>. + * + */ + +namespace OC\App\AppStore\Fetcher; + +use OCP\AppFramework\Utility\ITimeFactory; +use OCP\Files\IAppData; +use OCP\Http\Client\IClientService; +use OCP\IConfig; + +class AppFetcher extends Fetcher { + /** + * @param IAppData $appData + * @param IClientService $clientService + * @param ITimeFactory $timeFactory + * @param IConfig $config; + */ + public function __construct(IAppData $appData, + IClientService $clientService, + ITimeFactory $timeFactory, + IConfig $config) { + parent::__construct( + $appData, + $clientService, + $timeFactory + ); + + $this->fileName = 'apps.json'; + + $versionArray = \OC_Util::getVersion(); + $this->endpointUrl = sprintf( + 'https://apps.nextcloud.com/api/v1/platform/%d.%d.%d/apps.json', + $versionArray[0], + $versionArray[1], + $versionArray[2] + ); + } +} diff --git a/lib/private/App/AppStore/Fetcher/CategoryFetcher.php b/lib/private/App/AppStore/Fetcher/CategoryFetcher.php new file mode 100644 index 00000000000..74201ec3737 --- /dev/null +++ b/lib/private/App/AppStore/Fetcher/CategoryFetcher.php @@ -0,0 +1,45 @@ +<?php +/** + * @copyright Copyright (c) 2016 Lukas Reschke <lukas@statuscode.ch> + * + * @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 <http://www.gnu.org/licenses/>. + * + */ + +namespace OC\App\AppStore\Fetcher; + +use OCP\AppFramework\Utility\ITimeFactory; +use OCP\Files\IAppData; +use OCP\Http\Client\IClientService; + +class CategoryFetcher extends Fetcher { + /** + * @param IAppData $appData + * @param IClientService $clientService + * @param ITimeFactory $timeFactory + */ + public function __construct(IAppData $appData, + IClientService $clientService, + ITimeFactory $timeFactory) { + parent::__construct( + $appData, + $clientService, + $timeFactory + ); + $this->fileName = 'categories.json'; + $this->endpointUrl = 'https://apps.nextcloud.com/api/v1/categories.json'; + } +} diff --git a/lib/private/App/AppStore/Fetcher/Fetcher.php b/lib/private/App/AppStore/Fetcher/Fetcher.php new file mode 100644 index 00000000000..cffff9176e2 --- /dev/null +++ b/lib/private/App/AppStore/Fetcher/Fetcher.php @@ -0,0 +1,92 @@ +<?php +/** + * @copyright Copyright (c) 2016 Lukas Reschke <lukas@statuscode.ch> + * + * @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 <http://www.gnu.org/licenses/>. + * + */ + +namespace OC\App\AppStore\Fetcher; + +use OCP\AppFramework\Utility\ITimeFactory; +use OCP\Files\IAppData; +use OCP\Files\NotFoundException; +use OCP\Http\Client\IClientService; + +abstract class Fetcher { + const INVALIDATE_AFTER_SECONDS = 300; + + /** @var IAppData */ + private $appData; + /** @var IClientService */ + private $clientService; + /** @var ITimeFactory */ + private $timeFactory; + /** @var string */ + protected $fileName; + /** @var string */ + protected $endpointUrl; + + /** + * @param IAppData $appData + * @param IClientService $clientService + * @param ITimeFactory $timeFactory + */ + public function __construct(IAppData $appData, + IClientService $clientService, + ITimeFactory $timeFactory) { + $this->appData = $appData; + $this->clientService = $clientService; + $this->timeFactory = $timeFactory; + } + + /** + * Returns the array with the categories on the appstore server + * + * @return array + */ + public function get() { + $rootFolder = $this->appData->getFolder('/'); + + try { + // File does already exists + $file = $rootFolder->getFile($this->fileName); + $jsonBlob = json_decode($file->getContent(), true); + if(is_array($jsonBlob)) { + // If the timestamp is older than 300 seconds request the files new + if((int)$jsonBlob['timestamp'] > ($this->timeFactory->getTime() - self::INVALIDATE_AFTER_SECONDS)) { + return $jsonBlob['data']; + } + } + } catch (NotFoundException $e) { + // File does not already exists + $file = $rootFolder->newFile($this->fileName); + } + + // Refresh the file content + $client = $this->clientService->newClient(); + try { + $response = $client->get($this->endpointUrl); + $responseJson = []; + $responseJson['data'] = json_decode($response->getBody(), true); + $responseJson['timestamp'] = $this->timeFactory->getTime(); + $file->putContent(json_encode($responseJson)); + return json_decode($file->getContent(), true)['data']; + } catch (\Exception $e) { + return []; + } + } +} diff --git a/lib/private/App/AppStore/Version/Version.php b/lib/private/App/AppStore/Version/Version.php new file mode 100644 index 00000000000..ca182ae078b --- /dev/null +++ b/lib/private/App/AppStore/Version/Version.php @@ -0,0 +1,52 @@ +<?php +/** + * @copyright Copyright (c) 2016 Lukas Reschke <lukas@statuscode.ch> + * + * @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 <http://www.gnu.org/licenses/>. + * + */ + +namespace OC\App\AppStore\Version; + +class Version { + /** @var string */ + private $minVersion; + /** @var string */ + private $maxVersion; + + /** + * @param string $minVersion + * @param string $maxVersion + */ + public function __construct($minVersion, $maxVersion) { + $this->minVersion = $minVersion; + $this->maxVersion = $maxVersion; + } + + /** + * @return string + */ + public function getMinimumVersion() { + return $this->minVersion; + } + + /** + * @return string + */ + public function getMaximumVersion() { + return $this->maxVersion; + } +} diff --git a/lib/private/App/AppStore/Version/VersionParser.php b/lib/private/App/AppStore/Version/VersionParser.php new file mode 100644 index 00000000000..b548ef386d9 --- /dev/null +++ b/lib/private/App/AppStore/Version/VersionParser.php @@ -0,0 +1,83 @@ +<?php +/** + * @copyright Copyright (c) 2016 Lukas Reschke <lukas@statuscode.ch> + * + * @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 <http://www.gnu.org/licenses/>. + * + */ + +namespace OC\App\AppStore\Version; + +/** + * Class VersionParser parses the versions as sent by the Nextcloud app store + * + * @package OC\App\AppStore + */ +class VersionParser { + /** + * @param string $versionString + * @return bool + */ + private function isValidVersionString($versionString) { + return (bool)preg_match('/^[0-9.]+$/', $versionString); + } + + /** + * Returns the version for a version string + * + * @param string $versionSpec + * @return Version + * @throws \Exception If the version cannot be parsed + */ + public function getVersion($versionSpec) { + // * indicates that the version is compatible with all versions + if($versionSpec === '*') { + return new Version('', ''); + } + + // Count the amount of =, if it is one then it's either maximum or minimum + // version. If it is two then it is maximum and minimum. + $versionElements = explode(' ', $versionSpec); + $firstVersion = isset($versionElements[0]) ? $versionElements[0] : ''; + $firstVersionNumber = substr($firstVersion, 2); + $secondVersion = isset($versionElements[1]) ? $versionElements[1] : ''; + $secondVersionNumber = substr($secondVersion, 2); + + switch(count($versionElements)) { + case 1: + if(!$this->isValidVersionString($firstVersionNumber)) { + break; + } + if(substr($firstVersion, 0, 1) === '>') { + return new Version($firstVersionNumber, ''); + } else { + return new Version('', $firstVersionNumber); + } + case 2: + if(!$this->isValidVersionString($firstVersionNumber) || !$this->isValidVersionString($secondVersionNumber)) { + break; + } + return new Version($firstVersionNumber, $secondVersionNumber); + } + + throw new \Exception( + sprintf( + 'Version cannot be parsed: %s', + $versionSpec + ) + ); + } +} diff --git a/lib/private/App/DependencyAnalyzer.php b/lib/private/App/DependencyAnalyzer.php index 67268981e99..c24b25ff14d 100644 --- a/lib/private/App/DependencyAnalyzer.php +++ b/lib/private/App/DependencyAnalyzer.php @@ -1,6 +1,7 @@ <?php /** * @copyright Copyright (c) 2016, ownCloud, Inc. + * @copyright Copyright (c) 2016, Lukas Reschke <lukas@statuscode.ch> * * @author Bernhard Posselt <dev@bernhard-posselt.com> * @author Joas Schilling <coding@schilljs.com> @@ -294,7 +295,9 @@ class DependencyAnalyzer { private function analyzeOC(array $dependencies, array $appInfo) { $missing = []; $minVersion = null; - if (isset($dependencies['owncloud']['@attributes']['min-version'])) { + if (isset($dependencies['nextcloud']['@attributes']['min-version'])) { + $minVersion = $dependencies['nextcloud']['@attributes']['min-version']; + } elseif (isset($dependencies['owncloud']['@attributes']['min-version'])) { $minVersion = $dependencies['owncloud']['@attributes']['min-version']; } elseif (isset($appInfo['requiremin'])) { $minVersion = $appInfo['requiremin']; @@ -302,7 +305,9 @@ class DependencyAnalyzer { $minVersion = $appInfo['require']; } $maxVersion = null; - if (isset($dependencies['owncloud']['@attributes']['max-version'])) { + if (isset($dependencies['nextcloud']['@attributes']['max-version'])) { + $maxVersion = $dependencies['nextcloud']['@attributes']['max-version']; + } elseif (isset($dependencies['owncloud']['@attributes']['max-version'])) { $maxVersion = $dependencies['owncloud']['@attributes']['max-version']; } elseif (isset($appInfo['requiremax'])) { $maxVersion = $appInfo['requiremax']; diff --git a/lib/private/Archive/Archive.php b/lib/private/Archive/Archive.php index da2c53f2aa1..fadc12d2a24 100644 --- a/lib/private/Archive/Archive.php +++ b/lib/private/Archive/Archive.php @@ -32,26 +32,7 @@ namespace OC\Archive; -abstract class Archive{ - /** - * Open any of the supported archive types - * - * @param string $path - * @return Archive|void - */ - public static function open($path) { - $mime = \OC::$server->getMimeTypeDetector()->detect($path); - - switch($mime) { - case 'application/zip': - return new ZIP($path); - case 'application/x-gzip': - return new TAR($path); - case 'application/x-bzip2': - return new TAR($path); - } - } - +abstract class Archive { /** * @param $source */ diff --git a/lib/private/Installer.php b/lib/private/Installer.php index 009df790585..2366b762654 100644 --- a/lib/private/Installer.php +++ b/lib/private/Installer.php @@ -1,6 +1,7 @@ <?php /** * @copyright Copyright (c) 2016, ownCloud, Inc. + * @copyright Copyright (c) 2016, Lukas Reschke <lukas@statuscode.ch> * * @author Arthur Schiwon <blizzz@arthur-schiwon.de> * @author Bart Visscher <bartv@thisnet.nl> @@ -40,90 +41,64 @@ namespace OC; +use OC\App\AppStore\Fetcher\AppFetcher; use OC\App\CodeChecker\CodeChecker; use OC\App\CodeChecker\EmptyCheck; use OC\App\CodeChecker\PrivateCheck; +use OC\Archive\Archive; +use OC\Archive\TAR; use OC_App; use OC_DB; use OC_Helper; +use OCP\Http\Client\IClientService; +use OCP\ILogger; +use OCP\ITempManager; +use phpseclib\File\X509; /** - * This class provides the functionality needed to install, update and remove plugins/apps + * This class provides the functionality needed to install, update and remove apps */ class Installer { + /** @var AppFetcher */ + private $appFetcher; + /** @var IClientService */ + private $clientService; + /** @var ITempManager */ + private $tempManager; + /** @var ILogger */ + private $logger; /** + * @param AppFetcher $appFetcher + * @param IClientService $clientService + * @param ITempManager $tempManager + * @param ILogger $logger + */ + public function __construct(AppFetcher $appFetcher, + IClientService $clientService, + ITempManager $tempManager, + ILogger $logger) { + $this->appFetcher = $appFetcher; + $this->clientService = $clientService; + $this->tempManager = $tempManager; + $this->logger = $logger; + } + + /** + * Installs an app that is located in one of the app folders already * - * This function installs an app. All information needed are passed in the - * associative array $data. - * The following keys are required: - * - source: string, can be "path" or "http" - * - * One of the following keys is required: - * - path: path to the file containing the app - * - href: link to the downloadable file containing the app - * - * The following keys are optional: - * - pretend: boolean, if set true the system won't do anything - * - noinstall: boolean, if true appinfo/install.php won't be loaded - * - inactive: boolean, if set true the appconfig/app.sample.php won't be - * renamed - * - * This function works as follows - * -# fetching the file - * -# unzipping it - * -# check the code - * -# installing the database at appinfo/database.xml - * -# including appinfo/install.php - * -# setting the installed version - * - * It is the task of oc_app_install to create the tables and do whatever is - * needed to get the app working. - * - * Installs an app - * @param array $data with all information + * @param string $appId App to install * @throws \Exception * @return integer */ - public static function installApp( $data = array()) { - $l = \OC::$server->getL10N('lib'); - - list($extractDir, $path) = self::downloadApp($data); - - $info = self::checkAppsIntegrity($data, $extractDir, $path); - $appId = OC_App::cleanAppId($info['id']); - $basedir = OC_App::getInstallPath().'/'.$appId; - //check if the destination directory already exists - if(is_dir($basedir)) { - OC_Helper::rmdirr($extractDir); - if($data['source']=='http') { - unlink($path); - } - throw new \Exception($l->t("App directory already exists")); - } - - if(!empty($data['pretent'])) { - return false; + public function installApp($appId) { + $app = \OC_App::findAppInDirectories($appId); + if($app === false) { + throw new \Exception('App not found in any app directory'); } - //copy the app to the correct place - if(@!mkdir($basedir)) { - OC_Helper::rmdirr($extractDir); - if($data['source']=='http') { - unlink($path); - } - throw new \Exception($l->t("Can't create app folder. Please fix permissions. %s", array($basedir))); - } - - $extractDir .= '/' . $info['id']; - if(!file_exists($extractDir)) { - OC_Helper::rmdirr($basedir); - throw new \Exception($l->t("Archive does not contain a directory named %s", $info['id'])); - } - OC_Helper::copyr($extractDir, $basedir); - - //remove temporary files - OC_Helper::rmdirr($extractDir); + $basedir = $app['path'].'/'.$appId; + $info = OC_App::getAppInfo($basedir.'/appinfo/info.xml', true); //install the database if(is_file($basedir.'/appinfo/database.xml')) { @@ -168,259 +143,189 @@ class Installer { * * Checks whether or not an app is installed, i.e. registered in apps table. */ - public static function isInstalled( $app ) { + public static function isInstalled( $app ) { return (\OC::$server->getConfig()->getAppValue($app, "installed_version", null) !== null); } /** - * @brief Update an application - * @param array $info - * @param bool $isShipped - * @throws \Exception - * @return bool - * - * This function could work like described below, but currently it disables and then - * enables the app again. This does result in an updated app. - * - * - * This function installs an app. All information needed are passed in the - * associative array $info. - * The following keys are required: - * - source: string, can be "path" or "http" - * - * One of the following keys is required: - * - path: path to the file containing the app - * - href: link to the downloadable file containing the app - * - * The following keys are optional: - * - pretend: boolean, if set true the system won't do anything - * - noupgrade: boolean, if true appinfo/upgrade.php won't be loaded - * - * This function works as follows - * -# fetching the file - * -# removing the old files - * -# unzipping new file - * -# including appinfo/upgrade.php - * -# setting the installed version - * - * upgrade.php can determine the current installed version of the app using - * "\OC::$server->getAppConfig()->getValue($appid, 'installed_version')" - */ - public static function updateApp($info=array(), $isShipped=false) { - list($extractDir, $path) = self::downloadApp($info); - $info = self::checkAppsIntegrity($info, $extractDir, $path, $isShipped); - - $currentDir = OC_App::getAppPath($info['id']); - $basedir = OC_App::getInstallPath(); - $basedir .= '/'; - $basedir .= $info['id']; - - if($currentDir !== false && is_writable($currentDir)) { - $basedir = $currentDir; - } - if(is_dir($basedir)) { - OC_Helper::rmdirr($basedir); - } - - $appInExtractDir = $extractDir; - if (substr($extractDir, -1) !== '/') { - $appInExtractDir .= '/'; - } - - $appInExtractDir .= $info['id']; - OC_Helper::copyr($appInExtractDir, $basedir); - OC_Helper::rmdirr($extractDir); - - return OC_App::updateApp($info['id']); - } - - /** - * update an app by it's id + * Updates the specified app from the appstore * - * @param integer $ocsId + * @param string $appId * @return bool - * @throws \Exception */ - public static function updateAppByOCSId($ocsId) { - $ocsClient = new OCSClient( - \OC::$server->getHTTPClientService(), - \OC::$server->getConfig(), - \OC::$server->getLogger() - ); - $appData = $ocsClient->getApplication($ocsId, \OCP\Util::getVersion()); - $download = $ocsClient->getApplicationDownload($ocsId, \OCP\Util::getVersion()); - - if (isset($download['downloadlink']) && trim($download['downloadlink']) !== '') { - $download['downloadlink'] = str_replace(' ', '%20', $download['downloadlink']); - $info = array( - 'source' => 'http', - 'href' => $download['downloadlink'], - 'appdata' => $appData - ); - } else { - throw new \Exception('Could not fetch app info!'); + public function updateAppstoreApp($appId) { + if(self::isUpdateAvailable($appId, $this->appFetcher)) { + try { + $this->downloadApp($appId); + } catch (\Exception $e) { + $this->logger->error($e->getMessage(), ['app' => 'core']); + return false; + } + return OC_App::updateApp($appId); } - return self::updateApp($info); + return false; } /** - * @param array $data - * @return array - * @throws \Exception + * Downloads an app and puts it into the app directory + * + * @param string $appId + * + * @throws \Exception If the installation was not successful */ - public static function downloadApp($data = array()) { - $l = \OC::$server->getL10N('lib'); - - if(!isset($data['source'])) { - throw new \Exception($l->t("No source specified when installing app")); - } + public function downloadApp($appId) { + $appId = strtolower($appId); + + $apps = $this->appFetcher->get(); + foreach($apps as $app) { + if($app['id'] === $appId) { + // Load the certificate + $certificate = new X509(); + $certificate->loadCA(file_get_contents(__DIR__ . '/../../resources/codesigning/root.crt')); + $loadedCertificate = $certificate->loadX509($app['certificate']); + + // Verify if the certificate has been revoked + $crl = new X509(); + $crl->loadCA(file_get_contents(__DIR__ . '/../../resources/codesigning/root.crt')); + $crl->loadCRL(file_get_contents(__DIR__ . '/../../resources/codesigning/root.crl')); + if($crl->validateSignature() !== true) { + throw new \Exception('Could not validate CRL signature'); + } + $csn = $loadedCertificate['tbsCertificate']['serialNumber']->toString(); + $revoked = $crl->getRevoked($csn); + if ($revoked !== false) { + throw new \Exception( + sprintf( + 'Certificate "%s" has been revoked', + $csn + ) + ); + } - //download the file if necessary - if($data['source']=='http') { - $pathInfo = pathinfo($data['href']); - $extension = isset($pathInfo['extension']) ? '.' . $pathInfo['extension'] : ''; - $path = \OC::$server->getTempManager()->getTemporaryFile($extension); - if(!isset($data['href'])) { - throw new \Exception($l->t("No href specified when installing app from http")); - } - $client = \OC::$server->getHTTPClientService()->newClient(); - $client->get($data['href'], ['save_to' => $path]); - } else { - if(!isset($data['path'])) { - throw new \Exception($l->t("No path specified when installing app from local file")); - } - $path=$data['path']; - } + // Verify if the certificate has been issued by the Nextcloud Code Authority CA + if($certificate->validateSignature() !== true) { + throw new \Exception( + sprintf( + 'App with id %s has a certificate not issued by a trusted Code Signing Authority', + $appId + ) + ); + } - //detect the archive type - $mime = \OC::$server->getMimeTypeDetector()->detect($path); - if ($mime !=='application/zip' && $mime !== 'application/x-gzip' && $mime !== 'application/x-bzip2') { - throw new \Exception($l->t("Archives of type %s are not supported", array($mime))); - } + // Verify if the certificate is issued for the requested app id + $certInfo = openssl_x509_parse($app['certificate']); + if(!isset($certInfo['subject']['CN'])) { + throw new \Exception( + sprintf( + 'App with id %s has a cert with no CN', + $appId + ) + ); + } + if($certInfo['subject']['CN'] !== $appId) { + throw new \Exception( + sprintf( + 'App with id %s has a cert issued to %s', + $appId, + $certInfo['subject']['CN'] + ) + ); + } - //extract the archive in a temporary folder - $extractDir = \OC::$server->getTempManager()->getTemporaryFolder(); - OC_Helper::rmdirr($extractDir); - mkdir($extractDir); - if($archive=\OC\Archive\Archive::open($path)) { - $archive->extract($extractDir); - } else { - OC_Helper::rmdirr($extractDir); - if($data['source']=='http') { - unlink($path); - } - throw new \Exception($l->t("Failed to open archive when installing app")); - } + // Download the release + $tempFile = $this->tempManager->getTemporaryFile('.tar.gz'); + $client = $this->clientService->newClient(); + $client->get($app['releases'][0]['download'], ['save_to' => $tempFile]); + + // Check if the signature actually matches the downloaded content + $certificate = openssl_get_publickey($app['certificate']); + $verified = (bool)openssl_verify(file_get_contents($tempFile), base64_decode($app['releases'][0]['signature']), $certificate, OPENSSL_ALGO_SHA512); + openssl_free_key($certificate); + + if($verified === true) { + // Seems to match, let's proceed + $extractDir = $this->tempManager->getTemporaryFolder(); + $archive = new TAR($tempFile); + + if($archive) { + $archive->extract($extractDir); + $allFiles = scandir($extractDir); + $folders = array_diff($allFiles, ['.', '..']); + $folders = array_values($folders); + + if(count($folders) > 1) { + throw new \Exception( + sprintf( + 'Extracted app %s has more than 1 folder', + $appId + ) + ); + } - return array( - $extractDir, - $path - ); - } + // Check if appinfo/info.xml has the same app ID as well + $loadEntities = libxml_disable_entity_loader(false); + $xml = simplexml_load_file($extractDir . '/' . $folders[0] . '/appinfo/info.xml'); + libxml_disable_entity_loader($loadEntities); + if((string)$xml->id !== $appId) { + throw new \Exception( + sprintf( + 'App for id %s has a wrong app ID in info.xml: %s', + $appId, + (string)$xml->id + ) + ); + } - /** - * check an app's integrity - * @param array $data - * @param string $extractDir - * @param string $path - * @param bool $isShipped - * @return array - * @throws \Exception - */ - public static function checkAppsIntegrity($data, $extractDir, $path, $isShipped = false) { - $l = \OC::$server->getL10N('lib'); - //load the info.xml file of the app - if(!is_file($extractDir.'/appinfo/info.xml')) { - //try to find it in a subdir - $dh=opendir($extractDir); - if(is_resource($dh)) { - while (($folder = readdir($dh)) !== false) { - if($folder[0]!='.' and is_dir($extractDir.'/'.$folder)) { - if(is_file($extractDir.'/'.$folder.'/appinfo/info.xml')) { - $extractDir.='/'.$folder; + $baseDir = OC_App::getInstallPath() . '/' . $appId; + // Remove old app with the ID if existent + OC_Helper::rmdirr($baseDir); + // Move to app folder + if(@mkdir($baseDir)) { + $extractDir .= '/' . $folders[0]; + OC_Helper::copyr($extractDir, $baseDir); } + OC_Helper::copyr($extractDir, $baseDir); + OC_Helper::rmdirr($extractDir); + return; + } else { + throw new \Exception( + sprintf( + 'Could not extract app with ID %s to %s', + $appId, + $extractDir + ) + ); } - } - } - } - if(!is_file($extractDir.'/appinfo/info.xml')) { - OC_Helper::rmdirr($extractDir); - if($data['source'] === 'http') { - unlink($path); - } - throw new \Exception($l->t("App does not provide an info.xml file")); - } - - $info = OC_App::getAppInfo($extractDir.'/appinfo/info.xml', true); - if(!is_array($info)) { - throw new \Exception($l->t('App cannot be installed because appinfo file cannot be read.')); - } - - // We can't trust the parsed info.xml file as it may have been tampered - // with by an attacker and thus we need to use the local data to check - // whether the application needs to be signed. - $appId = OC_App::cleanAppId($data['appdata']['id']); - $appBelongingToId = OC_App::getInternalAppIdByOcs($appId); - if(is_string($appBelongingToId)) { - $previouslySigned = \OC::$server->getConfig()->getAppValue($appBelongingToId, 'signed', 'false'); - } else { - $appBelongingToId = $info['id']; - $previouslySigned = 'false'; - } - if($data['appdata']['level'] === OC_App::officialApp || $previouslySigned === 'true') { - \OC::$server->getConfig()->setAppValue($appBelongingToId, 'signed', 'true'); - $integrityResult = \OC::$server->getIntegrityCodeChecker()->verifyAppSignature( - $appBelongingToId, - $extractDir - ); - if($integrityResult !== []) { - $e = new \Exception( - $l->t( - 'Signature could not get checked. Please contact the app developer and check your admin screen.' + } else { + // Signature does not match + throw new \Exception( + sprintf( + 'App with id %s has invalid signature', + $appId ) - ); - throw $e; + ); + } } } - // check the code for not allowed calls - if(!$isShipped && !Installer::checkCode($extractDir)) { - OC_Helper::rmdirr($extractDir); - throw new \Exception($l->t("App can't be installed because of not allowed code in the App")); - } - - // check if the app is compatible with this version of ownCloud - if(!OC_App::isAppCompatible(\OCP\Util::getVersion(), $info)) { - OC_Helper::rmdirr($extractDir); - throw new \Exception($l->t("App can't be installed because it is not compatible with this version of the server")); - } - - // check if shipped tag is set which is only allowed for apps that are shipped with ownCloud - if(!$isShipped && isset($info['shipped']) && ($info['shipped']=='true')) { - OC_Helper::rmdirr($extractDir); - throw new \Exception($l->t("App can't be installed because it contains the <shipped>true</shipped> tag which is not allowed for non shipped apps")); - } - - // check if the ocs version is the same as the version in info.xml/version - $version = trim($info['version']); - - if(isset($data['appdata']['version']) && $version<>trim($data['appdata']['version'])) { - OC_Helper::rmdirr($extractDir); - throw new \Exception($l->t("App can't be installed because the version in info.xml is not the same as the version reported from the app store")); - } - - return $info; + throw new \Exception( + sprintf( + 'Could not download app %s', + $appId + ) + ); } /** * Check if an update for the app is available - * @param string $app - * @return string|false false or the version number of the update * - * The function will check if an update for a version is available + * @param string $appId + * @param AppFetcher $appFetcher + * @return string|false false or the version number of the update */ - public static function isUpdateAvailable( $app ) { + public static function isUpdateAvailable($appId, + AppFetcher $appFetcher) { static $isInstanceReadyForUpdates = null; if ($isInstanceReadyForUpdates === null) { @@ -436,27 +341,20 @@ class Installer { return false; } - $ocsid=\OC::$server->getAppConfig()->getValue( $app, 'ocsid', ''); - - if($ocsid<>'') { - $ocsClient = new OCSClient( - \OC::$server->getHTTPClientService(), - \OC::$server->getConfig(), - \OC::$server->getLogger() - ); - $ocsdata = $ocsClient->getApplication($ocsid, \OCP\Util::getVersion()); - $ocsversion= (string) $ocsdata['version']; - $currentversion=OC_App::getAppVersion($app); - if (version_compare($ocsversion, $currentversion, '>')) { - return($ocsversion); - }else{ - return false; + $apps = $appFetcher->get(); + foreach($apps as $app) { + if($app['id'] === $appId) { + $currentVersion = OC_App::getAppVersion($appId); + $newestVersion = $app['releases'][0]['version']; + if (version_compare($newestVersion, $currentVersion, '>')) { + return $newestVersion; + } else { + return false; + } } - - }else{ - return false; } + return false; } /** @@ -466,7 +364,7 @@ class Installer { * * The function will check if the app is already downloaded in the apps repository */ - public static function isDownloaded( $name ) { + public function isDownloaded($name) { foreach(\OC::$APPSROOTS as $dir) { $dirToTest = $dir['path']; $dirToTest .= '/'; @@ -483,7 +381,7 @@ class Installer { /** * Removes an app - * @param string $name name of the application to remove + * @param string $appId ID of the application to remove * @return boolean * * @@ -494,12 +392,10 @@ class Installer { * The function will not delete preferences, tables and the configuration, * this has to be done by the function oc_app_uninstall(). */ - public static function removeApp($appId) { - - if(Installer::isDownloaded( $appId )) { - $appDir=OC_App::getInstallPath() . '/' . $appId; + public function removeApp($appId) { + if($this->isDownloaded( $appId )) { + $appDir = OC_App::getInstallPath() . '/' . $appId; OC_Helper::rmdirr($appDir); - return true; }else{ \OCP\Util::writeLog('core', 'can\'t remove app '.$appId.'. It is not installed.', \OCP\Util::ERROR); @@ -620,7 +516,7 @@ class Installer { } /** - * @param $basedir + * @param string $script */ private static function includeAppScript($script) { if ( file_exists($script) ){ diff --git a/lib/private/OCSClient.php b/lib/private/OCSClient.php deleted file mode 100644 index 76c0b136c06..00000000000 --- a/lib/private/OCSClient.php +++ /dev/null @@ -1,351 +0,0 @@ -<?php -/** - * @copyright Copyright (c) 2016, ownCloud, Inc. - * - * @author Bart Visscher <bartv@thisnet.nl> - * @author Brice Maron <brice@bmaron.net> - * @author Felix Moeller <mail@felixmoeller.de> - * @author Frank Karlitschek <frank@karlitschek.de> - * @author Jarrett <JetUni@users.noreply.github.com> - * @author Joas Schilling <coding@schilljs.com> - * @author Jörn Friedrich Dreyer <jfd@butonic.de> - * @author Kamil Domanski <kdomanski@kdemail.net> - * @author Lukas Reschke <lukas@statuscode.ch> - * @author Morris Jobke <hey@morrisjobke.de> - * @author Robin McCorkell <robin@mccorkell.me.uk> - * @author Sam Tuke <mail@samtuke.com> - * @author Thomas Müller <thomas.mueller@tmit.eu> - * - * @license AGPL-3.0 - * - * 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 <http://www.gnu.org/licenses/> - * - */ - -namespace OC; - -use OCP\Http\Client\IClientService; -use OCP\IConfig; -use OCP\ILogger; - -/** - * Class OCSClient is a class for communication with the ownCloud appstore - * - * @package OC - */ -class OCSClient { - /** @var IClientService */ - private $httpClientService; - /** @var IConfig */ - private $config; - /** @var ILogger */ - private $logger; - - /** - * @param IClientService $httpClientService - * @param IConfig $config - * @param ILogger $logger - */ - public function __construct(IClientService $httpClientService, - IConfig $config, - ILogger $logger) { - $this->httpClientService = $httpClientService; - $this->config = $config; - $this->logger = $logger; - } - - /** - * Returns whether the AppStore is enabled (i.e. because the AppStore is disabled for EE) - * - * @return bool - */ - public function isAppStoreEnabled() { - return $this->config->getSystemValue('appstoreenabled', true) === true; - } - - /** - * Get the url of the OCS AppStore server. - * - * @return string of the AppStore server - */ - private function getAppStoreUrl() { - return $this->config->getSystemValue('appstoreurl', 'https://api.owncloud.com/v1'); - } - - /** - * @param string $body - * @param string $action - * @return null|\SimpleXMLElement - */ - private function loadData($body, $action) { - $loadEntities = libxml_disable_entity_loader(true); - $data = @simplexml_load_string($body); - libxml_disable_entity_loader($loadEntities); - - if($data === false) { - libxml_clear_errors(); - $this->logger->error( - sprintf('Could not get %s, content was no valid XML', $action), - [ - 'app' => 'core', - ] - ); - return null; - } - - return $data; - } - - /** - * Get all the categories from the OCS server - * - * @param array $targetVersion The target ownCloud version - * @return array|null an array of category ids or null - * @note returns NULL if config value appstoreenabled is set to false - * This function returns a list of all the application categories on the OCS server - */ - public function getCategories(array $targetVersion) { - if (!$this->isAppStoreEnabled()) { - return null; - } - - $client = $this->httpClientService->newClient(); - try { - $response = $client->get( - $this->getAppStoreUrl() . '/content/categories', - [ - 'timeout' => 20, - 'query' => [ - 'version' => implode('x', $targetVersion), - ], - ] - ); - } catch(\Exception $e) { - $this->logger->error( - sprintf('Could not get categories: %s', $e->getMessage()), - [ - 'app' => 'core', - ] - ); - return null; - } - - $data = $this->loadData($response->getBody(), 'categories'); - if($data === null) { - return null; - } - - $tmp = $data->data; - $cats = []; - - foreach ($tmp->category as $value) { - $id = (int)$value->id; - $name = (string)$value->name; - $cats[$id] = $name; - } - - return $cats; - } - - /** - * Get all the applications from the OCS server - * @param array $categories - * @param int $page - * @param string $filter - * @param array $targetVersion The target ownCloud version - * @return array An array of application data - */ - public function getApplications(array $categories, $page, $filter, array $targetVersion) { - if (!$this->isAppStoreEnabled()) { - return []; - } - - $client = $this->httpClientService->newClient(); - try { - $response = $client->get( - $this->getAppStoreUrl() . '/content/data', - [ - 'timeout' => 20, - 'query' => [ - 'version' => implode('x', $targetVersion), - 'filter' => $filter, - 'categories' => implode('x', $categories), - 'sortmode' => 'new', - 'page' => $page, - 'pagesize' => 100, - 'approved' => $filter - ], - ] - ); - } catch(\Exception $e) { - $this->logger->error( - sprintf('Could not get applications: %s', $e->getMessage()), - [ - 'app' => 'core', - ] - ); - return []; - } - - $data = $this->loadData($response->getBody(), 'applications'); - if($data === null) { - return []; - } - - $tmp = $data->data->content; - $tmpCount = count($tmp); - - $apps = []; - for ($i = 0; $i < $tmpCount; $i++) { - $app = []; - $app['id'] = (string)$tmp[$i]->id; - $app['name'] = (string)$tmp[$i]->name; - $app['label'] = (string)$tmp[$i]->label; - $app['version'] = (string)$tmp[$i]->version; - $app['type'] = (string)$tmp[$i]->typeid; - $app['typename'] = (string)$tmp[$i]->typename; - $app['personid'] = (string)$tmp[$i]->personid; - $app['profilepage'] = (string)$tmp[$i]->profilepage; - $app['license'] = (string)$tmp[$i]->license; - $app['detailpage'] = (string)$tmp[$i]->detailpage; - $app['preview'] = (string)$tmp[$i]->smallpreviewpic1; - $app['preview-full'] = (string)$tmp[$i]->previewpic1; - $app['changed'] = strtotime($tmp[$i]->changed); - $app['description'] = (string)$tmp[$i]->description; - $app['score'] = (string)$tmp[$i]->score; - $app['downloads'] = (int)$tmp[$i]->downloads; - $app['level'] = (int)$tmp[$i]->approved; - - $apps[] = $app; - } - - return $apps; - } - - - /** - * Get an the applications from the OCS server - * - * @param string $id - * @param array $targetVersion The target ownCloud version - * @return array|null an array of application data or null - * - * This function returns an applications from the OCS server - */ - public function getApplication($id, array $targetVersion) { - if (!$this->isAppStoreEnabled()) { - return null; - } - - $client = $this->httpClientService->newClient(); - try { - $response = $client->get( - $this->getAppStoreUrl() . '/content/data/' . urlencode($id), - [ - 'timeout' => 20, - 'query' => [ - 'version' => implode('x', $targetVersion), - ], - ] - ); - } catch(\Exception $e) { - $this->logger->error( - sprintf('Could not get application: %s', $e->getMessage()), - [ - 'app' => 'core', - ] - ); - return null; - } - - $data = $this->loadData($response->getBody(), 'application'); - if($data === null) { - return null; - } - - $tmp = $data->data->content; - if (is_null($tmp)) { - \OCP\Util::writeLog('core', 'No update found at the ownCloud appstore for app ' . $id, \OCP\Util::DEBUG); - return null; - } - - $app = []; - $app['id'] = (int)$id; - $app['name'] = (string)$tmp->name; - $app['version'] = (string)$tmp->version; - $app['type'] = (string)$tmp->typeid; - $app['label'] = (string)$tmp->label; - $app['typename'] = (string)$tmp->typename; - $app['personid'] = (string)$tmp->personid; - $app['profilepage'] = (string)$tmp->profilepage; - $app['detailpage'] = (string)$tmp->detailpage; - $app['preview1'] = (string)$tmp->smallpreviewpic1; - $app['preview2'] = (string)$tmp->smallpreviewpic2; - $app['preview3'] = (string)$tmp->smallpreviewpic3; - $app['changed'] = strtotime($tmp->changed); - $app['description'] = (string)$tmp->description; - $app['detailpage'] = (string)$tmp->detailpage; - $app['score'] = (int)$tmp->score; - $app['level'] = (int)$tmp->approved; - - return $app; - } - - /** - * Get the download url for an application from the OCS server - * @param string $id - * @param array $targetVersion The target ownCloud version - * @return array|null an array of application data or null - */ - public function getApplicationDownload($id, array $targetVersion) { - if (!$this->isAppStoreEnabled()) { - return null; - } - $url = $this->getAppStoreUrl() . '/content/download/' . urlencode($id) . '/1'; - $client = $this->httpClientService->newClient(); - try { - $response = $client->get( - $url, - [ - 'timeout' => 20, - 'query' => [ - 'version' => implode('x', $targetVersion), - ], - ] - ); - } catch(\Exception $e) { - $this->logger->error( - sprintf('Could not get application download URL: %s', $e->getMessage()), - [ - 'app' => 'core', - ] - ); - return null; - } - - $data = $this->loadData($response->getBody(), 'application download URL'); - if($data === null) { - return null; - } - - $tmp = $data->data->content; - $app = []; - if (isset($tmp->downloadlink)) { - $app['downloadlink'] = (string)$tmp->downloadlink; - } else { - $app['downloadlink'] = ''; - } - return $app; - } - -} diff --git a/lib/private/Server.php b/lib/private/Server.php index 21ec311401d..3ea358498da 100644 --- a/lib/private/Server.php +++ b/lib/private/Server.php @@ -1,6 +1,7 @@ <?php /** * @copyright Copyright (c) 2016, ownCloud, Inc. + * @copyright Copyright (c) 2016, Lukas Reschke <lukas@statuscode.ch> * * @author Arthur Schiwon <blizzz@arthur-schiwon.de> * @author Bart Visscher <bartv@thisnet.nl> @@ -41,6 +42,8 @@ namespace OC; use bantu\IniGetWrapper\IniGetWrapper; +use OC\App\AppStore\Fetcher\AppFetcher; +use OC\App\AppStore\Fetcher\CategoryFetcher; use OC\AppFramework\Http\Request; use OC\AppFramework\Db\Db; use OC\AppFramework\Utility\TimeFactory; @@ -320,6 +323,21 @@ class Server extends ServerContainer implements IServerContainer { $this->registerService('AppHelper', function ($c) { return new \OC\AppHelper(); }); + $this->registerService('AppFetcher', function ($c) { + return new AppFetcher( + $this->getAppDataDir('appstore'), + $this->getHTTPClientService(), + $this->query(TimeFactory::class), + $this->getConfig() + ); + }); + $this->registerService('CategoryFetcher', function ($c) { + return new CategoryFetcher( + $this->getAppDataDir('appstore'), + $this->getHTTPClientService(), + $this->query(TimeFactory::class) + ); + }); $this->registerService('UserCache', function ($c) { return new Cache\File(); }); @@ -580,13 +598,6 @@ class Server extends ServerContainer implements IServerContainer { $c->getThemingDefaults() ); }); - $this->registerService('OcsClient', function (Server $c) { - return new OCSClient( - $this->getHTTPClientService(), - $this->getConfig(), - $this->getLogger() - ); - }); $this->registerService('LDAPProvider', function(Server $c) { $config = $c->getConfig(); $factoryClass = $config->getSystemValue('ldapProviderFactory', null); @@ -1008,6 +1019,13 @@ class Server extends ServerContainer implements IServerContainer { } /** + * @return AppFetcher + */ + public function getAppFetcher() { + return $this->query('AppFetcher'); + } + + /** * Returns an ICache instance. Since 8.1.0 it returns a fake cache. Use * getMemCacheFactory() instead. * diff --git a/lib/private/Updater.php b/lib/private/Updater.php index 646fc031a83..cd2934f7196 100644 --- a/lib/private/Updater.php +++ b/lib/private/Updater.php @@ -1,6 +1,7 @@ <?php /** * @copyright Copyright (c) 2016, ownCloud, Inc. + * @copyright Copyright (c) 2016, Lukas Reschke <lukas@statuscode.ch> * * @author Arthur Schiwon <blizzz@arthur-schiwon.de> * @author Frank Karlitschek <frank@karlitschek.de> @@ -426,11 +427,15 @@ class Updater extends BasicEmitter { private function upgradeAppStoreApps(array $disabledApps) { foreach($disabledApps as $app) { try { - if (Installer::isUpdateAvailable($app)) { - $ocsId = \OC::$server->getConfig()->getAppValue($app, 'ocsid', ''); - - $this->emit('\OC\Updater', 'upgradeAppStoreApp', array($app)); - Installer::updateAppByOCSId($ocsId); + $installer = new Installer( + \OC::$server->getAppFetcher(), + \OC::$server->getHTTPClientService(), + \OC::$server->getTempManager(), + $this->log + ); + if (Installer::isUpdateAvailable($app, \OC::$server->getAppFetcher())) { + $this->emit('\OC\Updater', 'upgradeAppStoreApp', [$app]); + $installer->updateAppstoreApp($app); } } catch (\Exception $ex) { $this->log->logException($ex, ['app' => 'core']); diff --git a/lib/private/legacy/app.php b/lib/private/legacy/app.php index d25534aa822..a89a4650c5d 100644 --- a/lib/private/legacy/app.php +++ b/lib/private/legacy/app.php @@ -1,6 +1,7 @@ <?php /** * @copyright Copyright (c) 2016, ownCloud, Inc. + * @copyright Copyright (c) 2016, Lukas Reschke <lukas@statuscode.ch> * * @author Arthur Schiwon <blizzz@arthur-schiwon.de> * @author Bart Visscher <bartv@thisnet.nl> @@ -326,24 +327,44 @@ class OC_App { /** * enables an app * - * @param mixed $app app + * @param string $appId * @param array $groups (optional) when set, only these groups will have access to the app * @throws \Exception * @return void * * This function set an app as enabled in appconfig. */ - public static function enable($app, $groups = null) { + public function enable($appId, + $groups = null) { self::$enabledAppsCache = []; // flush - if (!Installer::isInstalled($app)) { - $app = self::installApp($app); + $l = \OC::$server->getL10N('core'); + $config = \OC::$server->getConfig(); + + // Check if app is already downloaded + $installer = new Installer( + \OC::$server->getAppFetcher(), + \OC::$server->getHTTPClientService(), + \OC::$server->getTempManager(), + \OC::$server->getLogger() + ); + $isDownloaded = $installer->isDownloaded($appId); + + if(!$isDownloaded) { + $installer->downloadApp($appId); + } + + if (!Installer::isInstalled($appId)) { + $appId = self::installApp( + $appId, + $config, + $l + ); + $installer->installApp($appId); } else { // check for required dependencies - $config = \OC::$server->getConfig(); - $l = \OC::$server->getL10N('core'); - $info = self::getAppInfo($app); - + $info = self::getAppInfo($appId); self::checkAppDependencies($config, $l, $info); + $installer->installApp($appId); } $appManager = \OC::$server->getAppManager(); @@ -356,42 +377,21 @@ class OC_App { $groupsList[] = $groupManager->get($group); } } - $appManager->enableAppForGroups($app, $groupsList); + $appManager->enableAppForGroups($appId, $groupsList); } else { - $appManager->enableApp($app); + $appManager->enableApp($appId); } - $info = self::getAppInfo($app); + $info = self::getAppInfo($appId); if(isset($info['settings']) && is_array($info['settings'])) { - $appPath = self::getAppPath($app); - self::registerAutoloading($app, $appPath); + $appPath = self::getAppPath($appId); + self::registerAutoloading($appId, $appPath); \OC::$server->getSettingsManager()->setupSettings($info['settings']); } } /** * @param string $app - * @return int - */ - private static function downloadApp($app) { - $ocsClient = new OCSClient( - \OC::$server->getHTTPClientService(), - \OC::$server->getConfig(), - \OC::$server->getLogger() - ); - $appData = $ocsClient->getApplication($app, \OCP\Util::getVersion()); - $download = $ocsClient->getApplicationDownload($app, \OCP\Util::getVersion()); - if(isset($download['downloadlink']) and $download['downloadlink']!='') { - // Replace spaces in download link without encoding entire URL - $download['downloadlink'] = str_replace(' ', '%20', $download['downloadlink']); - $info = array('source' => 'http', 'href' => $download['downloadlink'], 'appdata' => $appData); - $app = Installer::installApp($info); - } - return $app; - } - - /** - * @param string $app * @return bool */ public static function removeApp($app) { @@ -399,7 +399,13 @@ class OC_App { return false; } - return Installer::removeApp($app); + $installer = new Installer( + \OC::$server->getAppFetcher(), + \OC::$server->getHTTPClientService(), + \OC::$server->getTempManager(), + \OC::$server->getLogger() + ); + return $installer->removeApp($app); } /** @@ -409,11 +415,6 @@ class OC_App { * @throws Exception */ public static function disable($app) { - // Convert OCS ID to regular application identifier - if(self::getInternalAppIdByOcs($app) !== false) { - $app = self::getInternalAppIdByOcs($app); - } - // flush self::$enabledAppsCache = array(); @@ -554,7 +555,7 @@ class OC_App { * @param string $appId * @return false|string */ - protected static function findAppInDirectories($appId) { + public static function findAppInDirectories($appId) { $sanitizedAppId = self::cleanAppId($appId); if($sanitizedAppId !== $appId) { return false; @@ -613,18 +614,6 @@ class OC_App { return false; } - - /** - * check if an app's directory is writable - * - * @param string $appId - * @return bool - */ - public static function isAppDirWritable($appId) { - $path = self::getAppPath($appId); - return ($path !== false) ? is_writable($path) : false; - } - /** * Get the path for the given app on the access * If the app is defined in multiple directories, the first one is taken. (false if not found) @@ -837,20 +826,11 @@ class OC_App { /** * List all apps, this is used in apps.php * - * @param bool $onlyLocal - * @param bool $includeUpdateInfo Should we check whether there is an update - * in the app store? - * @param OCSClient $ocsClient * @return array */ - public static function listAllApps($onlyLocal = false, - $includeUpdateInfo = true, - OCSClient $ocsClient) { + public function listAllApps() { $installedApps = OC_App::getAllApps(); - //TODO which apps do we want to blacklist and how do we integrate - // blacklisting with the multi apps folder feature? - //we don't want to show configuration for these $blacklist = \OC::$server->getAppManager()->getAlwaysEnabledApps(); $appList = array(); @@ -893,8 +873,6 @@ class OC_App { $info['removable'] = true; } - $info['update'] = ($includeUpdateInfo) ? Installer::isUpdateAvailable($app) : null; - $appPath = self::getAppPath($app); if($appPath !== false) { $appIcon = $appPath . '/img/' . $app . '.svg'; @@ -926,29 +904,8 @@ class OC_App { $appList[] = $info; } } - if ($onlyLocal) { - $remoteApps = []; - } else { - $remoteApps = OC_App::getAppstoreApps('approved', null, $ocsClient); - } - if ($remoteApps) { - // Remove duplicates - foreach ($appList as $app) { - foreach ($remoteApps AS $key => $remote) { - if ($app['name'] === $remote['name'] || - (isset($app['ocsid']) && - $app['ocsid'] === $remote['id']) - ) { - unset($remoteApps[$key]); - } - } - } - $combinedApps = array_merge($appList, $remoteApps); - } else { - $combinedApps = $appList; - } - return $combinedApps; + return $appList; } /** @@ -966,70 +923,6 @@ class OC_App { return false; } - /** - * Get a list of all apps on the appstore - * @param string $filter - * @param string|null $category - * @param OCSClient $ocsClient - * @return array|bool multi-dimensional array of apps. - * Keys: id, name, type, typename, personid, license, detailpage, preview, changed, description - */ - public static function getAppstoreApps($filter = 'approved', - $category = null, - OCSClient $ocsClient) { - $categories = [$category]; - - if (is_null($category)) { - $categoryNames = $ocsClient->getCategories(\OCP\Util::getVersion()); - if (is_array($categoryNames)) { - // Check that categories of apps were retrieved correctly - if (!$categories = array_keys($categoryNames)) { - return false; - } - } else { - return false; - } - } - - $page = 0; - $remoteApps = $ocsClient->getApplications($categories, $page, $filter, \OCP\Util::getVersion()); - $apps = []; - $i = 0; - $l = \OC::$server->getL10N('core'); - foreach ($remoteApps as $app) { - $potentialCleanId = self::getInternalAppIdByOcs($app['id']); - // enhance app info (for example the description) - $apps[$i] = OC_App::parseAppInfo($app); - $apps[$i]['author'] = $app['personid']; - $apps[$i]['ocs_id'] = $app['id']; - $apps[$i]['internal'] = 0; - $apps[$i]['active'] = ($potentialCleanId !== false) ? self::isEnabled($potentialCleanId) : false; - $apps[$i]['update'] = false; - $apps[$i]['groups'] = false; - $apps[$i]['score'] = $app['score']; - $apps[$i]['removable'] = false; - if ($app['label'] == 'recommended') { - $apps[$i]['internallabel'] = (string)$l->t('Recommended'); - $apps[$i]['internalclass'] = 'recommendedapp'; - } - - // Apps from the appstore are always assumed to be compatible with the - // the current release as the initial filtering is done on the appstore - $apps[$i]['dependencies']['owncloud']['@attributes']['min-version'] = implode('.', \OCP\Util::getVersion()); - $apps[$i]['dependencies']['owncloud']['@attributes']['max-version'] = implode('.', \OCP\Util::getVersion()); - - $i++; - } - - - - if (empty($apps)) { - return false; - } else { - return $apps; - } - } - public static function shouldUpgrade($app) { $versions = self::getAppVersions(); $currentVersion = OC_App::getAppVersion($app); @@ -1083,7 +976,9 @@ class OC_App { public static function isAppCompatible($ocVersion, $appInfo) { $requireMin = ''; $requireMax = ''; - if (isset($appInfo['dependencies']['owncloud']['@attributes']['min-version'])) { + if (isset($appInfo['dependencies']['nextcloud']['@attributes']['min-version'])) { + $requireMin = $appInfo['dependencies']['nextcloud']['@attributes']['min-version']; + } elseif (isset($appInfo['dependencies']['owncloud']['@attributes']['min-version'])) { $requireMin = $appInfo['dependencies']['owncloud']['@attributes']['min-version']; } else if (isset($appInfo['requiremin'])) { $requireMin = $appInfo['requiremin']; @@ -1091,7 +986,9 @@ class OC_App { $requireMin = $appInfo['require']; } - if (isset($appInfo['dependencies']['owncloud']['@attributes']['max-version'])) { + if (isset($appInfo['dependencies']['nextcloud']['@attributes']['max-version'])) { + $requireMax = $appInfo['dependencies']['nextcloud']['@attributes']['max-version']; + } elseif (isset($appInfo['dependencies']['owncloud']['@attributes']['max-version'])) { $requireMax = $appInfo['dependencies']['owncloud']['@attributes']['max-version']; } else if (isset($appInfo['requiremax'])) { $requireMax = $appInfo['requiremax']; @@ -1132,46 +1029,16 @@ class OC_App { /** * @param string $app + * @param \OCP\IConfig $config + * @param \OCP\IL10N $l * @return bool + * * @throws Exception if app is not compatible with this version of ownCloud * @throws Exception if no app-name was specified */ - public static function installApp($app) { - $appName = $app; // $app will be overwritten, preserve name for error logging - $l = \OC::$server->getL10N('core'); - $config = \OC::$server->getConfig(); - $ocsClient = new OCSClient( - \OC::$server->getHTTPClientService(), - $config, - \OC::$server->getLogger() - ); - $appData = $ocsClient->getApplication($app, \OCP\Util::getVersion()); - - // check if app is a shipped app or not. OCS apps have an integer as id, shipped apps use a string - if (!is_numeric($app)) { - $shippedVersion = self::getAppVersion($app); - if ($appData && version_compare($shippedVersion, $appData['version'], '<')) { - $app = self::downloadApp($app); - } else { - $app = Installer::installShippedApp($app); - } - } else { - // Maybe the app is already installed - compare the version in this - // case and use the local already installed one. - // FIXME: This is a horrible hack. I feel sad. The god of code cleanness may forgive me. - $internalAppId = self::getInternalAppIdByOcs($app); - if($internalAppId !== false) { - if($appData && version_compare(\OC_App::getAppVersion($internalAppId), $appData['version'], '<')) { - $app = self::downloadApp($app); - } else { - self::enable($internalAppId); - $app = $internalAppId; - } - } else { - $app = self::downloadApp($app); - } - } - + public function installApp($app, + \OCP\IConfig $config, + \OCP\IL10N $l) { if ($app !== false) { // check if the app is compatible with this version of ownCloud $info = self::getAppInfo($app); diff --git a/lib/private/legacy/util.php b/lib/private/legacy/util.php index e4c2caeafd7..5cd92eaa415 100644 --- a/lib/private/legacy/util.php +++ b/lib/private/legacy/util.php @@ -757,6 +757,7 @@ class OC_Util { 'simplexml_load_string' => 'SimpleXML', 'hash' => 'HASH Message Digest Framework', 'curl_init' => 'cURL', + 'openssl_verify' => 'OpenSSL', ], 'defined' => array( 'PDO::ATTR_DRIVER_NAME' => 'PDO' |