diff options
Diffstat (limited to 'lib')
-rw-r--r-- | lib/private/integritycheck/checker.php | 449 | ||||
-rw-r--r-- | lib/private/integritycheck/exceptions/invalidsignatureexception.php | 30 | ||||
-rw-r--r-- | lib/private/integritycheck/helpers/applocator.php | 56 | ||||
-rw-r--r-- | lib/private/integritycheck/helpers/environmenthelper.php | 39 | ||||
-rw-r--r-- | lib/private/integritycheck/helpers/fileaccesshelper.php | 61 | ||||
-rw-r--r-- | lib/private/integritycheck/iterator/excludefilebynamefilteriterator.php | 58 | ||||
-rw-r--r-- | lib/private/integritycheck/iterator/excludefoldersbypathfilteriterator.php | 51 | ||||
-rw-r--r-- | lib/private/server.php | 31 | ||||
-rw-r--r-- | lib/private/templatelayout.php | 15 | ||||
-rw-r--r-- | lib/private/updater.php | 15 |
10 files changed, 802 insertions, 3 deletions
diff --git a/lib/private/integritycheck/checker.php b/lib/private/integritycheck/checker.php new file mode 100644 index 00000000000..edfe6b082e7 --- /dev/null +++ b/lib/private/integritycheck/checker.php @@ -0,0 +1,449 @@ +<?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\IntegrityCheck; + +use OC\IntegrityCheck\Exceptions\InvalidSignatureException; +use OC\IntegrityCheck\Helpers\AppLocator; +use OC\IntegrityCheck\Helpers\EnvironmentHelper; +use OC\IntegrityCheck\Helpers\FileAccessHelper; +use OC\Integritycheck\Iterator\ExcludeFileByNameFilterIterator; +use OC\IntegrityCheck\Iterator\ExcludeFoldersByPathFilterIterator; +use OCP\App\IAppManager; +use OCP\ICache; +use OCP\ICacheFactory; +use OCP\IConfig; +use phpseclib\Crypt\RSA; +use phpseclib\File\X509; + +/** + * Class Checker handles the code signing using X.509 and RSA. ownCloud ships with + * a public root certificate certificate that allows to issue new certificates that + * will be trusted for signing code. The CN will be used to verify that a certificate + * given to a third-party developer may not be used for other applications. For + * example the author of the application "calendar" would only receive a certificate + * only valid for this application. + * + * @package OC\IntegrityCheck + */ +class Checker { + const CACHE_KEY = 'oc.integritycheck.checker'; + /** @var EnvironmentHelper */ + private $environmentHelper; + /** @var AppLocator */ + private $appLocator; + /** @var FileAccessHelper */ + private $fileAccessHelper; + /** @var IConfig */ + private $config; + /** @var ICache */ + private $cache; + /** @var IAppManager */ + private $appManager; + + /** + * @param EnvironmentHelper $environmentHelper + * @param FileAccessHelper $fileAccessHelper + * @param AppLocator $appLocator + * @param IConfig $config + * @param ICacheFactory $cacheFactory + * @param IAppManager $appManager + */ + public function __construct(EnvironmentHelper $environmentHelper, + FileAccessHelper $fileAccessHelper, + AppLocator $appLocator, + IConfig $config = null, + ICacheFactory $cacheFactory, + IAppManager $appManager = null) { + $this->environmentHelper = $environmentHelper; + $this->fileAccessHelper = $fileAccessHelper; + $this->appLocator = $appLocator; + $this->config = $config; + $this->cache = $cacheFactory->create(self::CACHE_KEY); + $this->appManager = $appManager; + } + + /** + * Enumerates all files belonging to the folder. Sensible defaults are excluded. + * + * @param string $folderToIterate + * @return \RecursiveIteratorIterator + */ + private function getFolderIterator($folderToIterate) { + $dirItr = new \RecursiveDirectoryIterator( + $folderToIterate, + \RecursiveDirectoryIterator::SKIP_DOTS + ); + $excludeGenericFilesIterator = new ExcludeFileByNameFilterIterator($dirItr); + $excludeFoldersIterator = new ExcludeFoldersByPathFilterIterator($excludeGenericFilesIterator); + + return new \RecursiveIteratorIterator( + $excludeFoldersIterator, + \RecursiveIteratorIterator::SELF_FIRST + ); + } + + /** + * Returns an array of ['filename' => 'SHA512-hash-of-file'] for all files found + * in the iterator. + * + * @param \RecursiveIteratorIterator $iterator + * @param string $path + * @return array Array of hashes. + */ + private function generateHashes(\RecursiveIteratorIterator $iterator, + $path) { + $hashes = []; + + $baseDirectoryLength = strlen($path); + foreach($iterator as $filename => $data) { + /** @var \DirectoryIterator $data */ + if($data->isDir()) { + continue; + } + + $relativeFileName = substr($filename, $baseDirectoryLength); + + // Exclude signature.json files in the appinfo and root folder + if($relativeFileName === '/appinfo/signature.json') { + continue; + } + // Exclude signature.json files in the appinfo and core folder + if($relativeFileName === '/core/signature.json') { + continue; + } + + $hashes[$relativeFileName] = hash_file('sha512', $filename); + } + return $hashes; + } + + /** + * Creates the signature data + * + * @param array $hashes + * @param X509 $certificate + * @param RSA $privateKey + * @return string + */ + private function createSignatureData(array $hashes, + X509 $certificate, + RSA $privateKey) { + ksort($hashes); + + $privateKey->setSignatureMode(RSA::SIGNATURE_PSS); + $privateKey->setMGFHash('sha512'); + $signature = $privateKey->sign(json_encode($hashes)); + + return [ + 'hashes' => $hashes, + 'signature' => base64_encode($signature), + 'certificate' => $certificate->saveX509($certificate->currentCert), + ]; + } + + /** + * Write the signature of the specified app + * + * @param string $appId + * @param X509 $certificate + * @param RSA $privateKey + * @throws \Exception + */ + public function writeAppSignature($appId, + X509 $certificate, + RSA $privateKey) { + $path = $this->appLocator->getAppPath($appId); + $iterator = $this->getFolderIterator($path); + $hashes = $this->generateHashes($iterator, $path); + $signature = $this->createSignatureData($hashes, $certificate, $privateKey); + $this->fileAccessHelper->file_put_contents( + $path . '/appinfo/signature.json', + json_encode($signature, JSON_PRETTY_PRINT) + ); + } + + /** + * Write the signature of core + * + * @param X509 $certificate + * @param RSA $rsa + */ + public function writeCoreSignature(X509 $certificate, + RSA $rsa) { + $iterator = $this->getFolderIterator($this->environmentHelper->getServerRoot()); + $hashes = $this->generateHashes($iterator, $this->environmentHelper->getServerRoot()); + $signatureData = $this->createSignatureData($hashes, $certificate, $rsa); + $this->fileAccessHelper->file_put_contents( + $this->environmentHelper->getServerRoot() . '/core/signature.json', + json_encode($signatureData, JSON_PRETTY_PRINT) + ); + } + + /** + * Verifies the signature for the specified path. + * + * @param string $signaturePath + * @param string $basePath + * @param string $certificateCN + * @return array + * @throws InvalidSignatureException + * @throws \Exception + */ + private function verify($signaturePath, $basePath, $certificateCN) { + $signatureData = json_decode($this->fileAccessHelper->file_get_contents($signaturePath), true); + if(!is_array($signatureData)) { + throw new InvalidSignatureException('Signature data not found.'); + } + + $expectedHashes = $signatureData['hashes']; + ksort($expectedHashes); + $signature = base64_decode($signatureData['signature']); + $certificate = $signatureData['certificate']; + + // Check if certificate is signed by ownCloud Root Authority + $x509 = new \phpseclib\File\X509(); + $rootCertificatePublicKey = $this->fileAccessHelper->file_get_contents($this->environmentHelper->getServerRoot().'/resources/codesigning/root.crt'); + $x509->loadCA($rootCertificatePublicKey); + $x509->loadX509($certificate); + if(!$x509->validateSignature()) { + throw new InvalidSignatureException('Certificate is not valid.'); + } + // Verify if certificate has proper CN. "core" CN is always trusted. + if($x509->getDN(X509::DN_OPENSSL)['CN'] !== $certificateCN && $x509->getDN(X509::DN_OPENSSL)['CN'] !== 'core') { + throw new InvalidSignatureException( + sprintf('Certificate is not valid for required scope. (Requested: %s, current: %s)', $certificateCN, $x509->getDN(true)) + ); + } + + // Check if the signature of the files is valid + $rsa = new \phpseclib\Crypt\RSA(); + $rsa->loadKey($x509->currentCert['tbsCertificate']['subjectPublicKeyInfo']['subjectPublicKey']); + $rsa->setSignatureMode(RSA::SIGNATURE_PSS); + $rsa->setMGFHash('sha512'); + if(!$rsa->verify(json_encode($expectedHashes), $signature)) { + throw new InvalidSignatureException('Signature could not get verified.'); + } + + // Compare the list of files which are not identical + $currentInstanceHashes = $this->generateHashes($this->getFolderIterator($basePath), $basePath); + $differencesA = array_diff($expectedHashes, $currentInstanceHashes); + $differencesB = array_diff($currentInstanceHashes, $expectedHashes); + $differences = array_unique(array_merge($differencesA, $differencesB)); + $differenceArray = []; + foreach($differences as $filename => $hash) { + // Check if file should not exist in the new signature table + if(!array_key_exists($filename, $expectedHashes)) { + $differenceArray['EXTRA_FILE'][$filename]['expected'] = ''; + $differenceArray['EXTRA_FILE'][$filename]['current'] = $hash; + continue; + } + + // Check if file is missing + if(!array_key_exists($filename, $currentInstanceHashes)) { + $differenceArray['FILE_MISSING'][$filename]['expected'] = $expectedHashes[$filename]; + $differenceArray['FILE_MISSING'][$filename]['current'] = ''; + continue; + } + + // Check if hash does mismatch + if($expectedHashes[$filename] !== $currentInstanceHashes[$filename]) { + $differenceArray['INVALID_HASH'][$filename]['expected'] = $expectedHashes[$filename]; + $differenceArray['INVALID_HASH'][$filename]['current'] = $currentInstanceHashes[$filename]; + continue; + } + + // Should never happen. + throw new \Exception('Invalid behaviour in file hash comparison experienced. Please report this error to the developers.'); + } + + return $differenceArray; + } + + /** + * Whether the code integrity check has passed successful or not + * + * @return bool + */ + public function hasPassedCheck() { + $results = $this->getResults(); + if(empty($results)) { + return true; + } + + return false; + } + + /** + * @return array + */ + public function getResults() { + $cachedResults = $this->cache->get(self::CACHE_KEY); + if(!is_null($cachedResults)) { + return json_decode($cachedResults, true); + } + + return json_decode($this->config->getAppValue('core', self::CACHE_KEY, '{}'), true); + } + + /** + * Stores the results in the app config as well as cache + * + * @param string $scope + * @param array $result + */ + private function storeResults($scope, array $result) { + $resultArray = $this->getResults(); + unset($resultArray[$scope]); + if(!empty($result)) { + $resultArray[$scope] = $result; + } + $this->config->setAppValue('core', self::CACHE_KEY, json_encode($resultArray)); + $this->cache->set(self::CACHE_KEY, json_encode($resultArray)); + } + + + /** + * Verify the signature of $appId. Returns an array with the following content: + * [ + * 'FILE_MISSING' => + * [ + * 'filename' => [ + * 'expected' => 'expectedSHA512', + * 'current' => 'currentSHA512', + * ], + * ], + * 'EXTRA_FILE' => + * [ + * 'filename' => [ + * 'expected' => 'expectedSHA512', + * 'current' => 'currentSHA512', + * ], + * ], + * 'INVALID_HASH' => + * [ + * 'filename' => [ + * 'expected' => 'expectedSHA512', + * 'current' => 'currentSHA512', + * ], + * ], + * ] + * + * Array may be empty in case no problems have been found. + * + * @param string $appId + * @return array + */ + public function verifyAppSignature($appId) { + try { + $path = $this->appLocator->getAppPath($appId); + $result = $this->verify( + $path . '/appinfo/signature.json', + $path, + $appId + ); + } catch (\Exception $e) { + $result = [ + 'EXCEPTION' => [ + 'class' => get_class($e), + 'message' => $e->getMessage(), + ], + ]; + } + $this->storeResults($appId, $result); + + return $result; + } + + /** + * Verify the signature of core. Returns an array with the following content: + * [ + * 'FILE_MISSING' => + * [ + * 'filename' => [ + * 'expected' => 'expectedSHA512', + * 'current' => 'currentSHA512', + * ], + * ], + * 'EXTRA_FILE' => + * [ + * 'filename' => [ + * 'expected' => 'expectedSHA512', + * 'current' => 'currentSHA512', + * ], + * ], + * 'INVALID_HASH' => + * [ + * 'filename' => [ + * 'expected' => 'expectedSHA512', + * 'current' => 'currentSHA512', + * ], + * ], + * ] + * + * Array may be empty in case no problems have been found. + * + * @return array + */ + public function verifyCoreSignature() { + try { + $result = $this->verify( + $this->environmentHelper->getServerRoot() . '/core/signature.json', + $this->environmentHelper->getServerRoot(), + 'core' + ); + } catch (\Exception $e) { + $result = [ + 'EXCEPTION' => [ + 'class' => get_class($e), + 'message' => $e->getMessage(), + ], + ]; + } + $this->storeResults('core', $result); + + return $result; + } + + /** + * Verify the core code of the instance as well as all applicable applications + * and store the results. + */ + public function runInstanceVerification() { + $this->verifyCoreSignature(); + $appIds = $this->appLocator->getAllApps(); + foreach($appIds as $appId) { + // If an application is shipped a valid signature is required + $isShipped = $this->appManager->isShipped($appId); + $appNeedsToBeChecked = false; + if ($isShipped) { + $appNeedsToBeChecked = true; + } elseif ($this->fileAccessHelper->file_exists($this->appLocator->getAppPath($appId) . '/appinfo/signature.json')) { + // Otherwise only if the application explicitly ships a signature.json file + $appNeedsToBeChecked = true; + } + + if($appNeedsToBeChecked) { + $this->verifyAppSignature($appId); + } + } + } +} diff --git a/lib/private/integritycheck/exceptions/invalidsignatureexception.php b/lib/private/integritycheck/exceptions/invalidsignatureexception.php new file mode 100644 index 00000000000..9e05e5884f5 --- /dev/null +++ b/lib/private/integritycheck/exceptions/invalidsignatureexception.php @@ -0,0 +1,30 @@ +<?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\IntegrityCheck\Exceptions; + +/** + * Class InvalidSignatureException is thrown in case the signature of the hashes + * cannot be properly validated. This indicates that either files + * + * @package OC\IntegrityCheck\Exceptions + */ +class InvalidSignatureException extends \Exception {} diff --git a/lib/private/integritycheck/helpers/applocator.php b/lib/private/integritycheck/helpers/applocator.php new file mode 100644 index 00000000000..b732cb80893 --- /dev/null +++ b/lib/private/integritycheck/helpers/applocator.php @@ -0,0 +1,56 @@ +<?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\IntegrityCheck\Helpers; + +/** + * Class AppLocator provides a non-static helper for OC_App::getPath($appId) + * it is not possible to use IAppManager at this point as IAppManager has a + * dependency on a running ownCloud. + * + * @package OC\IntegrityCheck\Helpers + */ +class AppLocator { + /** + * Provides \OC_App::getAppPath($appId) + * + * @param string $appId + * @return string + * @throws \Exception If the app cannot be found + */ + public function getAppPath($appId) { + $path = \OC_App::getAppPath($appId); + if($path === false) { + + throw new \Exception('App not found'); + } + return $path; + } + + /** + * Providers \OC_App::getAllApps() + * + * @return array + */ + public function getAllApps() { + return \OC_App::getAllApps(); + } +} diff --git a/lib/private/integritycheck/helpers/environmenthelper.php b/lib/private/integritycheck/helpers/environmenthelper.php new file mode 100644 index 00000000000..d7747dbb966 --- /dev/null +++ b/lib/private/integritycheck/helpers/environmenthelper.php @@ -0,0 +1,39 @@ +<?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\IntegrityCheck\Helpers; + +/** + * Class EnvironmentHelper provides a non-static helper for access to static + * variables such as \OC::$SERVERROOT. + * + * @package OC\IntegrityCheck\Helpers + */ +class EnvironmentHelper { + /** + * Provides \OC::$SERVERROOT + * + * @return string + */ + public function getServerRoot() { + return \OC::$SERVERROOT; + } +} diff --git a/lib/private/integritycheck/helpers/fileaccesshelper.php b/lib/private/integritycheck/helpers/fileaccesshelper.php new file mode 100644 index 00000000000..23f592122dc --- /dev/null +++ b/lib/private/integritycheck/helpers/fileaccesshelper.php @@ -0,0 +1,61 @@ +<?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\IntegrityCheck\Helpers; + +/** + * Class FileAccessHelper provides a helper around file_get_contents and + * file_put_contents + * + * @package OC\IntegrityCheck\Helpers + */ +class FileAccessHelper { + /** + * Wrapper around file_get_contents($filename, $data) + * + * @param string $filename + * @return string|false + */ + public function file_get_contents($filename) { + return file_get_contents($filename); + } + + /** + * Wrapper around file_exists($filename) + * + * @param string $filename + * @return bool + */ + public function file_exists($filename) { + return file_exists($filename); + } + + /** + * Wrapper around file_put_contents($filename, $data) + * + * @param string $filename + * @param $data + * @return int|false + */ + public function file_put_contents($filename, $data) { + return file_put_contents($filename, $data); + } +} diff --git a/lib/private/integritycheck/iterator/excludefilebynamefilteriterator.php b/lib/private/integritycheck/iterator/excludefilebynamefilteriterator.php new file mode 100644 index 00000000000..c75554a7cc9 --- /dev/null +++ b/lib/private/integritycheck/iterator/excludefilebynamefilteriterator.php @@ -0,0 +1,58 @@ +<?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\IntegrityCheck\Iterator; + +/** + * Class ExcludeFileByNameFilterIterator provides a custom iterator which excludes + * entries with the specified file name from the file list. + * + * @package OC\Integritycheck\Iterator + */ +class ExcludeFileByNameFilterIterator extends \RecursiveFilterIterator { + /** + * Array of excluded file names. Those are not scanned by the integrity checker. + * This is used to exclude files which administrators could upload by mistakes + * such as .DS_Store files. + * + * @var array + */ + private $excludedFilenames = [ + '.DS_Store', // Mac OS X + 'Thumbs.db', // Microsoft Windows + '.directory', // Dolphin (KDE) + ]; + + /** + * @return bool + */ + public function accept() { + if($this->isDir()) { + return true; + } + + return !in_array( + $this->current()->getFilename(), + $this->excludedFilenames, + true + ); + } +} diff --git a/lib/private/integritycheck/iterator/excludefoldersbypathfilteriterator.php b/lib/private/integritycheck/iterator/excludefoldersbypathfilteriterator.php new file mode 100644 index 00000000000..43f19475862 --- /dev/null +++ b/lib/private/integritycheck/iterator/excludefoldersbypathfilteriterator.php @@ -0,0 +1,51 @@ +<?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\IntegrityCheck\Iterator; + +class ExcludeFoldersByPathFilterIterator extends \RecursiveFilterIterator { + private $excludedFolders = []; + + public function __construct(\RecursiveIterator $iterator) { + parent::__construct($iterator); + + $appFolders = \OC::$APPSROOTS; + foreach($appFolders as $key => $appFolder) { + $appFolders[$key] = rtrim($appFolder['path'], '/'); + } + + $this->excludedFolders = array_merge([ + rtrim(\OC::$server->getConfig()->getSystemValue('datadirectory', \OC::$SERVERROOT . '/data'), '/'), + rtrim(\OC::$SERVERROOT.'/themes', '/'), + ], $appFolders); + } + + /** + * @return bool + */ + public function accept() { + return !in_array( + $this->current()->getPathName(), + $this->excludedFolders, + true + ); + } +} diff --git a/lib/private/server.php b/lib/private/server.php index de3324d2cce..fe0d9dcf55d 100644 --- a/lib/private/server.php +++ b/lib/private/server.php @@ -51,6 +51,10 @@ use OC\Files\Node\HookConnector; use OC\Files\Node\Root; use OC\Files\View; use OC\Http\Client\ClientService; +use OC\IntegrityCheck\Checker; +use OC\IntegrityCheck\Helpers\AppLocator; +use OC\IntegrityCheck\Helpers\EnvironmentHelper; +use OC\IntegrityCheck\Helpers\FileAccessHelper; use OC\Lock\DBLockingProvider; use OC\Lock\MemcacheLockingProvider; use OC\Lock\NoopLockingProvider; @@ -409,6 +413,26 @@ class Server extends SimpleContainer implements IServerContainer { $this->registerService('TrustedDomainHelper', function ($c) { return new TrustedDomainHelper($this->getConfig()); }); + $this->registerService('IntegrityCodeChecker', function (Server $c) { + // IConfig and IAppManager requires a working database. This code + // might however be called when ownCloud is not yet setup. + if(\OC::$server->getSystemConfig()->getValue('installed', false)) { + $config = $c->getConfig(); + $appManager = $c->getAppManager(); + } else { + $config = null; + $appManager = null; + } + + return new Checker( + new EnvironmentHelper(), + new FileAccessHelper(), + new AppLocator(), + $config, + $c->getMemCacheFactory(), + $appManager + ); + }); $this->registerService('Request', function ($c) { if (isset($this['urlParams'])) { $urlParams = $this['urlParams']; @@ -1094,6 +1118,13 @@ class Server extends SimpleContainer implements IServerContainer { } /** + * @return \OC\IntegrityCheck\Checker + */ + public function getIntegrityCodeChecker() { + return $this->query('IntegrityCodeChecker'); + } + + /** * @return \OC\Session\CryptoWrapper */ public function getSessionCryptoWrapper() { diff --git a/lib/private/templatelayout.php b/lib/private/templatelayout.php index f5974128b73..1a6a07ddc9f 100644 --- a/lib/private/templatelayout.php +++ b/lib/private/templatelayout.php @@ -78,8 +78,12 @@ class OC_TemplateLayout extends OC_Template { // Update notification if($this->config->getSystemValue('updatechecker', true) === true && OC_User::isAdminUser(OC_User::getUser())) { - $updater = new \OC\Updater(\OC::$server->getHTTPHelper(), - \OC::$server->getConfig(), \OC::$server->getLogger()); + $updater = new \OC\Updater( + \OC::$server->getHTTPHelper(), + \OC::$server->getConfig(), + \OC::$server->getIntegrityCodeChecker(), + \OC::$server->getLogger() + ); $data = $updater->check(); if(isset($data['version']) && $data['version'] != '' and $data['version'] !== Array()) { @@ -96,8 +100,13 @@ class OC_TemplateLayout extends OC_Template { $this->assign('updateAvailable', false); // Update check is disabled } - // Add navigation entry + // Code integrity notification + $integrityChecker = \OC::$server->getIntegrityCodeChecker(); + if(!$integrityChecker->hasPassedCheck()) { + \OCP\Util::addScript('core', 'integritycheck-failed-notification'); + } + // Add navigation entry $this->assign( 'application', ''); $this->assign( 'appid', $appId ); $navigation = OC_App::getNavigation(); diff --git a/lib/private/updater.php b/lib/private/updater.php index 1ff80863737..366ad2555a8 100644 --- a/lib/private/updater.php +++ b/lib/private/updater.php @@ -34,6 +34,8 @@ namespace OC; use OC\Hooks\BasicEmitter; +use OC\IntegrityCheck\Checker; +use OC\IntegrityCheck\Storage; use OC_App; use OC_Installer; use OC_Util; @@ -61,6 +63,9 @@ class Updater extends BasicEmitter { /** @var IConfig */ private $config; + /** @var Checker */ + private $checker; + /** @var bool */ private $simulateStepEnabled; @@ -81,14 +86,17 @@ class Updater extends BasicEmitter { /** * @param HTTPHelper $httpHelper * @param IConfig $config + * @param Checker $checker * @param ILogger $log */ public function __construct(HTTPHelper $httpHelper, IConfig $config, + Checker $checker, ILogger $log = null) { $this->httpHelper = $httpHelper; $this->log = $log; $this->config = $config; + $this->checker = $checker; $this->simulateStepEnabled = true; $this->updateStepEnabled = true; } @@ -335,6 +343,13 @@ class Updater extends BasicEmitter { //Invalidate update feed $this->config->setAppValue('core', 'lastupdatedat', 0); + // Check for code integrity on the stable channel + if(\OC_Util::getChannel() === 'stable') { + $this->emit('\OC\Updater', 'startCheckCodeIntegrity'); + $this->checker->runInstanceVerification(); + $this->emit('\OC\Updater', 'finishedCheckCodeIntegrity'); + } + // only set the final version if everything went well $this->config->setSystemValue('version', implode('.', \OC_Util::getVersion())); } |