diff options
author | Lukas Reschke <lukas@statuscode.ch> | 2016-10-27 17:41:15 +0200 |
---|---|---|
committer | Lukas Reschke <lukas@statuscode.ch> | 2016-10-31 17:17:44 +0100 |
commit | 32cf661215fb3926789054a3953b465fc2665330 (patch) | |
tree | 4cf2865bef5856c59a1fdacb98208a14dfc1a128 | |
parent | 357a823457397d3e93ec8cd4dc01fb6859eb0049 (diff) | |
download | nextcloud-server-32cf661215fb3926789054a3953b465fc2665330.tar.gz nextcloud-server-32cf661215fb3926789054a3953b465fc2665330.zip |
Use new appstore API
This change introduces the new appstore API in Nextcloud.
Signed-off-by: Lukas Reschke <lukas@statuscode.ch>
30 files changed, 1243 insertions, 2193 deletions
diff --git a/apps/provisioning_api/lib/Controller/AppsController.php b/apps/provisioning_api/lib/Controller/AppsController.php index 3821fc343ad..7d11d92b55a 100644 --- a/apps/provisioning_api/lib/Controller/AppsController.php +++ b/apps/provisioning_api/lib/Controller/AppsController.php @@ -37,25 +37,20 @@ use OCP\IRequest; class AppsController extends OCSController { /** @var \OCP\App\IAppManager */ private $appManager; - /** @var OCSClient */ - private $ocsClient; /** * @param string $appName * @param IRequest $request * @param IAppManager $appManager - * @param OCSClient $ocsClient */ public function __construct( $appName, IRequest $request, - IAppManager $appManager, - OCSClient $ocsClient + IAppManager $appManager ) { parent::__construct($appName, $request); $this->appManager = $appManager; - $this->ocsClient = $ocsClient; } /** @@ -64,7 +59,7 @@ class AppsController extends OCSController { * @throws OCSException */ public function getApps($filter = null) { - $apps = OC_App::listAllApps(false, true, $this->ocsClient); + $apps = (new OC_App())->listAllApps(); $list = []; foreach($apps as $app) { $list[] = $app['id']; diff --git a/apps/provisioning_api/tests/Controller/AppsControllerTest.php b/apps/provisioning_api/tests/Controller/AppsControllerTest.php index 9ac4a8290e4..c891433258f 100644 --- a/apps/provisioning_api/tests/Controller/AppsControllerTest.php +++ b/apps/provisioning_api/tests/Controller/AppsControllerTest.php @@ -48,8 +48,6 @@ class AppsControllerTest extends \OCA\Provisioning_API\Tests\TestCase { private $api; /** @var IUserSession */ private $userSession; - /** @var OCSClient|\PHPUnit_Framework_MockObject_MockObject */ - private $ocsClient; protected function setUp() { parent::setUp(); @@ -57,9 +55,6 @@ class AppsControllerTest extends \OCA\Provisioning_API\Tests\TestCase { $this->appManager = \OC::$server->getAppManager(); $this->groupManager = \OC::$server->getGroupManager(); $this->userSession = \OC::$server->getUserSession(); - $this->ocsClient = $this->getMockBuilder('OC\OCSClient') - ->disableOriginalConstructor() - ->getMock(); $request = $this->getMockBuilder('OCP\IRequest') ->disableOriginalConstructor() @@ -68,8 +63,7 @@ class AppsControllerTest extends \OCA\Provisioning_API\Tests\TestCase { $this->api = new AppsController( 'provisioning_api', $request, - $this->appManager, - $this->ocsClient + $this->appManager ); } @@ -88,10 +82,6 @@ class AppsControllerTest extends \OCA\Provisioning_API\Tests\TestCase { } public function testGetApps() { - $this->ocsClient - ->expects($this->any()) - ->method($this->anything()) - ->will($this->returnValue(null)); $user = $this->generateUsers(); $this->groupManager->get('admin')->addUser($user); $this->userSession->setUser($user); @@ -99,7 +89,7 @@ class AppsControllerTest extends \OCA\Provisioning_API\Tests\TestCase { $result = $this->api->getApps(); $data = $result->getData(); - $this->assertEquals(count(\OC_App::listAllApps(false, true, $this->ocsClient)), count($data['apps'])); + $this->assertEquals(count((new \OC_App())->listAllApps()), count($data['apps'])); } public function testGetAppsEnabled() { @@ -109,13 +99,9 @@ class AppsControllerTest extends \OCA\Provisioning_API\Tests\TestCase { } public function testGetAppsDisabled() { - $this->ocsClient - ->expects($this->any()) - ->method($this->anything()) - ->will($this->returnValue(null)); $result = $this->api->getApps('disabled'); $data = $result->getData(); - $apps = \OC_App::listAllApps(false, true, $this->ocsClient); + $apps = (new \OC_App)->listAllApps(); $list = array(); foreach($apps as $app) { $list[] = $app['id']; diff --git a/config/config.sample.php b/config/config.sample.php index 7f4b3345642..fc52edbc778 100644 --- a/config/config.sample.php +++ b/config/config.sample.php @@ -674,20 +674,6 @@ $CONFIG = array( 'appstoreenabled' => true, /** - * The URL of the appstore to use. - */ -'appstoreurl' => 'https://api.owncloud.com/v1', - -/** - * Whether to show experimental apps in the appstore interface - * - * Experimental apps are not checked for security issues and are new or known - * to be unstable and under heavy development. Installing these can cause data - * loss or security breaches. - */ -'appstore.experimental.enabled' => false, - -/** * Use the ``apps_paths`` parameter to set the location of the Apps directory, * which should be scanned for available apps, and where user-specific apps * should be installed from the Apps store. The ``path`` defines the absolute 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..2a39b047a5a --- /dev/null +++ b/lib/private/App/AppStore/Fetcher/AppFetcher.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\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'; + $this->endpointUrl = sprintf( + 'https://apps.nextcloud.com/api/v1/platform/%s/apps.json', + substr(implode(\OC_Util::getVersion(), '.'), 0, 5) + ); + } +} 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..1058a8bc0fa --- /dev/null +++ b/lib/private/App/AppStore/Version/VersionParser.php @@ -0,0 +1,64 @@ +<?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 { + /** + * 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. + if (preg_match_all('/(?:>|<)(?:=|)[0-9.]+/', $versionSpec, $matches)) { + switch(count($matches[0])) { + case 1: + if(substr($matches[0][0], 0, 1) === '>') { + return new Version(substr($matches[0][0], 2), ''); + } else { + return new Version('', substr($matches[0][0], 2)); + } + break; + case 2: + return new Version(substr($matches[0][0], 2), substr($matches[0][1], 2)); + break; + default: + throw new \Exception('Version cannot be parsed'); + } + } + + throw new \Exception('Version cannot be parsed'); + } +} diff --git a/lib/private/Installer.php b/lib/private/Installer.php index 009df790585..02dbd670a0a 100644 --- a/lib/private/Installer.php +++ b/lib/private/Installer.php @@ -40,12 +40,18 @@ 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_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 @@ -81,49 +87,13 @@ class Installer { * 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']); + public function installApp($appId) { $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; - } - - //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); + $info = OC_App::getAppInfo($basedir.'/appinfo/info.xml', true); //install the database if(is_file($basedir.'/appinfo/database.xml')) { @@ -168,7 +138,7 @@ 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); } @@ -265,58 +235,148 @@ class Installer { } /** - * @param array $data - * @return array + * Downloads an app and puts it into the app directory + * + * @param string $appId + * @param AppFetcher $appFetcher + * @param IClientService $clientService + * @param ITempManager $tempManager + * @param ILogger $logger + * + * @return bool Whether the installation was successful or not * @throws \Exception */ - 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, + AppFetcher $appFetcher, + IClientService $clientService, + ITempManager $tempManager, + ILogger $logger) { + $appId = strtolower($appId); + + $apps = $appFetcher->get(); + foreach($apps as $app) { + if($app['id'] === $appId) { + // Verify if the certificate has been issued by the Nextcloud Code Authority CA + $x509 = new X509(); + $x509->loadCA(file_get_contents(__DIR__ . '/../../resources/codesigning/root.crt')); + $x509->loadX509($app['certificate']); + if($x509->validateSignature() !== true) { + $logger->error( + sprintf( + 'App with id %s has a certificate not issued by a trusted Code Signing Authority', + $appId + ), + [ + 'app' => 'core', + ] + ); + return false; + } - //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 is issued for the requested app id + $certInfo = openssl_x509_parse($app['certificate']); + if(!isset($certInfo['subject']['CN'])) { + $logger->error( + sprintf( + 'App with id %s has a cert with no CN', + $appId + ), + [ + 'app' => 'core', + ] + ); + return false; + } + if($certInfo['subject']['CN'] !== $appId) { + $logger->error( + sprintf( + 'App with id %s has a cert issued to %s', + $appId, + $certInfo['subject']['CN'] + ), + [ + 'app' => 'core', + ] + ); + return false; + } - //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))); - } + // Download the release + $tempFile = $tempManager->getTemporaryFile('.tar.gz'); + $client = $clientService->newClient(); + // FIXME: Proper way to determine what the latest release is + $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 = $tempManager->getTemporaryFolder(); + $archive = Archive::open($tempFile); + + if($archive) { + $archive->extract($extractDir); + + // Check if appinfo/info.xml has the same app ID as well + $loadEntities = libxml_disable_entity_loader(false); + $xml = simplexml_load_file($extractDir . '/' . $appId . '/appinfo/info.xml'); + libxml_disable_entity_loader($loadEntities); + if((string)$xml->id !== $appId) { + $logger->error( + sprintf( + 'App for id %s has a wrong app ID in info.xml: %s', + $appId, + (string)$xml->id + ), + [ + 'app' => 'core', + ] + ); + return false; + } - //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); + // Move to app folder + $baseDir = OC_App::getInstallPath().'/'.$appId; + //copy the app to the correct place + if(@mkdir($baseDir)) { + $extractDir .= '/' . $appId; + OC_Helper::copyr($extractDir, $baseDir); + } + OC_Helper::copyr($extractDir, $baseDir); + OC_Helper::rmdirr($extractDir); + return true; + } else { + $logger->error( + sprintf( + 'Could not extract app with ID %s to %s', + $appId, + $extractDir + ), + [ + 'app' => 'core', + ] + ); + return false; + } + } else { + // Signature does not match + $logger->error( + sprintf( + 'App with id %s has invalid signature', + $appId + ), + [ + 'app' => 'core', + ] + ); + } } - throw new \Exception($l->t("Failed to open archive when installing app")); } - return array( - $extractDir, - $path - ); + return false; } /** @@ -466,7 +526,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 .= '/'; 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..39905dcf7ce 100644 --- a/lib/private/Server.php +++ b/lib/private/Server.php @@ -580,13 +580,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); diff --git a/lib/private/legacy/app.php b/lib/private/legacy/app.php index d25534aa822..1db1ce74cdc 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,59 @@ 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(); + $isDownloaded = $installer->isDownloaded($appId); + + if(!$isDownloaded) { + $state = $installer->downloadApp( + $appId, + new \OC\App\AppStore\Fetcher\AppFetcher( + \OC::$server->getAppDataDir('appstore'), + \OC::$server->getHTTPClientService(), + new \OC\AppFramework\Utility\TimeFactory(), + $config + ), + \OC::$server->getHTTPClientService(), + \OC::$server->getTempManager(), + \OC::$server->getLogger() + ); + + if($state !== true) { + throw new \Exception( + sprintf( + 'Could not download app with id: %s', + $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 +392,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) { @@ -409,11 +424,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(); @@ -613,18 +623,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 +835,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 +882,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 +913,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 +932,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); @@ -1132,46 +1034,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/settings/Application.php b/settings/Application.php index dd237e40c9d..3dbf9acc524 100644 --- a/settings/Application.php +++ b/settings/Application.php @@ -30,7 +30,11 @@ namespace OC\Settings; +use OC\App\AppStore\Fetcher\AppFetcher; +use OC\App\AppStore\Fetcher\CategoryFetcher; +use OC\AppFramework\Utility\TimeFactory; use OC\Authentication\Token\IProvider; +use OC\Server; use OC\Settings\Middleware\SubadminMiddleware; use OCP\AppFramework\App; use OCP\IContainer; @@ -86,5 +90,24 @@ class Application extends App { $container->registerService(IManager::class, function (IContainer $c) { return $c->query('ServerContainer')->getSettingsManager(); }); + $container->registerService(AppFetcher::class, function (IContainer $c) { + /** @var Server $server */ + $server = $c->query('ServerContainer'); + return new AppFetcher( + $server->getAppDataDir('appstore'), + $server->getHTTPClientService(), + new TimeFactory(), + $server->getConfig() + ); + }); + $container->registerService(CategoryFetcher::class, function (IContainer $c) { + /** @var Server $server */ + $server = $c->query('ServerContainer'); + return new CategoryFetcher( + $server->getAppDataDir('appstore'), + $server->getHTTPClientService(), + new TimeFactory() + ); + }); } } diff --git a/settings/Controller/AppSettingsController.php b/settings/Controller/AppSettingsController.php index 2efd3b8a847..16d4780c5f9 100644 --- a/settings/Controller/AppSettingsController.php +++ b/settings/Controller/AppSettingsController.php @@ -1,6 +1,7 @@ <?php /** * @copyright Copyright (c) 2016, ownCloud, Inc. + * @copyright Copyright (c) 2016, Lukas Reschke <lukas@statuscode.ch> * * @author Christoph Wurst <christoph@owncloud.com> * @author Joas Schilling <coding@schilljs.com> @@ -26,19 +27,22 @@ namespace OC\Settings\Controller; +use OC\App\AppStore\Fetcher\AppFetcher; +use OC\App\AppStore\Fetcher\CategoryFetcher; +use OC\App\AppStore\Version\VersionParser; use OC\App\DependencyAnalyzer; use OC\App\Platform; -use OC\OCSClient; use OCP\App\IAppManager; use \OCP\AppFramework\Controller; use OCP\AppFramework\Http\ContentSecurityPolicy; -use OCP\AppFramework\Http\DataResponse; +use OCP\AppFramework\Http\JSONResponse; use OCP\AppFramework\Http\TemplateResponse; use OCP\ICacheFactory; use OCP\INavigationManager; use OCP\IRequest; use OCP\IL10N; use OCP\IConfig; +use OCP\L10N\IFactory; /** * @package OC\Settings\Controller @@ -57,8 +61,12 @@ class AppSettingsController extends Controller { private $navigationManager; /** @var IAppManager */ private $appManager; - /** @var OCSClient */ - private $ocsClient; + /** @var CategoryFetcher */ + private $categoryFetcher; + /** @var AppFetcher */ + private $appFetcher; + /** @var IFactory */ + private $l10nFactory; /** * @param string $appName @@ -68,7 +76,9 @@ class AppSettingsController extends Controller { * @param ICacheFactory $cache * @param INavigationManager $navigationManager * @param IAppManager $appManager - * @param OCSClient $ocsClient + * @param CategoryFetcher $categoryFetcher + * @param AppFetcher $appFetcher + * @param IFactory $l10nFactory */ public function __construct($appName, IRequest $request, @@ -77,69 +87,39 @@ class AppSettingsController extends Controller { ICacheFactory $cache, INavigationManager $navigationManager, IAppManager $appManager, - OCSClient $ocsClient) { + CategoryFetcher $categoryFetcher, + AppFetcher $appFetcher, + IFactory $l10nFactory) { parent::__construct($appName, $request); $this->l10n = $l10n; $this->config = $config; $this->cache = $cache->create($appName); $this->navigationManager = $navigationManager; $this->appManager = $appManager; - $this->ocsClient = $ocsClient; - } - - /** - * Enables or disables the display of experimental apps - * @param bool $state - * @return DataResponse - */ - public function changeExperimentalConfigState($state) { - $this->config->setSystemValue('appstore.experimental.enabled', $state); - $this->appManager->clearAppsCache(); - return new DataResponse(); - } - - /** - * @param string|int $category - * @return int - */ - protected function getCategory($category) { - if (is_string($category)) { - foreach ($this->listCategories() as $cat) { - if (isset($cat['ident']) && $cat['ident'] === $category) { - $category = (int) $cat['id']; - break; - } - } - - // Didn't find the category, falling back to enabled - if (is_string($category)) { - $category = self::CAT_ENABLED; - } - } - return (int) $category; + $this->categoryFetcher = $categoryFetcher; + $this->appFetcher = $appFetcher; + $this->l10nFactory = $l10nFactory; } /** * @NoCSRFRequired + * * @param string $category * @return TemplateResponse */ public function viewApps($category = '') { - $categoryId = $this->getCategory($category); - if ($categoryId === self::CAT_ENABLED) { - // Do not use an arbitrary input string, because we put the category in html + if ($category === '') { $category = 'enabled'; } $params = []; - $params['experimentalEnabled'] = $this->config->getSystemValue('appstore.experimental.enabled', false); $params['category'] = $category; $params['appstoreEnabled'] = $this->config->getSystemValue('appstoreenabled', true) === true; $this->navigationManager->setActiveEntry('core_apps'); $templateResponse = new TemplateResponse($this->appName, 'apps', $params, 'user'); $policy = new ContentSecurityPolicy(); - $policy->addAllowedImageDomain('https://apps.owncloud.com'); + $policy->addAllowedImageDomain('*'); $templateResponse->setContentSecurityPolicy($policy); return $templateResponse; @@ -147,139 +127,171 @@ class AppSettingsController extends Controller { /** * Get all available categories - * @return array + * + * @return JSONResponse */ public function listCategories() { + $currentLanguage = substr($this->l10nFactory->findLanguage(), 0, 2); - if(!is_null($this->cache->get('listCategories'))) { - return $this->cache->get('listCategories'); - } - $categories = [ + $formattedCategories = [ ['id' => self::CAT_ENABLED, 'ident' => 'enabled', 'displayName' => (string)$this->l10n->t('Enabled')], ['id' => self::CAT_DISABLED, 'ident' => 'disabled', 'displayName' => (string)$this->l10n->t('Not enabled')], ]; + $categories = $this->categoryFetcher->get(); + foreach($categories as $category) { + $formattedCategories[] = [ + 'id' => $category['id'], + 'ident' => $category['id'], + 'displayName' => isset($category['translations'][$currentLanguage]['name']) ? $category['translations'][$currentLanguage]['name'] : $category['translations']['en']['name'], + ]; + } - if($this->ocsClient->isAppStoreEnabled()) { - // apps from external repo via OCS - $ocs = $this->ocsClient->getCategories(\OCP\Util::getVersion()); - if ($ocs) { - foreach($ocs as $k => $v) { - $name = str_replace('ownCloud ', '', $v); - $ident = str_replace(' ', '-', urlencode(strtolower($name))); - $categories[] = [ - 'id' => $k, - 'ident' => $ident, - 'displayName' => $name, - ]; + return new JSONResponse($formattedCategories); + } + + /** + * Get all apps for a category + * + * @param string $requestedCategory + * @return array + */ + private function getAppsForCategory($requestedCategory) { + $versionParser = new VersionParser(); + $formattedApps = []; + $apps = $this->appFetcher->get(); + foreach($apps as $app) { + + // Skip all apps not in the requested category + $isInCategory = false; + foreach($app['categories'] as $category) { + if($category === $requestedCategory) { + $isInCategory = true; + } + } + if(!$isInCategory) { + continue; + } + + $nextCloudVersion = $versionParser->getVersion($app['releases'][0]['rawPlatformVersionSpec']); + $nextCloudVersionDependencies = []; + if($nextCloudVersion->getMinimumVersion() !== '') { + $nextCloudVersionDependencies['owncloud']['@attributes']['min-version'] = $nextCloudVersion->getMinimumVersion(); + } + if($nextCloudVersion->getMaximumVersion() !== '') { + $nextCloudVersionDependencies['owncloud']['@attributes']['max-version'] = $nextCloudVersion->getMaximumVersion(); + } + $phpVersion = $versionParser->getVersion($app['releases'][0]['rawPhpVersionSpec']); + $existsLocally = (\OC_App::getAppPath($app['id']) !== false) ? true : false; + $phpDependencies = []; + if($phpVersion->getMinimumVersion() !== '') { + $phpDependencies['php']['@attributes']['min-version'] = $phpVersion->getMinimumVersion(); + } + if($phpVersion->getMaximumVersion() !== '') { + $phpDependencies['php']['@attributes']['max-version'] = $phpVersion->getMaximumVersion(); + } + if(isset($app['releases'][0]['minIntSize'])) { + $phpDependencies['php']['@attributes']['min-int-size'] = $app['releases'][0]['minIntSize']; + } + $authors = ''; + foreach($app['authors'] as $key => $author) { + $authors .= $author['name']; + if($key !== count($app['authors']) - 1) { + $authors .= ', '; } } - } - $this->cache->set('listCategories', $categories, 3600); + $currentLanguage = substr(\OC::$server->getL10NFactory()->findLanguage(), 0, 2); + + $formattedApps[] = [ + 'id' => $app['id'], + 'name' => isset($app['translations'][$currentLanguage]['name']) ? $app['translations'][$currentLanguage]['name'] : $app['translations']['en']['name'], + 'description' => isset($app['translations'][$currentLanguage]['description']) ? $app['translations'][$currentLanguage]['description'] : $app['translations']['en']['description'], + 'license' => $app['releases'][0]['licenses'], + 'author' => $authors, + 'shipped' => false, + 'version' => $app['releases'][0]['version'], + 'default_enable' => '', + 'types' => [], + 'documentation' => [ + 'admin' => $app['adminDocs'], + 'user' => $app['userDocs'], + 'developer' => $app['developerDocs'] + ], + 'website' => $app['website'], + 'bugs' => $app['issueTracker'], + 'detailpage' => $app['website'], + 'dependencies' => array_merge( + $nextCloudVersionDependencies, + $phpDependencies + ), + 'level' => ($app['featured'] === true) ? 200 : 100, + 'missingMaxOwnCloudVersion' => false, + 'missingMinOwnCloudVersion' => false, + 'canInstall' => true, + 'preview' => $app['screenshots'][0]['url'], + 'score' => $app['ratingOverall'], + 'removable' => $existsLocally, + 'active' => $this->appManager->isEnabledForUser($app['id']), + 'needsDownload' => !$existsLocally, + ]; + } - return $categories; + return $formattedApps; } /** * Get all available apps in a category * * @param string $category - * @param bool $includeUpdateInfo Should we check whether there is an update - * in the app store? - * @return array + * @return JSONResponse */ - public function listApps($category = '', $includeUpdateInfo = true) { - $category = $this->getCategory($category); - $cacheName = 'listApps-' . $category . '-' . (int) $includeUpdateInfo; - - if(!is_null($this->cache->get($cacheName))) { - $apps = $this->cache->get($cacheName); - } else { - switch ($category) { - // installed apps - case 0: - $apps = $this->getInstalledApps($includeUpdateInfo); - usort($apps, function ($a, $b) { - $a = (string)$a['name']; - $b = (string)$b['name']; - if ($a === $b) { - return 0; - } - return ($a < $b) ? -1 : 1; - }); - $version = \OCP\Util::getVersion(); - foreach($apps as $key => $app) { - if(!array_key_exists('level', $app) && array_key_exists('ocsid', $app)) { - $remoteAppEntry = $this->ocsClient->getApplication($app['ocsid'], $version); - - if(is_array($remoteAppEntry) && array_key_exists('level', $remoteAppEntry)) { - $apps[$key]['level'] = $remoteAppEntry['level']; - } - } + public function listApps($category = '') { + $appClass = new \OC_App(); + + switch ($category) { + // installed apps + case 'enabled': + $apps = $appClass->listAllApps(); + $apps = array_filter($apps, function ($app) { + return $app['active']; + }); + usort($apps, function ($a, $b) { + $a = (string)$a['name']; + $b = (string)$b['name']; + if ($a === $b) { + return 0; } - break; - // not-installed apps - case 1: - $apps = \OC_App::listAllApps(true, $includeUpdateInfo, $this->ocsClient); - $apps = array_filter($apps, function ($app) { - return !$app['active']; - }); - $version = \OCP\Util::getVersion(); - foreach($apps as $key => $app) { - if(!array_key_exists('level', $app) && array_key_exists('ocsid', $app)) { - $remoteAppEntry = $this->ocsClient->getApplication($app['ocsid'], $version); - - if(is_array($remoteAppEntry) && array_key_exists('level', $remoteAppEntry)) { - $apps[$key]['level'] = $remoteAppEntry['level']; - } - } + return ($a < $b) ? -1 : 1; + }); + break; + // disabled apps + case 'disabled': + $apps = $appClass->listAllApps(); + $apps = array_filter($apps, function ($app) { + return !$app['active']; + }); + usort($apps, function ($a, $b) { + $a = (string)$a['name']; + $b = (string)$b['name']; + if ($a === $b) { + return 0; } - usort($apps, function ($a, $b) { - $a = (string)$a['name']; - $b = (string)$b['name']; - if ($a === $b) { - return 0; - } - return ($a < $b) ? -1 : 1; - }); - break; - default: - $filter = $this->config->getSystemValue('appstore.experimental.enabled', false) ? 'all' : 'approved'; - - $apps = \OC_App::getAppstoreApps($filter, $category, $this->ocsClient); - if (!$apps) { - $apps = array(); - } else { - // don't list installed apps - $installedApps = $this->getInstalledApps(false); - $installedApps = array_map(function ($app) { - if (isset($app['ocsid'])) { - return $app['ocsid']; - } - return $app['id']; - }, $installedApps); - $apps = array_filter($apps, function ($app) use ($installedApps) { - return !in_array($app['id'], $installedApps); - }); - - // show tooltip if app is downloaded from remote server - $inactiveApps = $this->getInactiveApps(); - foreach ($apps as &$app) { - $app['needsDownload'] = !in_array($app['id'], $inactiveApps); - } + return ($a < $b) ? -1 : 1; + }); + break; + default: + $apps = $this->getAppsForCategory($category); + + // sort by score + usort($apps, function ($a, $b) { + $a = (int)$a['score']; + $b = (int)$b['score']; + if ($a === $b) { + return 0; } - - // sort by score - usort($apps, function ($a, $b) { - $a = (int)$a['score']; - $b = (int)$b['score']; - if ($a === $b) { - return 0; - } - return ($a > $b) ? -1 : 1; - }); - break; - } + return ($a > $b) ? -1 : 1; + }); + break; } // fix groups to be an array @@ -310,40 +322,6 @@ class AppSettingsController extends Controller { return $app; }, $apps); - $this->cache->set($cacheName, $apps, 300); - - return ['apps' => $apps, 'status' => 'success']; - } - - /** - * @param bool $includeUpdateInfo Should we check whether there is an update - * in the app store? - * @return array - */ - private function getInstalledApps($includeUpdateInfo = true) { - $apps = \OC_App::listAllApps(true, $includeUpdateInfo, $this->ocsClient); - $apps = array_filter($apps, function ($app) { - return $app['active']; - }); - return $apps; - } - - /** - * @return array - */ - private function getInactiveApps() { - $inactiveApps = \OC_App::listAllApps(true, false, $this->ocsClient); - $inactiveApps = array_filter($inactiveApps, - function ($app) { - return !$app['active']; - }); - $inactiveApps = array_map(function($app) { - if (isset($app['ocsid'])) { - return $app['ocsid']; - } - return $app['id']; - }, $inactiveApps); - return $inactiveApps; + return new JSONResponse(['apps' => $apps, 'status' => 'success']); } - } diff --git a/settings/ajax/enableapp.php b/settings/ajax/enableapp.php index db4503f20e7..b378b3c918d 100644 --- a/settings/ajax/enableapp.php +++ b/settings/ajax/enableapp.php @@ -31,8 +31,10 @@ OCP\JSON::callCheck(); $groups = isset($_POST['groups']) ? (array)$_POST['groups'] : null; try { - $app = OC_App::cleanAppId((string)$_POST['appid']); - OC_App::enable($app, $groups); + $app = new OC_App(); + $appId = (string)$_POST['appid']; + $appId = OC_App::cleanAppId($appId); + $app->enable($appId, $groups); OC_JSON::success(['data' => ['update_required' => \OC_App::shouldUpgrade($app)]]); } catch (Exception $e) { \OCP\Util::writeLog('core', $e->getMessage(), \OCP\Util::ERROR); diff --git a/settings/ajax/installapp.php b/settings/ajax/installapp.php index 8831305e223..75f3fea83b7 100644 --- a/settings/ajax/installapp.php +++ b/settings/ajax/installapp.php @@ -29,14 +29,15 @@ if (!array_key_exists('appid', $_POST)) { exit; } +$app = new OC_App(); $appId = (string)$_POST['appid']; $appId = OC_App::cleanAppId($appId); - -$result = OC_App::installApp($appId); +$result = $app->installApp( + $appId, + \OC::$server->getConfig(), + \OC::$server->getL10N('core') +); if($result !== false) { - // FIXME: Clear the cache - move that into some sane helper method - \OC::$server->getMemCacheFactory()->create('settings')->remove('listApps-0'); - \OC::$server->getMemCacheFactory()->create('settings')->remove('listApps-1'); OC_JSON::success(array('data' => array('appid' => $appId))); } else { $l = \OC::$server->getL10N('settings'); diff --git a/settings/css/settings.css b/settings/css/settings.css index 0dadf401c04..fe0e40cb273 100644 --- a/settings/css/settings.css +++ b/settings/css/settings.css @@ -415,17 +415,6 @@ span.version { background-position: 5px center; padding-left: 25px; } -.app-level .approved { - border-color: #0082c9; -} -.app-level .experimental { - background-color: #ce3702; - border-color: #ce3702; - color: #fff; -} -.apps-experimental { - color: #ce3702; -} .app-score { position: relative; diff --git a/settings/js/apps.js b/settings/js/apps.js index 5fc366c4921..ecd7543c8ce 100644 --- a/settings/js/apps.js +++ b/settings/js/apps.js @@ -2,7 +2,7 @@ Handlebars.registerHelper('score', function() { if(this.score) { - var score = Math.round( this.score / 10 ); + var score = Math.round( this.score * 10 ); var imageName = 'rating/s' + score + '.svg'; return new Handlebars.SafeString('<img src="' + OC.imagePath('core', imageName) + '">'); @@ -13,10 +13,6 @@ Handlebars.registerHelper('level', function() { if(typeof this.level !== 'undefined') { if(this.level === 200) { return new Handlebars.SafeString('<span class="official icon-checkmark">' + t('settings', 'Official') + '</span>'); - } else if(this.level === 100) { - return new Handlebars.SafeString('<span class="approved">' + t('settings', 'Approved') + '</span>'); - } else { - return new Handlebars.SafeString('<span class="experimental">' + t('settings', 'Experimental') + '</span>'); } } }); diff --git a/settings/routes.php b/settings/routes.php index 64c4e549681..829474ce2bb 100644 --- a/settings/routes.php +++ b/settings/routes.php @@ -49,7 +49,6 @@ $application->registerRoutes($this, [ ['name' => 'AppSettings#listCategories', 'url' => '/settings/apps/categories', 'verb' => 'GET'], ['name' => 'AppSettings#viewApps', 'url' => '/settings/apps', 'verb' => 'GET'], ['name' => 'AppSettings#listApps', 'url' => '/settings/apps/list', 'verb' => 'GET'], - ['name' => 'AppSettings#changeExperimentalConfigState', 'url' => '/settings/apps/experimental', 'verb' => 'POST'], ['name' => 'SecuritySettings#trustedDomains', 'url' => '/settings/admin/security/trustedDomains', 'verb' => 'POST'], ['name' => 'Users#setDisplayName', 'url' => '/settings/users/{username}/displayName', 'verb' => 'POST'], ['name' => 'Users#setMailAddress', 'url' => '/settings/users/{id}/mailAddress', 'verb' => 'PUT'], diff --git a/settings/templates/apps.php b/settings/templates/apps.php index 46fd5bd0e40..36064f0981c 100644 --- a/settings/templates/apps.php +++ b/settings/templates/apps.php @@ -30,15 +30,6 @@ script( </script> <script id="app-template" type="text/x-handlebars"> - {{#if firstExperimental}} - <div class="section apps-experimental"> - <h2><?php p($l->t('Experimental applications ahead')) ?></h2> - <p> - <?php p($l->t('Experimental apps are not checked for security issues, new or known to be unstable and under heavy development. Installing them can cause data loss or security breaches.')) ?> - </p> - </div> - {{/if}} - <div class="section" id="app-{{id}}"> {{#if preview}} <div class="app-image{{#if previewAsIcon}} app-image-icon{{/if}} hidden"> @@ -160,16 +151,6 @@ script( <div id="app-settings-header"> <button class="settings-button" data-apps-slide-toggle="#app-settings-content"></button> </div> - - <div id="app-settings-content" class="apps-experimental"> - <input type="checkbox" id="enable-experimental-apps" <?php if($_['experimentalEnabled']) { print_unescaped('checked="checked"'); }?> class="checkbox"> - <label for="enable-experimental-apps"><?php p($l->t('Enable experimental apps')) ?></label> - <p> - <small> - <?php p($l->t('Experimental apps are not checked for security issues, new or known to be unstable and under heavy development. Installing them can cause data loss or security breaches.')) ?> - </small> - </p> - </div> </div> </div> <div id="app-content"> diff --git a/tests/Settings/Controller/AppSettingsControllerTest.php b/tests/Settings/Controller/AppSettingsControllerTest.php index 9dcc55e135b..fc9d04fcfe7 100644 --- a/tests/Settings/Controller/AppSettingsControllerTest.php +++ b/tests/Settings/Controller/AppSettingsControllerTest.php @@ -2,6 +2,7 @@ /** * @author Lukas Reschke <lukas@owncloud.com> * + * @copyright Copyright (c) 2016, Lukas Reschke <lukas@statuscode.ch> * @copyright Copyright (c) 2015, ownCloud, Inc. * @license AGPL-3.0 * @@ -21,10 +22,13 @@ namespace Tests\Settings\Controller; +use OC\App\AppStore\Fetcher\AppFetcher; +use OC\App\AppStore\Fetcher\CategoryFetcher; use OC\Settings\Controller\AppSettingsController; use OCP\AppFramework\Http\ContentSecurityPolicy; -use OCP\AppFramework\Http\DataResponse; +use OCP\AppFramework\Http\JSONResponse; use OCP\AppFramework\Http\TemplateResponse; +use OCP\ICacheFactory; use Test\TestCase; use OCP\IRequest; use OCP\IL10N; @@ -32,7 +36,6 @@ use OCP\IConfig; use OCP\ICache; use OCP\INavigationManager; use OCP\App\IAppManager; -use OC\OCSClient; /** * Class AppSettingsControllerTest @@ -42,49 +45,43 @@ use OC\OCSClient; class AppSettingsControllerTest extends TestCase { /** @var AppSettingsController */ private $appSettingsController; - /** @var IRequest */ + /** @var IRequest|\PHPUnit_Framework_MockObject_MockObject */ private $request; - /** @var IL10N */ + /** @var IL10N|\PHPUnit_Framework_MockObject_MockObject */ private $l10n; - /** @var IConfig */ + /** @var IConfig|\PHPUnit_Framework_MockObject_MockObject */ private $config; - /** @var ICache */ + /** @var ICache|\PHPUnit_Framework_MockObject_MockObject */ private $cache; - /** @var INavigationManager */ + /** @var INavigationManager|\PHPUnit_Framework_MockObject_MockObject */ private $navigationManager; - /** @var IAppManager */ + /** @var IAppManager|\PHPUnit_Framework_MockObject_MockObject */ private $appManager; - /** @var OCSClient */ - private $ocsClient; + /** @var CategoryFetcher|\PHPUnit_Framework_MockObject_MockObject */ + private $categoryFetcher; + /** @var AppFetcher|\PHPUnit_Framework_MockObject_MockObject */ + private $appFetcher; public function setUp() { parent::setUp(); - $this->request = $this->getMockBuilder('\OCP\IRequest') - ->disableOriginalConstructor()->getMock(); - $this->l10n = $this->getMockBuilder('\OCP\IL10N') - ->disableOriginalConstructor()->getMock(); + $this->request = $this->createMock(IRequest::class); + $this->l10n = $this->createMock(IL10N::class); $this->l10n->expects($this->any()) ->method('t') ->will($this->returnArgument(0)); - $this->config = $this->getMockBuilder('\OCP\IConfig') - ->disableOriginalConstructor()->getMock(); - $cacheFactory = $this->getMockBuilder('\OCP\ICacheFactory') - ->disableOriginalConstructor()->getMock(); - $this->cache = $this->getMockBuilder('\OCP\ICache') - ->disableOriginalConstructor()->getMock(); + $this->config = $this->createMock(IConfig::class); + $cacheFactory = $this->createMock(ICacheFactory::class); + $this->cache = $this->createMock(ICache::class); $cacheFactory ->expects($this->once()) ->method('create') ->with('settings') ->will($this->returnValue($this->cache)); - - $this->navigationManager = $this->getMockBuilder('\OCP\INavigationManager') - ->disableOriginalConstructor()->getMock(); - $this->appManager = $this->getMockBuilder('\OCP\App\IAppManager') - ->disableOriginalConstructor()->getMock(); - $this->ocsClient = $this->getMockBuilder('\OC\OCSClient') - ->disableOriginalConstructor()->getMock(); + $this->navigationManager = $this->createMock(INavigationManager::class); + $this->appManager = $this->createMock(IAppManager::class); + $this->categoryFetcher = $this->createMock(CategoryFetcher::class); + $this->appFetcher = $this->createMock(AppFetcher::class); $this->appSettingsController = new AppSettingsController( 'settings', @@ -94,43 +91,13 @@ class AppSettingsControllerTest extends TestCase { $cacheFactory, $this->navigationManager, $this->appManager, - $this->ocsClient + $this->categoryFetcher, + $this->appFetcher ); } - public function testChangeExperimentalConfigStateTrue() { - $this->config - ->expects($this->once()) - ->method('setSystemValue') - ->with('appstore.experimental.enabled', true); - $this->appManager - ->expects($this->once()) - ->method('clearAppsCache'); - $this->assertEquals(new DataResponse(), $this->appSettingsController->changeExperimentalConfigState(true)); - } - - public function testChangeExperimentalConfigStateFalse() { - $this->config - ->expects($this->once()) - ->method('setSystemValue') - ->with('appstore.experimental.enabled', false); - $this->appManager - ->expects($this->once()) - ->method('clearAppsCache'); - $this->assertEquals(new DataResponse(), $this->appSettingsController->changeExperimentalConfigState(false)); - } - - public function testListCategoriesCached() { - $this->cache - ->expects($this->exactly(2)) - ->method('get') - ->with('listCategories') - ->will($this->returnValue(['CachedArray'])); - $this->assertSame(['CachedArray'], $this->appSettingsController->listCategories()); - } - - public function testListCategoriesNotCachedWithoutAppStore() { - $expected = [ + public function testListCategories() { + $expected = new JSONResponse([ [ 'id' => 0, 'ident' => 'enabled', @@ -141,115 +108,69 @@ class AppSettingsControllerTest extends TestCase { 'ident' => 'disabled', 'displayName' => 'Not enabled', ], - ]; - $this->cache - ->expects($this->once()) - ->method('get') - ->with('listCategories') - ->will($this->returnValue(null)); - $this->cache - ->expects($this->once()) - ->method('set') - ->with('listCategories', $expected, 3600); - - - $this->assertSame($expected, $this->appSettingsController->listCategories()); - } - - public function testListCategoriesNotCachedWithAppStore() { - $expected = [ [ - 'id' => 0, - 'ident' => 'enabled', - 'displayName' => 'Enabled', + 'id' => 'auth', + 'ident' => 'auth', + 'displayName' => 'Authentication & authorization', ], [ - 'id' => 1, - 'ident' => 'disabled', - 'displayName' => 'Not enabled', + 'id' => 'customization', + 'ident' => 'customization', + 'displayName' => 'Customization', ], [ - 'id' => 0, - 'ident' => 'tools', - 'displayName' => 'Tools', + 'id' => 'files', + 'ident' => 'files', + 'displayName' => 'Files', ], [ - 'id' => 1, - 'ident' => 'games', - 'displayName' => 'Games', + 'id' => 'integration', + 'ident' => 'integration', + 'displayName' => 'Integration', ], [ - 'id' => 2, - 'ident' => 'productivity', - 'displayName' => 'Productivity', + 'id' => 'monitoring', + 'ident' => 'monitoring', + 'displayName' => 'Monitoring', ], [ - 'id' => 3, + 'id' => 'multimedia', 'ident' => 'multimedia', 'displayName' => 'Multimedia', ], - ]; + [ + 'id' => 'office', + 'ident' => 'office', + 'displayName' => 'Office & text', + ], + [ + 'id' => 'organization', + 'ident' => 'organization', + 'displayName' => 'Organization', + ], + [ + 'id' => 'social', + 'ident' => 'social', + 'displayName' => 'Social & communication', + ], + [ + 'id' => 'tools', + 'ident' => 'tools', + 'displayName' => 'Tools', + ], + ]); - $this->cache + $this->categoryFetcher ->expects($this->once()) ->method('get') - ->with('listCategories') - ->will($this->returnValue(null)); - $this->cache - ->expects($this->once()) - ->method('set') - ->with('listCategories', $expected, 3600); + ->willReturn(json_decode('[{"id":"auth","translations":{"cs":{"name":"Autentizace & autorizace","description":"Aplikace poskytující služby dodatečného ověření nebo přihlášení"},"hu":{"name":"Azonosítás és hitelesítés","description":"Apps that provide additional authentication or authorization services"},"de":{"name":"Authentifizierung & Authorisierung","description":"Apps die zusätzliche Autentifizierungs- oder Autorisierungsdienste bereitstellen"},"nl":{"name":"Authenticatie & authorisatie","description":"Apps die aanvullende authenticatie- en autorisatiediensten bieden"},"nb":{"name":"Pålogging og tilgangsstyring","description":"Apper for å tilby ekstra pålogging eller tilgangsstyring"},"it":{"name":"Autenticazione e autorizzazione","description":"Apps that provide additional authentication or authorization services"},"fr":{"name":"Authentification et autorisations","description":"Applications qui fournissent des services d\'authentification ou d\'autorisations additionnels."},"ru":{"name":"Аутентификация и авторизация","description":"Apps that provide additional authentication or authorization services"},"en":{"name":"Authentication & authorization","description":"Apps that provide additional authentication or authorization services"}}},{"id":"customization","translations":{"cs":{"name":"Přizpůsobení","description":"Motivy a aplikace měnící rozvržení a uživatelské rozhraní"},"it":{"name":"Personalizzazione","description":"Applicazioni di temi, modifiche della disposizione e UX"},"de":{"name":"Anpassung","description":"Apps zur Änderung von Themen, Layout und Benutzererfahrung"},"hu":{"name":"Személyre szabás","description":"Témák, elrendezések felhasználói felület módosító alkalmazások"},"nl":{"name":"Maatwerk","description":"Thema\'s, layout en UX aanpassingsapps"},"nb":{"name":"Tilpasning","description":"Apper for å endre Tema, utseende og brukeropplevelse"},"fr":{"name":"Personalisation","description":"Thèmes, apparence et applications modifiant l\'expérience utilisateur"},"ru":{"name":"Настройка","description":"Themes, layout and UX change apps"},"en":{"name":"Customization","description":"Themes, layout and UX change apps"}}},{"id":"files","translations":{"cs":{"name":"Soubory","description":"Aplikace rozšiřující správu souborů nebo aplikaci Soubory"},"it":{"name":"File","description":"Applicazioni di gestione dei file ed estensione dell\'applicazione FIle"},"de":{"name":"Dateien","description":"Dateimanagement sowie Erweiterungs-Apps für die Dateien-App"},"hu":{"name":"Fájlok","description":"Fájl kezelő és kiegészítő alkalmazások"},"nl":{"name":"Bestanden","description":"Bestandebeheer en uitbreidingen van bestand apps"},"nb":{"name":"Filer","description":"Apper for filhåndtering og filer"},"fr":{"name":"Fichiers","description":"Applications de gestion de fichiers et extensions à l\'application Fichiers"},"ru":{"name":"Файлы","description":"Расширение: файлы и управление файлами"},"en":{"name":"Files","description":"File management and Files app extension apps"}}},{"id":"integration","translations":{"it":{"name":"Integrazione","description":"Applicazioni che collegano Nextcloud con altri servizi e piattaforme"},"hu":{"name":"Integráció","description":"Apps that connect Nextcloud with other services and platforms"},"nl":{"name":"Integratie","description":"Apps die Nextcloud verbinden met andere services en platformen"},"nb":{"name":"Integrasjon","description":"Apper som kobler Nextcloud med andre tjenester og plattformer"},"de":{"name":"Integration","description":"Apps die Nextcloud mit anderen Diensten und Plattformen verbinden"},"cs":{"name":"Propojení","description":"Aplikace propojující NextCloud s dalšími službami a platformami"},"fr":{"name":"Intégration","description":"Applications qui connectent Nextcloud avec d\'autres services et plateformes"},"ru":{"name":"Интеграция","description":"Приложения, соединяющие Nextcloud с другими службами и платформами"},"en":{"name":"Integration","description":"Apps that connect Nextcloud with other services and platforms"}}},{"id":"monitoring","translations":{"nb":{"name":"Overvåking","description":"Apper for statistikk, systemdiagnose og aktivitet"},"it":{"name":"Monitoraggio","description":"Applicazioni di statistiche, diagnostica di sistema e attività"},"de":{"name":"Überwachung","description":"Datenstatistiken-, Systemdiagnose- und Aktivitäten-Apps"},"hu":{"name":"Megfigyelés","description":"Data statistics, system diagnostics and activity apps"},"nl":{"name":"Monitoren","description":"Gegevensstatistiek, systeem diagnose en activiteit apps"},"cs":{"name":"Kontrola","description":"Datové statistiky, diagnózy systému a aktivity aplikací"},"fr":{"name":"Surveillance","description":"Applications de statistiques sur les données, de diagnostics systèmes et d\'activité."},"ru":{"name":"Мониторинг","description":"Статистика данных, диагностика системы и активность приложений"},"en":{"name":"Monitoring","description":"Data statistics, system diagnostics and activity apps"}}},{"id":"multimedia","translations":{"nb":{"name":"Multimedia","description":"Apper for lyd, film og bilde"},"it":{"name":"Multimedia","description":"Applicazioni per audio, video e immagini"},"de":{"name":"Multimedia","description":"Audio-, Video- und Bilder-Apps"},"hu":{"name":"Multimédia","description":"Hang, videó és kép alkalmazások"},"nl":{"name":"Multimedia","description":"Audio, video en afbeelding apps"},"en":{"name":"Multimedia","description":"Audio, video and picture apps"},"cs":{"name":"Multimédia","description":"Aplikace audia, videa a obrázků"},"fr":{"name":"Multimédia","description":"Applications audio, vidéo et image"},"ru":{"name":"Мультимедиа","description":"Приложение аудио, видео и изображения"}}},{"id":"office","translations":{"nb":{"name":"Kontorstøtte og tekst","description":"Apper for Kontorstøtte og tekstbehandling"},"it":{"name":"Ufficio e testo","description":"Applicazione per ufficio ed elaborazione di testi"},"de":{"name":"Büro & Text","description":"Büro- und Textverarbeitungs-Apps"},"hu":{"name":"Iroda és szöveg","description":"Irodai és szöveg feldolgozó alkalmazások"},"nl":{"name":"Office & tekst","description":"Office en tekstverwerkingsapps"},"cs":{"name":"Kancelář a text","description":"Aplikace pro kancelář a zpracování textu"},"fr":{"name":"Bureautique & texte","description":"Applications de bureautique et de traitement de texte"},"en":{"name":"Office & text","description":"Office and text processing apps"}}},{"id":"organization","translations":{"nb":{"name":"Organisering","description":"Apper for tidsstyring, oppgaveliste og kalender"},"it":{"name":"Organizzazione","description":"Applicazioni di gestione del tempo, elenco delle cose da fare e calendario"},"hu":{"name":"Szervezet","description":"Időbeosztás, teendő lista és naptár alkalmazások"},"nl":{"name":"Organisatie","description":"Tijdmanagement, takenlijsten en agenda apps"},"cs":{"name":"Organizace","description":"Aplikace pro správu času, plánování a kalendáře"},"de":{"name":"Organisation","description":"Time management, Todo list and calendar apps"},"fr":{"name":"Organisation","description":"Applications de gestion du temps, de listes de tâches et d\'agendas"},"ru":{"name":"Организация","description":"Приложения по управлению временем, список задач и календарь"},"en":{"name":"Organization","description":"Time management, Todo list and calendar apps"}}},{"id":"social","translations":{"nb":{"name":"Sosialt og kommunikasjon","description":"Apper for meldinger, kontakthåndtering og sosiale medier"},"it":{"name":"Sociale e comunicazione","description":"Applicazioni di messaggistica, gestione dei contatti e reti sociali"},"de":{"name":"Kommunikation","description":"Nachrichten-, Kontaktverwaltungs- und Social-Media-Apps"},"hu":{"name":"Közösségi és kommunikáció","description":"Üzenetküldő, kapcsolat kezelő és közösségi média alkalmazások"},"nl":{"name":"Sociaal & communicatie","description":"Messaging, contactbeheer en social media apps"},"cs":{"name":"Sociální sítě a komunikace","description":"Aplikace pro zasílání zpráv, správu kontaktů a sociální sítě"},"fr":{"name":"Social & communication","description":"Applications de messagerie, de gestion de contacts et de réseaux sociaux"},"ru":{"name":"Социальное и связь","description":"Общение, управление контактами и социальное медиа-приложение"},"en":{"name":"Social & communication","description":"Messaging, contact management and social media apps"}}},{"id":"tools","translations":{"nb":{"name":"Verktøy","description":"Alt annet"},"it":{"name":"Strumenti","description":"Tutto il resto"},"hu":{"name":"Eszközök","description":"Minden más"},"nl":{"name":"Tools","description":"De rest"},"de":{"name":"Werkzeuge","description":"Alles Andere"},"en":{"name":"Tools","description":"Everything else"},"cs":{"name":"Nástroje","description":"Vše ostatní"},"fr":{"name":"Outils","description":"Tout le reste"},"ru":{"name":"Приложения","description":"Что-то еще"}}}]', true)); - $this->ocsClient - ->expects($this->once()) - ->method('isAppStoreEnabled') - ->will($this->returnValue(true)); - $this->ocsClient - ->expects($this->once()) - ->method('getCategories') - ->will($this->returnValue( - [ - 'ownCloud Tools', - 'Games', - 'ownCloud Productivity', - 'Multimedia', - ] - )); - - $this->assertSame($expected, $this->appSettingsController->listCategories()); + $this->assertEquals($expected, $this->appSettingsController->listCategories()); } public function testViewApps() { $this->config - ->expects($this->at(0)) - ->method('getSystemValue') - ->with('appstore.experimental.enabled', false); - $this->config - ->expects($this->at(1)) - ->method('getSystemValue') - ->with('appstoreenabled', true) - ->will($this->returnValue(true)); - $this->navigationManager ->expects($this->once()) - ->method('setActiveEntry') - ->with('core_apps'); - - $policy = new ContentSecurityPolicy(); - $policy->addAllowedImageDomain('https://apps.owncloud.com'); - - $expected = new TemplateResponse('settings', 'apps', ['experimentalEnabled' => false, 'category' => 'enabled', 'appstoreEnabled' => true], 'user'); - $expected->setContentSecurityPolicy($policy); - - $this->assertEquals($expected, $this->appSettingsController->viewApps()); - } - - public function testViewAppsNotEnabled() { - $this->config - ->expects($this->at(0)) - ->method('getSystemValue') - ->with('appstore.experimental.enabled', false); - $this->config - ->expects($this->at(1)) ->method('getSystemValue') ->with('appstoreenabled', true) ->will($this->returnValue(true)); @@ -259,21 +180,17 @@ class AppSettingsControllerTest extends TestCase { ->with('core_apps'); $policy = new ContentSecurityPolicy(); - $policy->addAllowedImageDomain('https://apps.owncloud.com'); + $policy->addAllowedImageDomain('*'); - $expected = new TemplateResponse('settings', 'apps', ['experimentalEnabled' => false, 'category' => 'disabled', 'appstoreEnabled' => true], 'user'); + $expected = new TemplateResponse('settings', 'apps', ['category' => 'enabled', 'appstoreEnabled' => true], 'user'); $expected->setContentSecurityPolicy($policy); - $this->assertEquals($expected, $this->appSettingsController->viewApps('disabled')); + $this->assertEquals($expected, $this->appSettingsController->viewApps()); } public function testViewAppsAppstoreNotEnabled() { $this->config - ->expects($this->at(0)) - ->method('getSystemValue') - ->with('appstore.experimental.enabled', false); - $this->config - ->expects($this->at(1)) + ->expects($this->once()) ->method('getSystemValue') ->with('appstoreenabled', true) ->will($this->returnValue(false)); @@ -283,9 +200,9 @@ class AppSettingsControllerTest extends TestCase { ->with('core_apps'); $policy = new ContentSecurityPolicy(); - $policy->addAllowedImageDomain('https://apps.owncloud.com'); + $policy->addAllowedImageDomain('*'); - $expected = new TemplateResponse('settings', 'apps', ['experimentalEnabled' => false, 'category' => 'enabled', 'appstoreEnabled' => false], 'user'); + $expected = new TemplateResponse('settings', 'apps', ['category' => 'enabled', 'appstoreEnabled' => false], 'user'); $expected->setContentSecurityPolicy($policy); $this->assertEquals($expected, $this->appSettingsController->viewApps()); diff --git a/tests/lib/App/AppStore/Fetcher/AppFetcherTest.php b/tests/lib/App/AppStore/Fetcher/AppFetcherTest.php new file mode 100644 index 00000000000..3b0418a7eba --- /dev/null +++ b/tests/lib/App/AppStore/Fetcher/AppFetcherTest.php @@ -0,0 +1,39 @@ +<?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 Test\App\AppStore\Fetcher; + +use OC\App\AppStore\Fetcher\AppFetcher; + +class AppFetcherTest extends FetcherBase { + public function setUp() { + parent::setUp(); + $this->fileName = 'apps.json'; + $this->endpoint = 'https://apps.nextcloud.com/api/v1/platform/9.2.0/apps.json'; + + $this->fetcher = new AppFetcher( + $this->appData, + $this->clientService, + $this->timeFactory, + $this->config + ); + } +} diff --git a/tests/lib/App/AppStore/Fetcher/CategoryFetcherTest.php b/tests/lib/App/AppStore/Fetcher/CategoryFetcherTest.php new file mode 100644 index 00000000000..db4354119a0 --- /dev/null +++ b/tests/lib/App/AppStore/Fetcher/CategoryFetcherTest.php @@ -0,0 +1,38 @@ +<?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 Test\App\AppStore\Fetcher; + +use OC\App\AppStore\Fetcher\CategoryFetcher; + +class CategoryFetcherTest extends FetcherBase { + public function setUp() { + parent::setUp(); + $this->fileName = 'categories.json'; + $this->endpoint = 'https://apps.nextcloud.com/api/v1/categories.json'; + + $this->fetcher = new CategoryFetcher( + $this->appData, + $this->clientService, + $this->timeFactory + ); + } +} diff --git a/tests/lib/App/AppStore/Fetcher/FetcherBase.php b/tests/lib/App/AppStore/Fetcher/FetcherBase.php new file mode 100644 index 00000000000..66df81f1b2e --- /dev/null +++ b/tests/lib/App/AppStore/Fetcher/FetcherBase.php @@ -0,0 +1,246 @@ +<?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 Test\App\AppStore\Fetcher; + +use OC\App\AppStore\Fetcher\AppFetcher; +use OC\App\AppStore\Fetcher\Fetcher; +use OCP\AppFramework\Utility\ITimeFactory; +use OCP\Files\IAppData; +use OCP\Files\NotFoundException; +use OCP\Files\SimpleFS\ISimpleFile; +use OCP\Files\SimpleFS\ISimpleFolder; +use OCP\Http\Client\IClient; +use OCP\Http\Client\IClientService; +use OCP\Http\Client\IResponse; +use OCP\IConfig; +use Test\TestCase; + +abstract class FetcherBase extends TestCase { + /** @var IAppData|\PHPUnit_Framework_MockObject_MockObject */ + protected $appData; + /** @var IClientService|\PHPUnit_Framework_MockObject_MockObject */ + protected $clientService; + /** @var ITimeFactory|\PHPUnit_Framework_MockObject_MockObject */ + protected $timeFactory; + /** @var IConfig|\PHPUnit_Framework_MockObject_MockObject */ + protected $config; + /** @var Fetcher */ + protected $fetcher; + /** @var string */ + protected $fileName; + /** @var string */ + protected $endpoint; + + public function setUp() { + parent::setUp(); + $this->appData = $this->createMock(IAppData::class); + $this->clientService = $this->createMock(IClientService::class); + $this->timeFactory = $this->createMock(ITimeFactory::class); + $this->config = $this->createMock(IConfig::class); + } + + public function testGetWithAlreadyExistingFileAndUpToDateTimestamp() { + $folder = $this->createMock(ISimpleFolder::class); + $file = $this->createMock(ISimpleFile::class); + $this->appData + ->expects($this->once()) + ->method('getFolder') + ->with('/') + ->willReturn($folder); + $folder + ->expects($this->once()) + ->method('getFile') + ->with($this->fileName) + ->willReturn($file); + $file + ->expects($this->once()) + ->method('getContent') + ->willReturn('{"timestamp":1200,"data":[{"id":"MyApp"}]}'); + $this->timeFactory + ->expects($this->once()) + ->method('getTime') + ->willReturn(1499); + + $expected = [ + [ + 'id' => 'MyApp', + ], + ]; + $this->assertSame($expected, $this->fetcher->get()); + } + + public function testGetWithNotExistingFileAndUpToDateTimestamp() { + $folder = $this->createMock(ISimpleFolder::class); + $file = $this->createMock(ISimpleFile::class); + $this->appData + ->expects($this->once()) + ->method('getFolder') + ->with('/') + ->willReturn($folder); + $folder + ->expects($this->at(0)) + ->method('getFile') + ->with($this->fileName) + ->willThrowException(new NotFoundException()); + $folder + ->expects($this->at(1)) + ->method('newFile') + ->with($this->fileName) + ->willReturn($file); + $client = $this->createMock(IClient::class); + $this->clientService + ->expects($this->once()) + ->method('newClient') + ->willReturn($client); + $response = $this->createMock(IResponse::class); + $client + ->expects($this->once()) + ->method('get') + ->with($this->endpoint) + ->willReturn($response); + $response + ->expects($this->once()) + ->method('getBody') + ->willReturn('[{"id":"MyNewApp", "foo": "foo"}, {"id":"bar"}]'); + $fileData = '{"data":[{"id":"MyNewApp","foo":"foo"},{"id":"bar"}],"timestamp":1502}'; + $file + ->expects($this->at(0)) + ->method('putContent') + ->with($fileData); + $file + ->expects($this->at(1)) + ->method('getContent') + ->willReturn($fileData); + $this->timeFactory + ->expects($this->at(0)) + ->method('getTime') + ->willReturn(1502); + + $expected = [ + [ + 'id' => 'MyNewApp', + 'foo' => 'foo', + ], + [ + 'id' => 'bar', + ], + ]; + $this->assertSame($expected, $this->fetcher->get()); + } + + public function testGetWithAlreadyExistingFileAndOutdatedTimestamp() { + $folder = $this->createMock(ISimpleFolder::class); + $file = $this->createMock(ISimpleFile::class); + $this->appData + ->expects($this->once()) + ->method('getFolder') + ->with('/') + ->willReturn($folder); + $folder + ->expects($this->once()) + ->method('getFile') + ->with($this->fileName) + ->willReturn($file); + $file + ->expects($this->at(0)) + ->method('getContent') + ->willReturn('{"timestamp":1200,"data":{"MyApp":{"id":"MyApp"}}}'); + $this->timeFactory + ->expects($this->at(0)) + ->method('getTime') + ->willReturn(1501); + $client = $this->createMock(IClient::class); + $this->clientService + ->expects($this->once()) + ->method('newClient') + ->willReturn($client); + $response = $this->createMock(IResponse::class); + $client + ->expects($this->once()) + ->method('get') + ->with($this->endpoint) + ->willReturn($response); + $response + ->expects($this->once()) + ->method('getBody') + ->willReturn('[{"id":"MyNewApp", "foo": "foo"}, {"id":"bar"}]'); + $fileData = '{"data":[{"id":"MyNewApp","foo":"foo"},{"id":"bar"}],"timestamp":1502}'; + $file + ->expects($this->at(1)) + ->method('putContent') + ->with($fileData); + $file + ->expects($this->at(2)) + ->method('getContent') + ->willReturn($fileData); + $this->timeFactory + ->expects($this->at(1)) + ->method('getTime') + ->willReturn(1502); + + $expected = [ + [ + 'id' => 'MyNewApp', + 'foo' => 'foo', + ], + [ + 'id' => 'bar', + ], + ]; + $this->assertSame($expected, $this->fetcher->get()); + } + + public function testGetWithExceptionInClient() { + $folder = $this->createMock(ISimpleFolder::class); + $file = $this->createMock(ISimpleFile::class); + $this->appData + ->expects($this->once()) + ->method('getFolder') + ->with('/') + ->willReturn($folder); + $folder + ->expects($this->once()) + ->method('getFile') + ->with($this->fileName) + ->willReturn($file); + $file + ->expects($this->at(0)) + ->method('getContent') + ->willReturn('{"timestamp":1200,"data":{"MyApp":{"id":"MyApp"}}}'); + $this->timeFactory + ->expects($this->at(0)) + ->method('getTime') + ->willReturn(1501); + $client = $this->createMock(IClient::class); + $this->clientService + ->expects($this->once()) + ->method('newClient') + ->willReturn($client); + $client + ->expects($this->once()) + ->method('get') + ->with($this->endpoint) + ->willThrowException(new \Exception()); + + $this->assertSame([], $this->fetcher->get()); + } +} diff --git a/tests/lib/App/AppStore/Version/VersionParserTest.php b/tests/lib/App/AppStore/Version/VersionParserTest.php new file mode 100644 index 00000000000..ccf557eefbc --- /dev/null +++ b/tests/lib/App/AppStore/Version/VersionParserTest.php @@ -0,0 +1,84 @@ +<?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 Test\App\AppStore\Version; + +use OC\App\AppStore\Version\Version; +use OC\App\AppStore\Version\VersionParser; +use Test\TestCase; + +class VersionParserTest extends TestCase { + /** @var VersionParser */ + private $versionParser; + + public function setUp() { + parent::setUp(); + $this->versionParser = new VersionParser(); + } + + /** + * @return array + */ + public function versionProvider() { + return [ + [ + '*', + new Version('', ''), + ], + [ + '<=8.1.2', + new Version('', '8.1.2'), + ], + [ + '<=9', + new Version('', '9'), + ], + [ + '>=9.3.2', + new Version('9.3.2', ''), + ], + [ + '>=8.1.2 <=9.3.2', + new Version('8.1.2', '9.3.2'), + ], + [ + '>=8.2 <=9.1', + new Version('8.2', '9.1'), + ], + [ + '>=9 <=11', + new Version('9', '11'), + ], + ]; + } + + /** + * @dataProvider versionProvider + * + * @param string $input + * @param Version $expected + */ + public function testGetVersion($input, + Version $expected) { + $this->assertEquals($expected, $this->versionParser->getVersion($input)); + } + +} diff --git a/tests/lib/App/AppStore/Version/VersionTest.php b/tests/lib/App/AppStore/Version/VersionTest.php new file mode 100644 index 00000000000..969c96a57a8 --- /dev/null +++ b/tests/lib/App/AppStore/Version/VersionTest.php @@ -0,0 +1,37 @@ +<?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 Test\App\AppStore\Version; + +use OC\App\AppStore\Version\Version; +use Test\TestCase; + +class VersionTest extends TestCase { + public function testGetMinimumVersion() { + $version = new Version('9', '10'); + $this->assertSame('9', $version->getMinimumVersion()); + } + + public function testGetMaximumVersion() { + $version = new Version('9', '10'); + $this->assertSame('10', $version->getMaximumVersion()); + } +} diff --git a/tests/lib/OCSClientTest.php b/tests/lib/OCSClientTest.php deleted file mode 100644 index d4bfd77e871..00000000000 --- a/tests/lib/OCSClientTest.php +++ /dev/null @@ -1,1132 +0,0 @@ -<?php -/** - * @author Lukas Reschke <lukas@owncloud.com> - * - * @copyright Copyright (c) 2015, ownCloud, Inc. - * @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 Test; - -use OC\OCSClient; -use OCP\Http\Client\IClient; -use OCP\Http\Client\IClientService; -use OCP\Http\Client\IResponse; -use OCP\IConfig; -use OCP\ILogger; - -/** - * Class OCSClientTest - */ -class OCSClientTest extends \Test\TestCase { - /** @var OCSClient */ - private $ocsClient; - /** @var IConfig */ - private $config; - /** @var IClientService */ - private $clientService; - /** @var ILogger */ - private $logger; - - public function setUp() { - parent::setUp(); - - $this->config = $this->getMockBuilder('\OCP\IConfig') - ->disableOriginalConstructor()->getMock(); - $this->clientService = $this->createMock(IClientService::class); - $this->logger = $this->createMock(ILogger::class); - - $this->ocsClient = new OCSClient( - $this->clientService, - $this->config, - $this->logger - ); - } - - public function testIsAppStoreEnabledSuccess() { - $this->config - ->expects($this->once()) - ->method('getSystemValue') - ->with('appstoreenabled', true) - ->will($this->returnValue(true)); - $this->assertTrue($this->ocsClient->isAppStoreEnabled()); - } - - public function testIsAppStoreEnabledFail() { - $this->config - ->expects($this->once()) - ->method('getSystemValue') - ->with('appstoreenabled', true) - ->will($this->returnValue(false)); - $this->assertFalse($this->ocsClient->isAppStoreEnabled()); - } - - public function testGetAppStoreUrl() { - $this->config - ->expects($this->once()) - ->method('getSystemValue') - ->with('appstoreurl', 'https://api.owncloud.com/v1') - ->will($this->returnValue('https://api.owncloud.com/v1')); - $this->assertSame('https://api.owncloud.com/v1', self::invokePrivate($this->ocsClient, 'getAppStoreUrl')); - } - - public function testGetCategoriesDisabledAppStore() { - $this->config - ->expects($this->once()) - ->method('getSystemValue') - ->with('appstoreenabled', true) - ->will($this->returnValue(false)); - $this->assertNull($this->ocsClient->getCategories([8, 1, 0, 7])); - } - - public function testGetCategoriesExceptionClient() { - $this->config - ->expects($this->at(0)) - ->method('getSystemValue') - ->with('appstoreenabled', true) - ->will($this->returnValue(true)); - $this->config - ->expects($this->at(1)) - ->method('getSystemValue') - ->with('appstoreurl', 'https://api.owncloud.com/v1') - ->will($this->returnValue('https://api.owncloud.com/v1')); - - $client = $this->createMock(IClient::class); - $client - ->expects($this->once()) - ->method('get') - ->with( - 'https://api.owncloud.com/v1/content/categories', - [ - 'timeout' => 20, - 'query' => ['version' => '8x1x0x7'], - ] - ) - ->will($this->throwException(new \Exception('TheErrorMessage'))); - - $this->clientService - ->expects($this->once()) - ->method('newClient') - ->will($this->returnValue($client)); - - $this->logger - ->expects($this->once()) - ->method('error') - ->with( - 'Could not get categories: TheErrorMessage', - [ - 'app' => 'core', - ] - ); - - $this->assertNull($this->ocsClient->getCategories([8, 1, 0, 7])); - } - - public function testGetCategoriesParseError() { - $this->config - ->expects($this->at(0)) - ->method('getSystemValue') - ->with('appstoreenabled', true) - ->will($this->returnValue(true)); - $this->config - ->expects($this->at(1)) - ->method('getSystemValue') - ->with('appstoreurl', 'https://api.owncloud.com/v1') - ->will($this->returnValue('https://api.owncloud.com/v1')); - - $response = $this->createMock(IResponse::class); - $response - ->expects($this->once()) - ->method('getBody') - ->will($this->returnValue('MyInvalidXml')); - - $client = $this->createMock(IClient::class); - $client - ->expects($this->once()) - ->method('get') - ->with( - 'https://api.owncloud.com/v1/content/categories', - [ - 'timeout' => 20, - 'query' => ['version' => '8x1x0x7'], - ] - ) - ->will($this->returnValue($response)); - - $this->clientService - ->expects($this->once()) - ->method('newClient') - ->will($this->returnValue($client)); - - $this->logger - ->expects($this->once()) - ->method('error') - ->with( - 'Could not get categories, content was no valid XML', - [ - 'app' => 'core', - ] - ); - - $this->assertNull($this->ocsClient->getCategories([8, 1, 0, 7])); - } - - public function testGetCategoriesSuccessful() { - $this->config - ->expects($this->at(0)) - ->method('getSystemValue') - ->with('appstoreenabled', true) - ->will($this->returnValue(true)); - $this->config - ->expects($this->at(1)) - ->method('getSystemValue') - ->with('appstoreurl', 'https://api.owncloud.com/v1') - ->will($this->returnValue('https://api.owncloud.com/v1')); - - $response = $this->createMock(IResponse::class); - $response - ->expects($this->once()) - ->method('getBody') - ->will($this->returnValue('<?xml version="1.0"?> - <ocs> - <meta> - <status>ok</status> - <statuscode>100</statuscode> - <message></message> - <totalitems>6</totalitems> - </meta> - <data> - <category> - <id>920</id> - <name>ownCloud Multimedia</name> - </category> - <category> - <id>921</id> - <name>ownCloud PIM</name> - </category> - <category> - <id>922</id> - <name>ownCloud Productivity</name> - </category> - <category> - <id>923</id> - <name>ownCloud Game</name> - </category> - <category> - <id>924</id> - <name>ownCloud Tool</name> - </category> - <category> - <id>925</id> - <name>ownCloud other</name> - </category> - </data> - </ocs> - ')); - - $client = $this->createMock(IClient::class); - $client - ->expects($this->once()) - ->method('get') - ->with( - 'https://api.owncloud.com/v1/content/categories', - [ - 'timeout' => 20, - 'query' => ['version' => '8x1x0x7'], - ] - ) - ->will($this->returnValue($response)); - - $this->clientService - ->expects($this->once()) - ->method('newClient') - ->will($this->returnValue($client)); - - $expected = [ - 920 => 'ownCloud Multimedia', - 921 => 'ownCloud PIM', - 922 => 'ownCloud Productivity', - 923 => 'ownCloud Game', - 924 => 'ownCloud Tool', - 925 => 'ownCloud other', - ]; - $this->assertSame($expected, $this->ocsClient->getCategories([8, 1, 0, 7])); - } - - public function testGetApplicationsDisabledAppStore() { - $this->config - ->expects($this->once()) - ->method('getSystemValue') - ->with('appstoreenabled', true) - ->will($this->returnValue(false)); - $this->assertSame([], $this->ocsClient->getApplications([], 1, 'approved', [8, 1, 0, 7])); - } - - public function testGetApplicationsExceptionClient() { - $this->config - ->expects($this->at(0)) - ->method('getSystemValue') - ->with('appstoreenabled', true) - ->will($this->returnValue(true)); - $this->config - ->expects($this->at(1)) - ->method('getSystemValue') - ->with('appstoreurl', 'https://api.owncloud.com/v1') - ->will($this->returnValue('https://api.owncloud.com/v1')); - - $client = $this->createMock(IClient::class); - $client - ->expects($this->once()) - ->method('get') - ->with( - 'https://api.owncloud.com/v1/content/data', - [ - 'timeout' => 20, - 'query' => [ - 'version' => implode('x', [8, 1, 0, 7]), - 'filter' => 'approved', - 'categories' => '815x1337', - 'sortmode' => 'new', - 'page' => 1, - 'pagesize' => 100, - 'approved' => 'approved', - ], - ] - ) - ->will($this->throwException(new \Exception('TheErrorMessage'))); - - $this->clientService - ->expects($this->once()) - ->method('newClient') - ->will($this->returnValue($client)); - - $this->logger - ->expects($this->once()) - ->method('error') - ->with( - 'Could not get applications: TheErrorMessage', - [ - 'app' => 'core', - ] - ); - - $this->assertSame([], $this->ocsClient->getApplications([815, 1337], 1, 'approved', [8, 1, 0, 7])); - } - - public function testGetApplicationsParseError() { - $this->config - ->expects($this->at(0)) - ->method('getSystemValue') - ->with('appstoreenabled', true) - ->will($this->returnValue(true)); - $this->config - ->expects($this->at(1)) - ->method('getSystemValue') - ->with('appstoreurl', 'https://api.owncloud.com/v1') - ->will($this->returnValue('https://api.owncloud.com/v1')); - - $response = $this->createMock(IResponse::class); - $response - ->expects($this->once()) - ->method('getBody') - ->will($this->returnValue('MyInvalidXml')); - - $client = $this->createMock(IClient::class); - $client - ->expects($this->once()) - ->method('get') - ->with( - 'https://api.owncloud.com/v1/content/data', - [ - 'timeout' => 20, - 'query' => [ - 'version' => implode('x', [8, 1, 0, 7]), - 'filter' => 'approved', - 'categories' => '815x1337', - 'sortmode' => 'new', - 'page' => 1, - 'pagesize' => 100, - 'approved' => 'approved', - ], - ] - ) - ->will($this->returnValue($response)); - - $this->clientService - ->expects($this->once()) - ->method('newClient') - ->will($this->returnValue($client)); - - $this->logger - ->expects($this->once()) - ->method('error') - ->with( - 'Could not get applications, content was no valid XML', - [ - 'app' => 'core', - ] - ); - - $this->assertSame([], $this->ocsClient->getApplications([815, 1337], 1, 'approved', [8, 1, 0, 7])); - } - - public function testGetApplicationsSuccessful() { - $this->config - ->expects($this->at(0)) - ->method('getSystemValue') - ->with('appstoreenabled', true) - ->will($this->returnValue(true)); - $this->config - ->expects($this->at(1)) - ->method('getSystemValue') - ->with('appstoreurl', 'https://api.owncloud.com/v1') - ->will($this->returnValue('https://api.owncloud.com/v1')); - - $response = $this->createMock(IResponse::class); - $response - ->expects($this->once()) - ->method('getBody') - ->will($this->returnValue('<?xml version="1.0"?> - <ocs> - <meta> - <status>ok</status> - <statuscode>100</statuscode> - <message></message> - <totalitems>2</totalitems> - <itemsperpage>100</itemsperpage> - </meta> - <data> - <content details="summary"> - <id>168707</id> - <name>Calendar 8.0</name> - <version>0.6.4</version> - <label>recommended</label> - <changed>2015-02-09T15:23:56+01:00</changed> - <created>2015-01-26T04:35:19+01:00</created> - <typeid>921</typeid> - <typename>ownCloud PIM</typename> - <language></language> - <personid>owncloud</personid> - <profilepage>http://opendesktop.org/usermanager/search.php?username=owncloud</profilepage> - <downloads>5393</downloads> - <score>60</score> - <description>Calendar App for ownCloud</description> - <comments>7</comments> - <fans>10</fans> - <licensetype>16</licensetype> - <approved>0</approved> - <category>1</category> - <license>AGPL</license> - <preview1></preview1> - <detailpage>https://apps.owncloud.com/content/show.php?content=168707</detailpage> - <downloadtype1></downloadtype1> - <downloadway1>0</downloadway1> - <downloadprice1>0</downloadprice1> - <downloadlink1>http://apps.owncloud.com/content/download.php?content=168707&id=1</downloadlink1> - <downloadgpgsignature1></downloadgpgsignature1> - <downloadgpgfingerprint1></downloadgpgfingerprint1> - <downloadpackagename1></downloadpackagename1> - <downloadrepository1></downloadrepository1> - <downloadname1></downloadname1> - <downloadsize1>885</downloadsize1> - </content> - <content details="summary"> - <id>168708</id> - <name>Contacts 8.0</name> - <version>0.3.0.18</version> - <label>recommended</label> - <changed>2015-02-09T15:18:58+01:00</changed> - <created>2015-01-26T04:45:17+01:00</created> - <typeid>921</typeid> - <typename>ownCloud PIM</typename> - <language></language> - <personid>owncloud</personid> - <profilepage>http://opendesktop.org/usermanager/search.php?username=owncloud</profilepage> - <downloads>4237</downloads> - <score>58</score> - <description></description> - <comments>3</comments> - <fans>6</fans> - <licensetype>16</licensetype> - <approved>200</approved> - <category>1</category> - <license>AGPL</license> - <preview1></preview1> - <detailpage>https://apps.owncloud.com/content/show.php?content=168708</detailpage> - <downloadtype1></downloadtype1> - <downloadway1>0</downloadway1> - <downloadprice1>0</downloadprice1> - <downloadlink1>http://apps.owncloud.com/content/download.php?content=168708&id=1</downloadlink1> - <downloadgpgsignature1></downloadgpgsignature1> - <downloadgpgfingerprint1></downloadgpgfingerprint1> - <downloadpackagename1></downloadpackagename1> - <downloadrepository1></downloadrepository1> - <downloadname1></downloadname1> - <downloadsize1>1409</downloadsize1> - </content> - </data> - </ocs> ')); - - $client = $this->createMock(IClient::class); - $client - ->expects($this->once()) - ->method('get') - ->with( - 'https://api.owncloud.com/v1/content/data', - [ - 'timeout' => 20, - 'query' => [ - 'version' => implode('x', [8, 1, 0, 7]), - 'filter' => 'approved', - 'categories' => '815x1337', - 'sortmode' => 'new', - 'page' => 1, - 'pagesize' => 100, - 'approved' => 'approved', - ], - ] - ) - ->will($this->returnValue($response)); - - $this->clientService - ->expects($this->once()) - ->method('newClient') - ->will($this->returnValue($client)); - - $expected = [ - [ - 'id' => '168707', - 'name' => 'Calendar 8.0', - 'label' => 'recommended', - 'version' => '0.6.4', - 'type' => '921', - 'typename' => 'ownCloud PIM', - 'personid' => 'owncloud', - 'license' => 'AGPL', - 'detailpage' => 'https://apps.owncloud.com/content/show.php?content=168707', - 'preview' => '', - 'preview-full' => '', - 'changed' => 1423491836, - 'description' => 'Calendar App for ownCloud', - 'score' => '60', - 'downloads' => 5393, - 'level' => 0, - 'profilepage' => 'http://opendesktop.org/usermanager/search.php?username=owncloud', - ], - [ - 'id' => '168708', - 'name' => 'Contacts 8.0', - 'label' => 'recommended', - 'version' => '0.3.0.18', - 'type' => '921', - 'typename' => 'ownCloud PIM', - 'personid' => 'owncloud', - 'license' => 'AGPL', - 'detailpage' => 'https://apps.owncloud.com/content/show.php?content=168708', - 'preview' => '', - 'preview-full' => '', - 'changed' => 1423491538, - 'description' => '', - 'score' => '58', - 'downloads' => 4237, - 'level' => 200, - 'profilepage' => 'http://opendesktop.org/usermanager/search.php?username=owncloud', - ], - ]; - $this->assertEquals($expected, $this->ocsClient->getApplications([815, 1337], 1, 'approved', [8, 1, 0, 7])); - } - - public function tesGetApplicationDisabledAppStore() { - $this->config - ->expects($this->once()) - ->method('getSystemValue') - ->with('appstoreenabled', true) - ->will($this->returnValue(false)); - $this->assertNull($this->ocsClient->getApplication('MyId', [8, 1, 0, 7])); - } - - public function testGetApplicationExceptionClient() { - $this->config - ->expects($this->at(0)) - ->method('getSystemValue') - ->with('appstoreenabled', true) - ->will($this->returnValue(true)); - $this->config - ->expects($this->at(1)) - ->method('getSystemValue') - ->with('appstoreurl', 'https://api.owncloud.com/v1') - ->will($this->returnValue('https://api.owncloud.com/v1')); - - $client = $this->createMock(IClient::class); - $client - ->expects($this->once()) - ->method('get') - ->with( - 'https://api.owncloud.com/v1/content/data/MyId', - [ - 'timeout' => 20, - 'query' => ['version' => '8x1x0x7'], - ] - ) - ->will($this->throwException(new \Exception('TheErrorMessage'))); - - $this->clientService - ->expects($this->once()) - ->method('newClient') - ->will($this->returnValue($client)); - - $this->logger - ->expects($this->once()) - ->method('error') - ->with( - 'Could not get application: TheErrorMessage', - [ - 'app' => 'core', - ] - ); - - $this->assertNull($this->ocsClient->getApplication('MyId', [8, 1, 0, 7])); - } - - public function testGetApplicationParseError() { - $this->config - ->expects($this->at(0)) - ->method('getSystemValue') - ->with('appstoreenabled', true) - ->will($this->returnValue(true)); - $this->config - ->expects($this->at(1)) - ->method('getSystemValue') - ->with('appstoreurl', 'https://api.owncloud.com/v1') - ->will($this->returnValue('https://api.owncloud.com/v1')); - - $response = $this->createMock(IResponse::class); - $response - ->expects($this->once()) - ->method('getBody') - ->will($this->returnValue('MyInvalidXml')); - - $client = $this->createMock(IClient::class); - $client - ->expects($this->once()) - ->method('get') - ->with( - 'https://api.owncloud.com/v1/content/data/MyId', - [ - 'timeout' => 20, - 'query' => ['version' => '8x1x0x7'], - ] - ) - ->will($this->returnValue($response)); - - $this->clientService - ->expects($this->once()) - ->method('newClient') - ->will($this->returnValue($client)); - - $this->logger - ->expects($this->once()) - ->method('error') - ->with( - 'Could not get application, content was no valid XML', - [ - 'app' => 'core', - ] - ); - - $this->assertNull($this->ocsClient->getApplication('MyId', [8, 1, 0, 7])); - } - - public function testGetApplicationSuccessful() { - $this->config - ->expects($this->at(0)) - ->method('getSystemValue') - ->with('appstoreenabled', true) - ->will($this->returnValue(true)); - $this->config - ->expects($this->at(1)) - ->method('getSystemValue') - ->with('appstoreurl', 'https://api.owncloud.com/v1') - ->will($this->returnValue('https://api.owncloud.com/v1')); - - $response = $this->createMock(IResponse::class); - $response - ->expects($this->once()) - ->method('getBody') - ->will($this->returnValue('<?xml version="1.0"?> - <ocs> - <meta> - <status>ok</status> - <statuscode>100</statuscode> - <message></message> - </meta> - <data> - <content details="full"> - <id>166053</id> - <name>Versioning</name> - <version>0.0.1</version> - <label>recommended</label> - <typeid>925</typeid> - <typename>ownCloud other</typename> - <language></language> - <personid>owncloud</personid> - <profilepage>http://opendesktop.org/usermanager/search.php?username=owncloud</profilepage> - <created>2014-07-07T16:34:40+02:00</created> - <changed>2014-07-07T16:34:40+02:00</changed> - <downloads>140</downloads> - <score>50</score> - <description>Placeholder for future updates</description> - <summary></summary> - <feedbackurl></feedbackurl> - <changelog></changelog> - <homepage></homepage> - <homepagetype></homepagetype> - <homepage2></homepage2> - <homepagetype2></homepagetype2> - <homepage3></homepage3> - <homepagetype3></homepagetype3> - <homepage4></homepage4> - <homepagetype4></homepagetype4> - <homepage5></homepage5> - <homepagetype5></homepagetype5> - <homepage6></homepage6> - <homepagetype6></homepagetype6> - <homepage7></homepage7> - <homepagetype7></homepagetype7> - <homepage8></homepage8> - <homepagetype8></homepagetype8> - <homepage9></homepage9> - <homepagetype9></homepagetype9> - <homepage10></homepage10> - <homepagetype10></homepagetype10> - <licensetype>16</licensetype> - <license>AGPL</license> - <donationpage></donationpage> - <comments>0</comments> - <commentspage>http://apps.owncloud.com/content/show.php?content=166053</commentspage> - <fans>0</fans> - <fanspage>http://apps.owncloud.com/content/show.php?action=fan&content=166053</fanspage> - <knowledgebaseentries>0</knowledgebaseentries> - <knowledgebasepage>http://apps.owncloud.com/content/show.php?action=knowledgebase&content=166053</knowledgebasepage> - <depend>ownCloud 7</depend> - <preview1></preview1> - <preview2></preview2> - <preview3></preview3> - <previewpic1></previewpic1> - <previewpic2></previewpic2> - <previewpic3></previewpic3> - <picsmall1></picsmall1> - <picsmall2></picsmall2> - <picsmall3></picsmall3> - <detailpage>https://apps.owncloud.com/content/show.php?content=166053</detailpage> - <downloadtype1></downloadtype1> - <downloadprice1>0</downloadprice1> - <downloadlink1>http://apps.owncloud.com/content/download.php?content=166053&id=1</downloadlink1> - <downloadname1></downloadname1> - <downloadgpgfingerprint1></downloadgpgfingerprint1> - <downloadgpgsignature1></downloadgpgsignature1> - <downloadpackagename1></downloadpackagename1> - <downloadrepository1></downloadrepository1> - <downloadsize1>1</downloadsize1> - <approved>200</approved> - </content> - </data> - </ocs> - ')); - - $client = $this->createMock(IClient::class); - $client - ->expects($this->once()) - ->method('get') - ->with( - 'https://api.owncloud.com/v1/content/data/166053', - [ - 'timeout' => 20, - 'query' => ['version' => '8x1x0x7'], - ] - ) - ->will($this->returnValue($response)); - - $this->clientService - ->expects($this->once()) - ->method('newClient') - ->will($this->returnValue($client)); - - $expected = [ - 'id' => 166053, - 'name' => 'Versioning', - 'version' => '0.0.1', - 'type' => '925', - 'label' => 'recommended', - 'typename' => 'ownCloud other', - 'personid' => 'owncloud', - 'profilepage' => 'http://opendesktop.org/usermanager/search.php?username=owncloud', - 'detailpage' => 'https://apps.owncloud.com/content/show.php?content=166053', - 'preview1' => '', - 'preview2' => '', - 'preview3' => '', - 'changed' => 1404743680, - 'description' => 'Placeholder for future updates', - 'score' => 50, - 'level' => 200, - ]; - $this->assertSame($expected, $this->ocsClient->getApplication(166053, [8, 1, 0, 7])); - } - - public function testGetApplicationSuccessfulWithOldId() { - $this->config - ->expects($this->at(0)) - ->method('getSystemValue') - ->with('appstoreenabled', true) - ->will($this->returnValue(true)); - $this->config - ->expects($this->at(1)) - ->method('getSystemValue') - ->with('appstoreurl', 'https://api.owncloud.com/v1') - ->will($this->returnValue('https://api.owncloud.com/v1')); - - $response = $this->createMock(IResponse::class); - $response - ->expects($this->once()) - ->method('getBody') - ->will($this->returnValue('<?xml version="1.0"?> - <ocs> - <meta> - <status>ok</status> - <statuscode>100</statuscode> - <message></message> - </meta> - <data> - <content details="full"> - <id>1337</id> - <name>Versioning</name> - <version>0.0.1</version> - <label>recommended</label> - <typeid>925</typeid> - <typename>ownCloud other</typename> - <language></language> - <personid>owncloud</personid> - <profilepage>http://opendesktop.org/usermanager/search.php?username=owncloud</profilepage> - <created>2014-07-07T16:34:40+02:00</created> - <changed>2014-07-07T16:34:40+02:00</changed> - <downloads>140</downloads> - <score>50</score> - <description>Placeholder for future updates</description> - <summary></summary> - <feedbackurl></feedbackurl> - <changelog></changelog> - <homepage></homepage> - <homepagetype></homepagetype> - <homepage2></homepage2> - <homepagetype2></homepagetype2> - <homepage3></homepage3> - <homepagetype3></homepagetype3> - <homepage4></homepage4> - <homepagetype4></homepagetype4> - <homepage5></homepage5> - <homepagetype5></homepagetype5> - <homepage6></homepage6> - <homepagetype6></homepagetype6> - <homepage7></homepage7> - <homepagetype7></homepagetype7> - <homepage8></homepage8> - <homepagetype8></homepagetype8> - <homepage9></homepage9> - <homepagetype9></homepagetype9> - <homepage10></homepage10> - <homepagetype10></homepagetype10> - <licensetype>16</licensetype> - <license>AGPL</license> - <donationpage></donationpage> - <comments>0</comments> - <commentspage>http://apps.owncloud.com/content/show.php?content=166053</commentspage> - <fans>0</fans> - <fanspage>http://apps.owncloud.com/content/show.php?action=fan&content=166053</fanspage> - <knowledgebaseentries>0</knowledgebaseentries> - <knowledgebasepage>http://apps.owncloud.com/content/show.php?action=knowledgebase&content=166053</knowledgebasepage> - <depend>ownCloud 7</depend> - <preview1></preview1> - <preview2></preview2> - <preview3></preview3> - <previewpic1></previewpic1> - <previewpic2></previewpic2> - <previewpic3></previewpic3> - <picsmall1></picsmall1> - <picsmall2></picsmall2> - <picsmall3></picsmall3> - <detailpage>https://apps.owncloud.com/content/show.php?content=166053</detailpage> - <downloadtype1></downloadtype1> - <downloadprice1>0</downloadprice1> - <downloadlink1>http://apps.owncloud.com/content/download.php?content=166053&id=1</downloadlink1> - <downloadname1></downloadname1> - <downloadgpgfingerprint1></downloadgpgfingerprint1> - <downloadgpgsignature1></downloadgpgsignature1> - <downloadpackagename1></downloadpackagename1> - <downloadrepository1></downloadrepository1> - <downloadsize1>1</downloadsize1> - <approved>200</approved> - </content> - </data> - </ocs> - ')); - - $client = $this->createMock(IClient::class); - $client - ->expects($this->once()) - ->method('get') - ->with( - 'https://api.owncloud.com/v1/content/data/166053', - [ - 'timeout' => 20, - 'query' => ['version' => '8x1x0x7'], - ] - ) - ->will($this->returnValue($response)); - - $this->clientService - ->expects($this->once()) - ->method('newClient') - ->will($this->returnValue($client)); - - $expected = [ - 'id' => 166053, - 'name' => 'Versioning', - 'version' => '0.0.1', - 'type' => '925', - 'label' => 'recommended', - 'typename' => 'ownCloud other', - 'personid' => 'owncloud', - 'profilepage' => 'http://opendesktop.org/usermanager/search.php?username=owncloud', - 'detailpage' => 'https://apps.owncloud.com/content/show.php?content=166053', - 'preview1' => '', - 'preview2' => '', - 'preview3' => '', - 'changed' => 1404743680, - 'description' => 'Placeholder for future updates', - 'score' => 50, - 'level' => 200, - ]; - $this->assertSame($expected, $this->ocsClient->getApplication(166053, [8, 1, 0, 7])); - } - - public function testGetApplicationEmptyXml() { - $this->config - ->expects($this->at(0)) - ->method('getSystemValue') - ->with('appstoreenabled', true) - ->will($this->returnValue(true)); - $this->config - ->expects($this->at(1)) - ->method('getSystemValue') - ->with('appstoreurl', 'https://api.owncloud.com/v1') - ->will($this->returnValue('https://api.owncloud.com/v1')); - - $response = $this->createMock(IResponse::class); - $response - ->expects($this->once()) - ->method('getBody') - ->will($this->returnValue('<?xml version="1.0"?> - <ocs> - <meta> - <status>ok</status> - <statuscode>100</statuscode> - <message></message> - </meta> - </ocs> - ')); - - $client = $this->createMock(IClient::class); - $client - ->expects($this->once()) - ->method('get') - ->with( - 'https://api.owncloud.com/v1/content/data/MyId', - [ - 'timeout' => 20, - 'query' => ['version' => '8x1x0x7'], - ] - ) - ->will($this->returnValue($response)); - - $this->clientService - ->expects($this->once()) - ->method('newClient') - ->will($this->returnValue($client)); - - $this->assertSame(null, $this->ocsClient->getApplication('MyId', [8, 1, 0, 7])); - } - - public function testGetApplicationDownloadDisabledAppStore() { - $this->config - ->expects($this->once()) - ->method('getSystemValue') - ->with('appstoreenabled', true) - ->will($this->returnValue(false)); - $this->assertNull($this->ocsClient->getApplicationDownload('MyId', [8, 1, 0, 7])); - } - - public function testGetApplicationDownloadExceptionClient() { - $this->config - ->expects($this->at(0)) - ->method('getSystemValue') - ->with('appstoreenabled', true) - ->will($this->returnValue(true)); - $this->config - ->expects($this->at(1)) - ->method('getSystemValue') - ->with('appstoreurl', 'https://api.owncloud.com/v1') - ->will($this->returnValue('https://api.owncloud.com/v1')); - - $client = $this->createMock(IClient::class); - $client - ->expects($this->once()) - ->method('get') - ->with( - 'https://api.owncloud.com/v1/content/download/MyId/1', - [ - 'timeout' => 20, - 'query' => ['version' => '8x1x0x7'], - ] - ) - ->will($this->throwException(new \Exception('TheErrorMessage'))); - - $this->clientService - ->expects($this->once()) - ->method('newClient') - ->will($this->returnValue($client)); - - $this->logger - ->expects($this->once()) - ->method('error') - ->with( - 'Could not get application download URL: TheErrorMessage', - [ - 'app' => 'core', - ] - ); - - $this->assertNull($this->ocsClient->getApplicationDownload('MyId', [8, 1, 0, 7])); - } - - public function testGetApplicationDownloadParseError() { - $this->config - ->expects($this->at(0)) - ->method('getSystemValue') - ->with('appstoreenabled', true) - ->will($this->returnValue(true)); - $this->config - ->expects($this->at(1)) - ->method('getSystemValue') - ->with('appstoreurl', 'https://api.owncloud.com/v1') - ->will($this->returnValue('https://api.owncloud.com/v1')); - - $response = $this->createMock(IResponse::class); - $response - ->expects($this->once()) - ->method('getBody') - ->will($this->returnValue('MyInvalidXml')); - - $client = $this->createMock(IClient::class); - $client - ->expects($this->once()) - ->method('get') - ->with( - 'https://api.owncloud.com/v1/content/download/MyId/1', - [ - 'timeout' => 20, - 'query' => ['version' => '8x1x0x7'], - ] - ) - ->will($this->returnValue($response)); - - $this->clientService - ->expects($this->once()) - ->method('newClient') - ->will($this->returnValue($client)); - - $this->logger - ->expects($this->once()) - ->method('error') - ->with( - 'Could not get application download URL, content was no valid XML', - [ - 'app' => 'core', - ] - ); - - $this->assertNull($this->ocsClient->getApplicationDownload('MyId', [8, 1, 0, 7])); - } - - public function testGetApplicationDownloadUrlSuccessful() { - $this->config - ->expects($this->at(0)) - ->method('getSystemValue') - ->with('appstoreenabled', true) - ->will($this->returnValue(true)); - $this->config - ->expects($this->at(1)) - ->method('getSystemValue') - ->with('appstoreurl', 'https://api.owncloud.com/v1') - ->will($this->returnValue('https://api.owncloud.com/v1')); - - $response = $this->createMock(IResponse::class); - $response - ->expects($this->once()) - ->method('getBody') - ->will($this->returnValue('<?xml version="1.0"?> - <ocs> - <meta> - <status>ok</status> - <statuscode>100</statuscode> - <message></message> - </meta> - <data> - <content details="download"> - <downloadlink>https://apps.owncloud.com/CONTENT/content-files/166052-files_trashbin.zip</downloadlink> - <mimetype>application/zip</mimetype> - <gpgfingerprint></gpgfingerprint> - <gpgsignature></gpgsignature> - <packagename></packagename> - <repository></repository> - </content> - </data> - </ocs> - ')); - - $client = $this->createMock(IClient::class); - $client - ->expects($this->once()) - ->method('get') - ->with( - 'https://api.owncloud.com/v1/content/download/MyId/1', - [ - 'timeout' => 20, - 'query' => ['version' => '8x1x0x7'], - ] - ) - ->will($this->returnValue($response)); - - $this->clientService - ->expects($this->once()) - ->method('newClient') - ->will($this->returnValue($client)); - - $expected = [ - 'downloadlink' => 'https://apps.owncloud.com/CONTENT/content-files/166052-files_trashbin.zip', - ]; - $this->assertSame($expected, $this->ocsClient->getApplicationDownload('MyId', [8, 1, 0, 7])); - } -} diff --git a/tests/lib/ServerTest.php b/tests/lib/ServerTest.php index 2e5900c4ce5..3ff8787b91a 100644 --- a/tests/lib/ServerTest.php +++ b/tests/lib/ServerTest.php @@ -122,8 +122,6 @@ class ServerTest extends \Test\TestCase { ['UserCache', '\OC\Cache\File'], ['UserCache', '\OCP\ICache'], - ['OcsClient', '\OC\OCSClient'], - ['PreviewManager', '\OC\PreviewManager'], ['PreviewManager', '\OCP\IPreview'], |