Add experimental applications switch

Allows administrators to disable or enabled experimental applications as well as show the trust level.
This commit is contained in:
Lukas Reschke 2015-03-30 15:58:20 +02:00
parent a2087f30d1
commit 0816cf9142
18 changed files with 1635 additions and 248 deletions

View File

@ -576,6 +576,15 @@ $CONFIG = array(
*/
'appstoreurl' => 'https://api.owncloud.com/v1',
/**
* Whether to 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

View File

@ -123,7 +123,7 @@
if(OC_User::isAdminUser(OC_User::getUser())):
?>
<li id="apps-management">
<a href="<?php print_unescaped(OC_Helper::linkToRoute('settings_apps')); ?>" tabindex="4"
<a href="<?php print_unescaped(\OC::$server->getURLGenerator()->linkToRoute('settings.AppSettings.viewApps')); ?>" tabindex="4"
<?php if( $_['appsmanagement_active'] ): ?> class="active"<?php endif; ?>>
<img class="app-icon svg" alt="" src="<?php print_unescaped(OC_Helper::imagePath('settings', 'apps.svg')); ?>">
<div class="icon-loading-dark" style="display:none;"></div>

View File

@ -61,6 +61,7 @@ class OC_App {
static private $loadedApps = array();
static private $altLogin = array();
private static $shippedApps = null;
const officialApp = 200;
/**
* clean the appId
@ -306,8 +307,13 @@ class OC_App {
* @return int
*/
public static function downloadApp($app) {
$appData= OCSClient::getApplication($app);
$download= OCSClient::getApplicationDownload($app, 1);
$ocsClient = new OCSClient(
\OC::$server->getHTTPClientService(),
\OC::$server->getConfig(),
\OC::$server->getLogger()
);
$appData = $ocsClient->getApplication($app);
$download= $ocsClient->getApplicationDownload($app);
if(isset($download['downloadlink']) and $download['downloadlink']!='') {
// Replace spaces in download link without encoding entire URL
$download['downloadlink'] = str_replace(' ', '%20', $download['downloadlink']);
@ -780,8 +786,9 @@ class OC_App {
}
/**
* Lists all apps, this is used in apps.php
* List all apps, this is used in apps.php
*
* @param bool $onlyLocal
* @return array
*/
public static function listAllApps($onlyLocal = false) {
@ -819,8 +826,7 @@ class OC_App {
if (isset($info['shipped']) and ($info['shipped'] == 'true')) {
$info['internal'] = true;
$info['internallabel'] = (string)$l->t('Recommended');
$info['internalclass'] = 'recommendedapp';
$info['level'] = self::officialApp;
$info['removable'] = false;
} else {
$info['internal'] = false;
@ -845,7 +851,7 @@ class OC_App {
}
}
if ($onlyLocal) {
$remoteApps = array();
$remoteApps = [];
} else {
$remoteApps = OC_App::getAppstoreApps();
}
@ -865,34 +871,6 @@ class OC_App {
} else {
$combinedApps = $appList;
}
// bring the apps into the right order with a custom sort function
usort($combinedApps, function ($a, $b) {
// priority 1: active
if ($a['active'] != $b['active']) {
return $b['active'] - $a['active'];
}
// priority 2: shipped
$aShipped = (array_key_exists('shipped', $a) && $a['shipped'] === 'true') ? 1 : 0;
$bShipped = (array_key_exists('shipped', $b) && $b['shipped'] === 'true') ? 1 : 0;
if ($aShipped !== $bShipped) {
return ($bShipped - $aShipped);
}
// priority 3: recommended
$internalClassA = isset($a['internalclass']) ? $a['internalclass'] : '';
$internalClassB = isset($b['internalclass']) ? $b['internalclass'] : '';
if ($internalClassA != $internalClassB) {
$aTemp = ($internalClassA == 'recommendedapp' ? 1 : 0);
$bTemp = ($internalClassB == 'recommendedapp' ? 1 : 0);
return ($bTemp - $aTemp);
}
// priority 4: alphabetical
return strcasecmp($a['name'], $b['name']);
});
return $combinedApps;
}
@ -913,15 +891,24 @@ class OC_App {
}
/**
* get a list of all apps on apps.owncloud.com
*
* @return array|false multi-dimensional array of apps.
* Keys: id, name, type, typename, personid, license, detailpage, preview, changed, description
* Get a list of all apps on the appstore
* @param string $filter
* @param string $category
* @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) {
$categories = array($category);
$categories = [$category];
$ocsClient = new OCSClient(
\OC::$server->getHTTPClientService(),
\OC::$server->getConfig(),
\OC::$server->getLogger()
);
if (is_null($category)) {
$categoryNames = OCSClient::getCategories();
$categoryNames = $ocsClient->getCategories();
if (is_array($categoryNames)) {
// Check that categories of apps were retrieved correctly
if (!$categories = array_keys($categoryNames)) {
@ -933,34 +920,36 @@ class OC_App {
}
$page = 0;
$remoteApps = OCSClient::getApplications($categories, $page, $filter);
$app1 = array();
$remoteApps = $ocsClient->getApplications($categories, $page, $filter);
$apps = [];
$i = 0;
$l = \OC::$server->getL10N('core');
foreach ($remoteApps as $app) {
$potentialCleanId = self::getInternalAppIdByOcs($app['id']);
// enhance app info (for example the description)
$app1[$i] = OC_App::parseAppInfo($app);
$app1[$i]['author'] = $app['personid'];
$app1[$i]['ocs_id'] = $app['id'];
$app1[$i]['internal'] = 0;
$app1[$i]['active'] = ($potentialCleanId !== false) ? self::isEnabled($potentialCleanId) : false;
$app1[$i]['update'] = false;
$app1[$i]['groups'] = false;
$app1[$i]['score'] = $app['score'];
$app1[$i]['removable'] = false;
$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') {
$app1[$i]['internallabel'] = (string)$l->t('Recommended');
$app1[$i]['internalclass'] = 'recommendedapp';
$apps[$i]['internallabel'] = (string)$l->t('Recommended');
$apps[$i]['internalclass'] = 'recommendedapp';
}
$i++;
}
if (empty($app1)) {
if (empty($apps)) {
return false;
} else {
return $app1;
return $apps;
}
}
@ -1084,7 +1073,12 @@ class OC_App {
public static function installApp($app) {
$l = \OC::$server->getL10N('core');
$config = \OC::$server->getConfig();
$appData=OCSClient::getApplication($app);
$ocsClient = new OCSClient(
\OC::$server->getHTTPClientService(),
$config,
\OC::$server->getLogger()
);
$appData = $ocsClient->getApplication($app);
// 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)) {

View File

@ -203,7 +203,7 @@ class AppManager implements IAppManager {
/**
* Clear the cached list of apps when enabling/disabling an app
*/
protected function clearAppsCache() {
public function clearAppsCache() {
$settingsMemCache = $this->memCacheFactory->create('settings');
$settingsMemCache->clear('listApps');
}

View File

@ -222,8 +222,13 @@ class OC_Installer{
* @throws Exception
*/
public static function updateAppByOCSId($ocsId) {
$appData = OCSClient::getApplication($ocsId);
$download = OCSClient::getApplicationDownload($ocsId, 1);
$ocsClient = new OCSClient(
\OC::$server->getHTTPClientService(),
\OC::$server->getConfig(),
\OC::$server->getLogger()
);
$appData = $ocsClient->getApplication($ocsId);
$download = $ocsClient->getApplicationDownload($ocsId);
if (isset($download['downloadlink']) && trim($download['downloadlink']) !== '') {
$download['downloadlink'] = str_replace(' ', '%20', $download['downloadlink']);
@ -385,8 +390,12 @@ class OC_Installer{
$ocsid=OC_Appconfig::getValue( $app, 'ocsid', '');
if($ocsid<>'') {
$ocsdata=OCSClient::getApplication($ocsid);
$ocsClient = new OCSClient(
\OC::$server->getHTTPClientService(),
\OC::$server->getConfig(),
\OC::$server->getLogger()
);
$ocsdata = $ocsClient->getApplication($ocsid);
$ocsversion= (string) $ocsdata['version'];
$currentversion=OC_App::getAppVersion($app);
if (version_compare($ocsversion, $currentversion, '>')) {

View File

@ -32,36 +32,52 @@
namespace OC;
/**
* This class provides an easy way for apps to store config values in the
* database.
*/
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 static function isAppStoreEnabled() {
if (\OC::$server->getConfig()->getSystemValue('appstoreenabled', true) === false ) {
return false;
}
return true;
public function isAppStoreEnabled() {
return $this->config->getSystemValue('appstoreenabled', true) === true;
}
/**
* Get the url of the OCS AppStore server.
*
* @return string of the AppStore server
*
* This function returns the url of the OCS AppStore server. It´s possible
* to set it in the config file or it will fallback to the default
*/
private static function getAppStoreURL() {
return \OC::$server->getConfig()->getSystemValue('appstoreurl', 'https://api.owncloud.com/v1');
private function getAppStoreUrl() {
return $this->config->getSystemValue('appstoreurl', 'https://api.owncloud.com/v1');
}
/**
@ -71,36 +87,50 @@ class OCSClient {
* @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 static function getCategories() {
if (!self::isAppStoreEnabled()) {
public function getCategories() {
if (!$this->isAppStoreEnabled()) {
return null;
}
$url = self::getAppStoreURL() . '/content/categories';
$client = \OC::$server->getHTTPClientService()->newClient();
$client = $this->httpClientService->newClient();
try {
$response = $client->get($url, ['timeout' => 5]);
$response = $client->get(
$this->getAppStoreUrl() . '/content/categories',
[
'timeout' => 5,
]
);
} catch(\Exception $e) {
return null;
}
if($response->getStatusCode() !== 200) {
$this->logger->error(
sprintf('Could not get categories: %s', $e->getMessage()),
[
'app' => 'core',
]
);
return null;
}
$loadEntities = libxml_disable_entity_loader(true);
$data = simplexml_load_string($response->getBody());
$data = @simplexml_load_string($response->getBody());
libxml_disable_entity_loader($loadEntities);
if($data === false) {
$this->logger->error(
'Could not get categories, content was no valid XML',
[
'app' => 'core',
]
);
return null;
}
$tmp = $data->data;
$cats = [];
foreach ($tmp->category as $value) {
$id = (int)$value->id;
$name = (string)$value->name;
$cats[$id] = $name;
}
return $cats;
@ -108,50 +138,63 @@ class OCSClient {
/**
* Get all the applications from the OCS server
*
* @return array|null an array of application data or null
*
* This function returns a list of all the applications on the OCS server
* @param array|string $categories
* @param array $categories
* @param int $page
* @param string $filter
* @return array An array of application data
*/
public static function getApplications($categories, $page, $filter) {
if (!self::isAppStoreEnabled()) {
return (array());
public function getApplications(array $categories, $page, $filter) {
if (!$this->isAppStoreEnabled()) {
return [];
}
if (is_array($categories)) {
$categoriesString = implode('x', $categories);
} else {
$categoriesString = $categories;
}
$version = '&version=' . implode('x', \OC_Util::getVersion());
$filterUrl = '&filter=' . urlencode($filter);
$url = self::getAppStoreURL() . '/content/data?categories=' . urlencode($categoriesString)
. '&sortmode=new&page=' . urlencode($page) . '&pagesize=100' . $filterUrl . $version;
$apps = [];
$client = \OC::$server->getHTTPClientService()->newClient();
$client = $this->httpClientService->newClient();
try {
$response = $client->get($url, ['timeout' => 5]);
$response = $client->get(
$this->getAppStoreUrl() . '/content/data',
[
'timeout' => 5,
'query' => [
'version' => implode('x', \OC_Util::getVersion()),
'filter' => $filter,
'categories' => implode('x', $categories),
'sortmode' => 'new',
'page' => $page,
'pagesize' => 100,
'approved' => $filter
],
]
);
} catch(\Exception $e) {
return null;
}
if($response->getStatusCode() !== 200) {
return null;
$this->logger->error(
sprintf('Could not get applications: %s', $e->getMessage()),
[
'app' => 'core',
]
);
return [];
}
$loadEntities = libxml_disable_entity_loader(true);
$data = simplexml_load_string($response->getBody());
$data = @simplexml_load_string($response->getBody());
libxml_disable_entity_loader($loadEntities);
if($data === false) {
$this->logger->error(
'Could not get applications, content was no valid XML',
[
'app' => 'core',
]
);
return [];
}
$tmp = $data->data->content;
$tmpCount = count($tmp);
$apps = [];
for ($i = 0; $i < $tmpCount; $i++) {
$app = array();
$app = [];
$app['id'] = (string)$tmp[$i]->id;
$app['name'] = (string)$tmp[$i]->name;
$app['label'] = (string)$tmp[$i]->label;
@ -167,9 +210,11 @@ class OCSClient {
$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;
}
@ -182,84 +227,111 @@ class OCSClient {
*
* This function returns an applications from the OCS server
*/
public static function getApplication($id) {
if (!self::isAppStoreEnabled()) {
return null;
}
$url = self::getAppStoreURL() . '/content/data/' . urlencode($id);
$client = \OC::$server->getHTTPClientService()->newClient();
try {
$response = $client->get($url, ['timeout' => 5]);
} catch(\Exception $e) {
public function getApplication($id) {
if (!$this->isAppStoreEnabled()) {
return null;
}
if($response->getStatusCode() !== 200) {
$client = $this->httpClientService->newClient();
try {
$response = $client->get(
$this->getAppStoreUrl() . '/content/data/' . urlencode($id),
[
'timeout' => 5,
]
);
} catch(\Exception $e) {
$this->logger->error(
sprintf('Could not get application: %s', $e->getMessage()),
[
'app' => 'core',
]
);
return null;
}
$loadEntities = libxml_disable_entity_loader(true);
$data = simplexml_load_string($response->getBody());
$data = @simplexml_load_string($response->getBody());
libxml_disable_entity_loader($loadEntities);
$tmp = $data->data->content;
if (is_null($tmp)) {
\OC_Log::write('core', 'Invalid OCS content returned for app ' . $id, \OC_Log::FATAL);
if($data === false) {
$this->logger->error(
'Could not get application, content was no valid XML',
[
'app' => 'core',
]
);
return null;
}
$tmp = $data->data->content;
$app = [];
$app['id'] = $tmp->id;
$app['name'] = $tmp->name;
$app['version'] = $tmp->version;
$app['type'] = $tmp->typeid;
$app['label'] = $tmp->label;
$app['typename'] = $tmp->typename;
$app['personid'] = $tmp->personid;
$app['detailpage'] = $tmp->detailpage;
$app['preview1'] = $tmp->smallpreviewpic1;
$app['preview2'] = $tmp->smallpreviewpic2;
$app['preview3'] = $tmp->smallpreviewpic3;
$app['id'] = (int)$tmp->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['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'] = $tmp->description;
$app['detailpage'] = $tmp->detailpage;
$app['score'] = $tmp->score;
$app['description'] = (string)$tmp->description;
$app['detailpage'] = (string)$tmp->detailpage;
$app['score'] = (int)$tmp->score;
return $app;
}
/**
* Get the download url for an application from the OCS server
*
* @param $id
* @return array|null an array of application data or null
*
* This function returns an download url for an applications from the OCS server
* @param string $id
* @param integer $item
*/
public static function getApplicationDownload($id, $item) {
if (!self::isAppStoreEnabled()) {
public function getApplicationDownload($id) {
if (!$this->isAppStoreEnabled()) {
return null;
}
$url = self::getAppStoreURL() . '/content/download/' . urlencode($id) . '/' . urlencode($item);
$client = \OC::$server->getHTTPClientService()->newClient();
$url = $this->getAppStoreUrl() . '/content/download/' . urlencode($id) . '/1';
$client = $this->httpClientService->newClient();
try {
$response = $client->get($url, ['timeout' => 5]);
$response = $client->get(
$url,
[
'timeout' => 5,
]
);
} catch(\Exception $e) {
return null;
}
if($response->getStatusCode() !== 200) {
$this->logger->error(
sprintf('Could not get application download URL: %s', $e->getMessage()),
[
'app' => 'core',
]
);
return null;
}
$loadEntities = libxml_disable_entity_loader(true);
$data = simplexml_load_string($response->getBody());
$data = @simplexml_load_string($response->getBody());
libxml_disable_entity_loader($loadEntities);
if($data === false) {
$this->logger->error(
'Could not get application download URL, content was no valid XML',
[
'app' => 'core',
]
);
return null;
}
$tmp = $data->data->content;
$app = array();
$app = [];
if (isset($tmp->downloadlink)) {
$app['downloadlink'] = $tmp->downloadlink;
$app['downloadlink'] = (string)$tmp->downloadlink;
} else {
$app['downloadlink'] = '';
}

View File

@ -391,6 +391,13 @@ class Server extends SimpleContainer implements IServerContainer {
new \OC_Defaults()
);
});
$this->registerService('OcsClient', function(Server $c) {
return new OCSClient(
$this->getHTTPClientService(),
$this->getConfig(),
$this->getLogger()
);
});
}
/**
@ -836,6 +843,13 @@ class Server extends SimpleContainer implements IServerContainer {
return $this->webRoot;
}
/**
* @return \OC\OCSClient
*/
public function getOcsClient() {
return $this->query('OcsClient');
}
/**
* @return \OCP\IDateTimeZone
*/

View File

@ -107,7 +107,7 @@ class OC_TemplateLayout extends OC_Template {
$userDisplayName = OC_User::getDisplayName();
$this->assign('user_displayname', $userDisplayName);
$this->assign('user_uid', OC_User::getUser());
$this->assign('appsmanagement_active', strpos(\OC::$server->getRequest()->getRequestUri(), OC_Helper::linkToRoute('settings_apps')) === 0 );
$this->assign('appsmanagement_active', strpos(\OC::$server->getRequest()->getRequestUri(), \OC::$server->getURLGenerator()->linkToRoute('settings.AppSettings.viewApps')) === 0 );
$this->assign('enableAvatars', $this->config->getSystemValue('enable_avatars', true));
$this->assign('userAvatarSet', \OC_Helper::userAvatarSet(OC_User::getUser()));
} else if ($renderAs == 'error') {

View File

@ -78,4 +78,9 @@ interface IAppManager {
* @return string[]
*/
public function getInstalledApps();
/**
* Clear the cached list of apps when enabling/disabling an app
*/
public function clearAppsCache();
}

View File

@ -71,7 +71,10 @@ class Application extends App {
$c->query('Request'),
$c->query('L10N'),
$c->query('Config'),
$c->query('ICacheFactory')
$c->query('ICacheFactory'),
$c->query('INavigationManager'),
$c->query('IAppManager'),
$c->query('OcsClient')
);
});
$container->registerService('SecuritySettingsController', function(IContainer $c) {
@ -191,6 +194,15 @@ class Application extends App {
$container->registerService('ClientService', function(IContainer $c) {
return $c->query('ServerContainer')->getHTTPClientService();
});
$container->registerService('INavigationManager', function(IContainer $c) {
return $c->query('ServerContainer')->getNavigationManager();
});
$container->registerService('IAppManager', function(IContainer $c) {
return $c->query('ServerContainer')->getAppManager();
});
$container->registerService('OcsClient', function(IContainer $c) {
return $c->query('ServerContainer')->getOcsClient();
});
$container->registerService('Util', function(IContainer $c) {
return new \OC_Util();
});

View File

@ -1,42 +0,0 @@
<?php
/**
* @author Bart Visscher <bartv@thisnet.nl>
* @author Frank Karlitschek <frank@owncloud.org>
* @author Jan-Christoph Borchardt <hey@jancborchardt.net>
* @author Lukas Reschke <lukas@owncloud.com>
* @author Morris Jobke <hey@morrisjobke.de>
* @author Robin Appelman <icewind@owncloud.com>
* @author Thomas Müller <thomas.mueller@tmit.eu>
*
* @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/>
*
*/
OC_Util::checkAdminUser();
\OC::$server->getSession()->close();
// Load the files we need
\OC_Util::addVendorScript('handlebars/handlebars');
\OCP\Util::addScript("settings", "settings");
\OCP\Util::addStyle("settings", "settings");
\OC_Util::addVendorScript('select2/select2');
\OC_Util::addVendorStyle('select2/select2');
\OCP\Util::addScript("settings", "apps");
\OC_App::setActiveNavigationEntry( "core_apps" );
$tmpl = new OC_Template( "settings", "apps", "user" );
$tmpl->printPage();

View File

@ -27,8 +27,13 @@ namespace OC\Settings\Controller;
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\TemplateResponse;
use OCP\ICacheFactory;
use OCP\INavigationManager;
use OCP\IRequest;
use OCP\IL10N;
use OCP\IConfig;
@ -44,6 +49,12 @@ class AppSettingsController extends Controller {
private $config;
/** @var \OCP\ICache */
private $cache;
/** @var INavigationManager */
private $navigationManager;
/** @var IAppManager */
private $appManager;
/** @var OCSClient */
private $ocsClient;
/**
* @param string $appName
@ -51,16 +62,53 @@ class AppSettingsController extends Controller {
* @param IL10N $l10n
* @param IConfig $config
* @param ICacheFactory $cache
* @param INavigationManager $navigationManager
* @param IAppManager $appManager
* @param OCSClient $ocsClient
*/
public function __construct($appName,
IRequest $request,
IL10N $l10n,
IConfig $config,
ICacheFactory $cache) {
ICacheFactory $cache,
INavigationManager $navigationManager,
IAppManager $appManager,
OCSClient $ocsClient) {
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();
}
/**
* @NoCSRFRequired
* @return TemplateResponse
*/
public function viewApps() {
$params = [];
$params['experimentalEnabled'] = $this->config->getSystemValue('appstore.experimental.enabled', false);
$this->navigationManager->setActiveEntry('core_apps');
$templateResponse = new TemplateResponse($this->appName, 'apps', $params, 'user');
$policy = new ContentSecurityPolicy();
$policy->addAllowedImageDomain('https://apps.owncloud.com');
$templateResponse->setContentSecurityPolicy($policy);
return $templateResponse;
}
/**
@ -77,16 +125,15 @@ class AppSettingsController extends Controller {
['id' => 1, 'displayName' => (string)$this->l10n->t('Not enabled')],
];
if(OCSClient::isAppStoreEnabled()) {
$categories[] = ['id' => 2, 'displayName' => (string)$this->l10n->t('Recommended')];
if($this->ocsClient->isAppStoreEnabled()) {
// apps from external repo via OCS
$ocs = OCSClient::getCategories();
$ocs = $this->ocsClient->getCategories();
if ($ocs) {
foreach($ocs as $k => $v) {
$categories[] = array(
$categories[] = [
'id' => $k,
'displayName' => str_replace('ownCloud ', '', $v)
);
];
}
}
}
@ -97,7 +144,8 @@ class AppSettingsController extends Controller {
}
/**
* Get all available categories
* Get all available apps in a category
*
* @param int $category
* @return array
*/
@ -134,16 +182,9 @@ class AppSettingsController extends Controller {
});
break;
default:
if ($category === 2) {
$apps = \OC_App::getAppstoreApps('approved');
if ($apps) {
$apps = array_filter($apps, function ($app) {
return isset($app['internalclass']) && $app['internalclass'] === 'recommendedapp';
});
}
} else {
$apps = \OC_App::getAppstoreApps('approved', $category);
}
$filter = $this->config->getSystemValue('appstore.experimental.enabled', false) ? 'all' : 'approved';
$apps = \OC_App::getAppstoreApps($filter, $category);
if (!$apps) {
$apps = array();
} else {

View File

@ -210,6 +210,24 @@ span.version { margin-left:1em; margin-right:1em; color:#555; }
opacity: .5;
}
.app-level {
color: white;
}
.app-level .official, .app-level .approved {
background-color: #E8C805;
border-radius: 2px;
margin-left: 5px;
padding: 3px;
}
.app-level .experimental {
background-color: #F02405;
border-radius: 2px;
margin-left: 5px;
padding: 3px;
}
#apps-list {
position: relative;
height: 100%;
@ -236,6 +254,7 @@ span.version { margin-left:1em; margin-right:1em; color:#555; }
.app-name,
.app-version,
.app-score,
.app-level,
.recommendedapp {
display: inline-block;
}
@ -261,7 +280,7 @@ span.version { margin-left:1em; margin-right:1em; color:#555; }
white-space: pre-line;
}
#app-category-2 {
#app-category-1 {
border-bottom: 1px solid #e8e8e8;
}

View File

@ -9,6 +9,17 @@ Handlebars.registerHelper('score', function() {
}
return new Handlebars.SafeString('');
});
Handlebars.registerHelper('level', function() {
if(typeof this.level !== 'undefined') {
if(this.level === 200) {
return new Handlebars.SafeString('<span class="official">Official</span>');
} else if(this.level === 100) {
return new Handlebars.SafeString('<span class="approved">Approved</span>');
} else {
return new Handlebars.SafeString('<span class="experimental">Experimental</span>');
}
}
});
OC.Settings = OC.Settings || {};
OC.Settings.Apps = OC.Settings.Apps || {
@ -73,7 +84,6 @@ OC.Settings.Apps = OC.Settings.Apps || {
this._loadCategoryCall = $.ajax(OC.generateUrl('settings/apps/list?category={categoryId}', {
categoryId: categoryId
}), {
data:{},
type:'GET',
success: function (apps) {
OC.Settings.Apps.State.apps = _.indexBy(apps.apps, 'id');
@ -81,13 +91,27 @@ OC.Settings.Apps = OC.Settings.Apps || {
var template = Handlebars.compile(source);
if (apps.apps.length) {
apps.apps.sort(function(a,b) {
return b.level - a.level;
});
var firstExperimental = false;
_.each(apps.apps, function(app) {
OC.Settings.Apps.renderApp(app, template, null);
if(app.level === 0 && firstExperimental === false) {
firstExperimental = true;
OC.Settings.Apps.renderApp(app, template, null, true);
} else {
OC.Settings.Apps.renderApp(app, template, null, false);
}
});
} else {
$('#apps-list').addClass('hidden');
$('#apps-list-empty').removeClass('hidden');
}
$('.app-level .official').tipsy({fallback: t('core', 'Official apps are developed by and within the ownCloud community and its GitHub repository and offer functionality central to ownCloud. They are ready for serious use.')});
$('.app-level .approved').tipsy({fallback: t('core', 'Approved apps are developed by trusted developers and have passed a cursory security check. They are actively maintained in an open code repository and their maintainers deem them to be stable for casual to normal use.')});
$('.app-level .experimental').tipsy({fallback: t('core', 'This app is not checked for security issues and is new or known to be unstable. Install on your own risk.')});
},
complete: function() {
$('#apps-list').removeClass('icon-loading');
@ -95,7 +119,7 @@ OC.Settings.Apps = OC.Settings.Apps || {
});
},
renderApp: function(app, template, selector) {
renderApp: function(app, template, selector, firstExperimental) {
if (!template) {
var source = $("#app-template").html();
template = Handlebars.compile(source);
@ -103,6 +127,7 @@ OC.Settings.Apps = OC.Settings.Apps || {
if (typeof app === 'string') {
app = OC.Settings.Apps.State.apps[app];
}
app.firstExperimental = firstExperimental;
var html = template(app);
if (selector) {
@ -438,6 +463,16 @@ OC.Settings.Apps = OC.Settings.Apps || {
$select.change();
});
$(document).on('click', '#enable-experimental-apps', function () {
var state = $('#enable-experimental-apps').prop('checked');
$.ajax(OC.generateUrl('settings/apps/experimental'), {
data: {state: state},
type: 'POST',
success:function () {
location.reload();
}
});
});
}
};

View File

@ -33,25 +33,27 @@
namespace OC\Settings;
$application = new Application();
$application->registerRoutes($this, array(
'resources' => array(
'groups' => array('url' => '/settings/users/groups'),
'users' => array('url' => '/settings/users/users')
),
'routes' => array(
array('name' => 'MailSettings#setMailSettings', 'url' => '/settings/admin/mailsettings', 'verb' => 'POST'),
array('name' => 'MailSettings#storeCredentials', 'url' => '/settings/admin/mailsettings/credentials', 'verb' => 'POST'),
array('name' => 'MailSettings#sendTestMail', 'url' => '/settings/admin/mailtest', 'verb' => 'POST'),
array('name' => 'AppSettings#listCategories', 'url' => '/settings/apps/categories', 'verb' => 'GET'),
array('name' => 'AppSettings#listApps', 'url' => '/settings/apps/list', 'verb' => 'GET'),
array('name' => 'SecuritySettings#trustedDomains', 'url' => '/settings/admin/security/trustedDomains', 'verb' => 'POST'),
array('name' => 'Users#setMailAddress', 'url' => '/settings/users/{id}/mailAddress', 'verb' => 'PUT'),
array('name' => 'LogSettings#setLogLevel', 'url' => '/settings/admin/log/level', 'verb' => 'POST'),
array('name' => 'LogSettings#getEntries', 'url' => '/settings/admin/log/entries', 'verb' => 'GET'),
array('name' => 'LogSettings#download', 'url' => '/settings/admin/log/download', 'verb' => 'GET'),
$application->registerRoutes($this, [
'resources' => [
'groups' => ['url' => '/settings/users/groups'],
'users' => ['url' => '/settings/users/users']
],
'routes' => [
['name' => 'MailSettings#setMailSettings', 'url' => '/settings/admin/mailsettings', 'verb' => 'POST'],
['name' => 'MailSettings#storeCredentials', 'url' => '/settings/admin/mailsettings/credentials', 'verb' => 'POST'],
['name' => 'MailSettings#sendTestMail', 'url' => '/settings/admin/mailtest', 'verb' => 'POST'],
['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#setMailAddress', 'url' => '/settings/users/{id}/mailAddress', 'verb' => 'PUT'],
['name' => 'LogSettings#setLogLevel', 'url' => '/settings/admin/log/level', 'verb' => 'POST'],
['name' => 'LogSettings#getEntries', 'url' => '/settings/admin/log/entries', 'verb' => 'GET'],
['name' => 'LogSettings#download', 'url' => '/settings/admin/log/download', 'verb' => 'GET'],
['name' => 'CheckSetup#check', 'url' => '/settings/ajax/checksetup', 'verb' => 'GET'],
)
));
]
]);
/** @var $this \OCP\Route\IRouter */
@ -62,8 +64,6 @@ $this->create('settings_personal', '/settings/personal')
->actionInclude('settings/personal.php');
$this->create('settings_users', '/settings/users')
->actionInclude('settings/users.php');
$this->create('settings_apps', '/settings/apps')
->actionInclude('settings/apps.php');
$this->create('settings_admin', '/settings/admin')
->actionInclude('settings/admin.php');
// Settings ajax actions

View File

@ -1,3 +1,27 @@
<?php
style('settings', 'settings');
vendor_style(
'core',
[
'select2/select2',
]
);
vendor_script(
'core',
[
'handlebars/handlebars',
'select2/select2'
]
);
script(
'settings',
[
'settings',
'apps',
]
);
/** @var array $_ */
?>
<script id="categories-template" type="text/x-handlebars-template">
{{#each this}}
<li id="app-category-{{id}}" data-category-id="{{id}}" tabindex="0">
@ -16,6 +40,18 @@
</script>
<script id="app-template" type="text/x-handlebars">
{{#if firstExperimental}}
<div style="background-color: lightyellow; border-top:1px solid black; border-bottom: 1px solid black;">
<h2><?php p($l->t('Experimental applications ahead')) ?></h2>
<p>
<?php p($l->t('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.')) ?>
</p>
</div>
{{/if}}
<div class="section" id="app-{{id}}">
{{#if preview}}
<div class="app-image{{#if previewAsIcon}} app-image-icon{{/if}} hidden">
@ -23,6 +59,9 @@
{{/if}}
<h2 class="app-name"><a href="{{detailpage}}" target="_blank">{{name}}</a></h2>
<div class="app-version"> {{version}}</div>
<div class="app-level">
{{{level}}}
</div>
<div class="app-author"><?php p($l->t('by')); ?> {{author}}
{{#if licence}}
({{licence}}-<?php p($l->t('licensed')); ?>)
@ -95,6 +134,24 @@
<ul id="apps-categories">
</ul>
<div id="app-settings">
<div id="app-settings-header">
<button class="settings-button" data-apps-slide-toggle="#app-settings-content"></button>
</div>
<div id="app-settings-content" style="color: #c33">
<input type="checkbox" id="enable-experimental-apps" <?php if($_['experimentalEnabled']) { print_unescaped('checked="checked"'); }?>>
<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 and are new or known to be unstable and under heavy ' .
'development. Installing these can cause data loss or security ' .
'breaches.')) ?>
</small>
</p>
</div>
</div>
</div>
<div id="app-content">
<div id="apps-list" class="icon-loading"></div>

931
tests/lib/OCSClientTest.php Normal file
View File

@ -0,0 +1,931 @@
<?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/>
*
*/
use OC\OCSClient;
use OCP\Http\Client\IClientService;
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->getMock('\OCP\Http\Client\IClientService');
$this->logger = $this->getMock('\OCP\ILogger');
$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', Test_Helper::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());
}
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->getMock('\OCP\Http\Client\IClient');
$client
->expects($this->once())
->method('get')
->with(
'https://api.owncloud.com/v1/content/categories',
[
'timeout' => 5,
]
)
->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());
}
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->getMock('\OCP\Http\Client\IResponse');
$response
->expects($this->once())
->method('getBody')
->will($this->returnValue('MyInvalidXml'));
$client = $this->getMock('\OCP\Http\Client\IClient');
$client
->expects($this->once())
->method('get')
->with(
'https://api.owncloud.com/v1/content/categories',
[
'timeout' => 5,
]
)
->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());
}
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->getMock('\OCP\Http\Client\IResponse');
$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->getMock('\OCP\Http\Client\IClient');
$client
->expects($this->once())
->method('get')
->with(
'https://api.owncloud.com/v1/content/categories',
[
'timeout' => 5,
]
)
->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());
}
public function testGetApplicationsDisabledAppStore() {
$this->config
->expects($this->once())
->method('getSystemValue')
->with('appstoreenabled', true)
->will($this->returnValue(false));
$this->assertSame([], $this->ocsClient->getApplications([], 1, 'approved'));
}
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->getMock('\OCP\Http\Client\IClient');
$client
->expects($this->once())
->method('get')
->with(
'https://api.owncloud.com/v1/content/data',
[
'timeout' => 5,
'query' => [
'version' => implode('x', \OC_Util::getVersion()),
'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'));
}
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->getMock('\OCP\Http\Client\IResponse');
$response
->expects($this->once())
->method('getBody')
->will($this->returnValue('MyInvalidXml'));
$client = $this->getMock('\OCP\Http\Client\IClient');
$client
->expects($this->once())
->method('get')
->with(
'https://api.owncloud.com/v1/content/data',
[
'timeout' => 5,
'query' => [
'version' => implode('x', \OC_Util::getVersion()),
'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'));
}
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->getMock('\OCP\Http\Client\IResponse');
$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&amp;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&amp;id=1</downloadlink1>
<downloadgpgsignature1></downloadgpgsignature1>
<downloadgpgfingerprint1></downloadgpgfingerprint1>
<downloadpackagename1></downloadpackagename1>
<downloadrepository1></downloadrepository1>
<downloadname1></downloadname1>
<downloadsize1>1409</downloadsize1>
</content>
</data>
</ocs> '));
$client = $this->getMock('\OCP\Http\Client\IClient');
$client
->expects($this->once())
->method('get')
->with(
'https://api.owncloud.com/v1/content/data',
[
'timeout' => 5,
'query' => [
'version' => implode('x', \OC_Util::getVersion()),
'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,
],
[
'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,
],
];
$this->assertEquals($expected, $this->ocsClient->getApplications([815, 1337], 1, 'approved'));
}
public function tesGetApplicationDisabledAppStore() {
$this->config
->expects($this->once())
->method('getSystemValue')
->with('appstoreenabled', true)
->will($this->returnValue(false));
$this->assertNull($this->ocsClient->getApplication('MyId'));
}
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->getMock('\OCP\Http\Client\IClient');
$client
->expects($this->once())
->method('get')
->with(
'https://api.owncloud.com/v1/content/data/MyId',
[
'timeout' => 5,
]
)
->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'));
}
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->getMock('\OCP\Http\Client\IResponse');
$response
->expects($this->once())
->method('getBody')
->will($this->returnValue('MyInvalidXml'));
$client = $this->getMock('\OCP\Http\Client\IClient');
$client
->expects($this->once())
->method('get')
->with(
'https://api.owncloud.com/v1/content/data/MyId',
[
'timeout' => 5,
]
)
->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'));
}
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->getMock('\OCP\Http\Client\IResponse');
$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&amp;content=166053</fanspage>
<knowledgebaseentries>0</knowledgebaseentries>
<knowledgebasepage>http://apps.owncloud.com/content/show.php?action=knowledgebase&amp;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&amp;id=1</downloadlink1>
<downloadname1></downloadname1>
<downloadgpgfingerprint1></downloadgpgfingerprint1>
<downloadgpgsignature1></downloadgpgsignature1>
<downloadpackagename1></downloadpackagename1>
<downloadrepository1></downloadrepository1>
<downloadsize1>1</downloadsize1>
</content>
</data>
</ocs>
'));
$client = $this->getMock('\OCP\Http\Client\IClient');
$client
->expects($this->once())
->method('get')
->with(
'https://api.owncloud.com/v1/content/data/MyId',
[
'timeout' => 5,
]
)
->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',
'detailpage' => 'https://apps.owncloud.com/content/show.php?content=166053',
'preview1' => '',
'preview2' => '',
'preview3' => '',
'changed' => 1404743680,
'description' => 'Placeholder for future updates',
'score' => 50,
];
$this->assertSame($expected, $this->ocsClient->getApplication('MyId'));
}
public function testGetApplicationDownloadDisabledAppStore() {
$this->config
->expects($this->once())
->method('getSystemValue')
->with('appstoreenabled', true)
->will($this->returnValue(false));
$this->assertNull($this->ocsClient->getApplicationDownload('MyId'));
}
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->getMock('\OCP\Http\Client\IClient');
$client
->expects($this->once())
->method('get')
->with(
'https://api.owncloud.com/v1/content/download/MyId/1',
[
'timeout' => 5,
]
)
->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'));
}
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->getMock('\OCP\Http\Client\IResponse');
$response
->expects($this->once())
->method('getBody')
->will($this->returnValue('MyInvalidXml'));
$client = $this->getMock('\OCP\Http\Client\IClient');
$client
->expects($this->once())
->method('get')
->with(
'https://api.owncloud.com/v1/content/download/MyId/1',
[
'timeout' => 5,
]
)
->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'));
}
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->getMock('\OCP\Http\Client\IResponse');
$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->getMock('\OCP\Http\Client\IClient');
$client
->expects($this->once())
->method('get')
->with(
'https://api.owncloud.com/v1/content/download/MyId/1',
[
'timeout' => 5,
]
)
->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'));
}
}

View File

@ -0,0 +1,231 @@
<?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 OC\Settings\Controller;
use OCP\AppFramework\Http\ContentSecurityPolicy;
use OCP\AppFramework\Http\DataResponse;
use OCP\AppFramework\Http\TemplateResponse;
use Test\TestCase;
use OCP\IRequest;
use OCP\IL10N;
use OCP\IConfig;
use OCP\ICache;
use OCP\INavigationManager;
use OCP\App\IAppManager;
use OC\OCSClient;
/**
* Class AppSettingsControllerTest
*
* @package OC\Settings\Controller
*/
class AppSettingsControllerTest extends TestCase {
/** @var AppSettingsController */
private $appSettingsController;
/** @var IRequest */
private $request;
/** @var IL10N */
private $l10n;
/** @var IConfig */
private $config;
/** @var ICache */
private $cache;
/** @var INavigationManager */
private $navigationManager;
/** @var IAppManager */
private $appManager;
/** @var OCSClient */
private $ocsClient;
public function setUp() {
parent::setUp();
$this->request = $this->getMockBuilder('\OCP\IRequest')
->disableOriginalConstructor()->getMock();
$this->l10n = $this->getMockBuilder('\OCP\IL10N')
->disableOriginalConstructor()->getMock();
$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();
$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->appSettingsController = new AppSettingsController(
'settings',
$this->request,
$this->l10n,
$this->config,
$cacheFactory,
$this->navigationManager,
$this->appManager,
$this->ocsClient
);
}
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 = [
[
'id' => 0,
'displayName' => 'Enabled',
],
[
'id' => 1,
'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,
'displayName' => 'Enabled',
],
[
'id' => 1,
'displayName' => 'Not enabled',
],
[
'id' => 0,
'displayName' => 'Tools',
],
[
'id' => 1,
'displayName' => 'Awesome Games',
],
[
'id' => 2,
'displayName' => 'PIM',
],
[
'id' => 3,
'displayName' => 'Papershop',
],
];
$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->ocsClient
->expects($this->once())
->method('isAppStoreEnabled')
->will($this->returnValue(true));
$this->ocsClient
->expects($this->once())
->method('getCategories')
->will($this->returnValue(
[
'ownCloud Tools',
'Awesome Games',
'ownCloud PIM',
'Papershop',
]
));
$this->assertSame($expected, $this->appSettingsController->listCategories());
}
public function testViewApps() {
$this->config
->expects($this->once())
->method('getSystemValue')
->with('appstore.experimental.enabled', false);
$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], 'user');
$expected->setContentSecurityPolicy($policy);
$this->assertEquals($expected, $this->appSettingsController->viewApps());
}
}