diff options
121 files changed, 3412 insertions, 441 deletions
diff --git a/apps/encryption/controller/settingscontroller.php b/apps/encryption/controller/settingscontroller.php index 641c97e1d6e..2a668f7cd4a 100644 --- a/apps/encryption/controller/settingscontroller.php +++ b/apps/encryption/controller/settingscontroller.php @@ -100,10 +100,10 @@ class SettingsController extends Controller { if ($passwordCorrect !== false) { $encryptedKey = $this->keyManager->getPrivateKey($uid); - $decryptedKey = $this->crypt->decryptPrivateKey($encryptedKey, $oldPassword); + $decryptedKey = $this->crypt->decryptPrivateKey($encryptedKey, $oldPassword, $uid); if ($decryptedKey) { - $encryptedKey = $this->crypt->symmetricEncryptFileContent($decryptedKey, $newPassword); + $encryptedKey = $this->crypt->encryptPrivateKey($decryptedKey, $newPassword, $uid); $header = $this->crypt->generateHeader(); if ($encryptedKey) { $this->keyManager->setPrivateKey($uid, $header . $encryptedKey); diff --git a/apps/encryption/hooks/userhooks.php b/apps/encryption/hooks/userhooks.php index a86b8662781..8b6f17bec6d 100644 --- a/apps/encryption/hooks/userhooks.php +++ b/apps/encryption/hooks/userhooks.php @@ -220,8 +220,7 @@ class UserHooks implements IHook { if ($user && $params['uid'] === $user->getUID() && $privateKey) { // Encrypt private key with new user pwd as passphrase - $encryptedPrivateKey = $this->crypt->symmetricEncryptFileContent($privateKey, - $params['password']); + $encryptedPrivateKey = $this->crypt->encryptPrivateKey($privateKey, $params['password'], $params['uid']); // Save private key if ($encryptedPrivateKey) { @@ -259,8 +258,7 @@ class UserHooks implements IHook { $this->keyManager->setPublicKey($user, $keyPair['publicKey']); // Encrypt private key with new password - $encryptedKey = $this->crypt->symmetricEncryptFileContent($keyPair['privateKey'], - $newUserPassword); + $encryptedKey = $this->crypt->encryptPrivateKey($keyPair['privateKey'], $newUserPassword, $user); if ($encryptedKey) { $this->keyManager->setPrivateKey($user, $this->crypt->generateHeader() . $encryptedKey); diff --git a/apps/encryption/lib/crypto/crypt.php b/apps/encryption/lib/crypto/crypt.php index f3cf38fb96c..6c4c108f50a 100644 --- a/apps/encryption/lib/crypto/crypt.php +++ b/apps/encryption/lib/crypto/crypt.php @@ -30,6 +30,7 @@ use OC\Encryption\Exceptions\DecryptionFailedException; use OC\Encryption\Exceptions\EncryptionFailedException; use OCA\Encryption\Exceptions\MultiKeyDecryptException; use OCA\Encryption\Exceptions\MultiKeyEncryptException; +use OCA\Encryption\Vendor\PBKDF2Fallback; use OCP\Encryption\Exceptions\GenericEncryptionException; use OCP\IConfig; use OCP\ILogger; @@ -42,6 +43,10 @@ class Crypt { // default cipher from old ownCloud versions const LEGACY_CIPHER = 'AES-128-CFB'; + // default key format, old ownCloud version encrypted the private key directly + // with the user password + const LEGACY_KEY_FORMAT = 'password'; + const HEADER_START = 'HBEGIN'; const HEADER_END = 'HEND'; /** @@ -58,6 +63,11 @@ class Crypt { private $config; /** + * @var array + */ + private $supportedKeyFormats; + + /** * @param ILogger $logger * @param IUserSession $userSession * @param IConfig $config @@ -66,6 +76,7 @@ class Crypt { $this->logger = $logger; $this->user = $userSession && $userSession->isLoggedIn() ? $userSession->getUser() : false; $this->config = $config; + $this->supportedKeyFormats = ['hash', 'password']; } /** @@ -161,10 +172,23 @@ class Crypt { /** * generate header for encrypted file + * + * @param string $keyFormat (can be 'hash' or 'password') + * @return string + * @throws \InvalidArgumentException */ - public function generateHeader() { + public function generateHeader($keyFormat = 'hash') { + + if (in_array($keyFormat, $this->supportedKeyFormats, true) === false) { + throw new \InvalidArgumentException('key format "' . $keyFormat . '" is not supported'); + } + $cipher = $this->getCipher(); - $header = self::HEADER_START . ':cipher:' . $cipher . ':' . self::HEADER_END; + + $header = self::HEADER_START + . ':cipher:' . $cipher + . ':keyFormat:' . $keyFormat + . ':' . self::HEADER_END; return $header; } @@ -212,6 +236,25 @@ class Crypt { } /** + * get key size depending on the cipher + * + * @param string $cipher supported ('AES-256-CFB' and 'AES-128-CFB') + * @return int + * @throws \InvalidArgumentException + */ + protected function getKeySize($cipher) { + if ($cipher === 'AES-256-CFB') { + return 32; + } else if ($cipher === 'AES-128-CFB') { + return 16; + } + + throw new \InvalidArgumentException( + 'Wrong cipher defined only AES-128-CFB and AES-256-CFB are supported.' + ); + } + + /** * get legacy cipher * * @return string @@ -238,11 +281,71 @@ class Crypt { } /** + * generate password hash used to encrypt the users private key + * + * @param string $password + * @param string $cipher + * @param string $uid only used for user keys + * @return string + */ + protected function generatePasswordHash($password, $cipher, $uid = '') { + $instanceId = $this->config->getSystemValue('instanceid'); + $instanceSecret = $this->config->getSystemValue('secret'); + $salt = hash('sha256', $uid . $instanceId . $instanceSecret, true); + $keySize = $this->getKeySize($cipher); + + if (function_exists('hash_pbkdf2')) { + $hash = hash_pbkdf2( + 'sha256', + $password, + $salt, + 100000, + $keySize, + true + ); + } else { + // fallback to 3rdparty lib for PHP <= 5.4. + // FIXME: Can be removed as soon as support for PHP 5.4 was dropped + $fallback = new PBKDF2Fallback(); + $hash = $fallback->pbkdf2( + 'sha256', + $password, + $salt, + 100000, + $keySize, + true + ); + } + + return $hash; + } + + /** + * encrypt private key + * * @param string $privateKey * @param string $password + * @param string $uid for regular users, empty for system keys * @return bool|string */ - public function decryptPrivateKey($privateKey, $password = '') { + public function encryptPrivateKey($privateKey, $password, $uid = '') { + $cipher = $this->getCipher(); + $hash = $this->generatePasswordHash($password, $cipher, $uid); + $encryptedKey = $this->symmetricEncryptFileContent( + $privateKey, + $hash + ); + + return $encryptedKey; + } + + /** + * @param string $privateKey + * @param string $password + * @param string $uid for regular users, empty for system keys + * @return bool|string + */ + public function decryptPrivateKey($privateKey, $password = '', $uid = '') { $header = $this->parseHeader($privateKey); @@ -252,6 +355,16 @@ class Crypt { $cipher = self::LEGACY_CIPHER; } + if (isset($header['keyFormat'])) { + $keyFormat = $header['keyFormat']; + } else { + $keyFormat = self::LEGACY_KEY_FORMAT; + } + + if ($keyFormat === 'hash') { + $password = $this->generatePasswordHash($password, $cipher, $uid); + } + // If we found a header we need to remove it from the key we want to decrypt if (!empty($header)) { $privateKey = substr($privateKey, @@ -263,18 +376,29 @@ class Crypt { $password, $cipher); - // Check if this is a valid private key + if ($this->isValidPrivateKey($plainKey) === false) { + return false; + } + + return $plainKey; + } + + /** + * check if it is a valid private key + * + * @param $plainKey + * @return bool + */ + protected function isValidPrivateKey($plainKey) { $res = openssl_get_privatekey($plainKey); if (is_resource($res)) { $sslInfo = openssl_pkey_get_details($res); - if (!isset($sslInfo['key'])) { - return false; + if (isset($sslInfo['key'])) { + return true; } - } else { - return false; } - return $plainKey; + return true; } /** @@ -358,7 +482,7 @@ class Crypt { * @param $data * @return array */ - private function parseHeader($data) { + protected function parseHeader($data) { $result = []; if (substr($data, 0, strlen(self::HEADER_START)) === self::HEADER_START) { diff --git a/apps/encryption/lib/keymanager.php b/apps/encryption/lib/keymanager.php index 8c8c1f8fd78..6c793e5964f 100644 --- a/apps/encryption/lib/keymanager.php +++ b/apps/encryption/lib/keymanager.php @@ -146,7 +146,7 @@ class KeyManager { Encryption::ID); // Encrypt private key empty passphrase - $encryptedKey = $this->crypt->symmetricEncryptFileContent($keyPair['privateKey'], ''); + $encryptedKey = $this->crypt->encryptPrivateKey($keyPair['privateKey'], ''); $header = $this->crypt->generateHeader(); $this->setSystemPrivateKey($this->publicShareKeyId, $header . $encryptedKey); } @@ -184,8 +184,7 @@ class KeyManager { */ public function checkRecoveryPassword($password) { $recoveryKey = $this->keyStorage->getSystemUserKey($this->recoveryKeyId . '.privateKey', Encryption::ID); - $decryptedRecoveryKey = $this->crypt->decryptPrivateKey($recoveryKey, - $password); + $decryptedRecoveryKey = $this->crypt->decryptPrivateKey($recoveryKey, $password); if ($decryptedRecoveryKey) { return true; @@ -203,8 +202,8 @@ class KeyManager { // Save Public Key $this->setPublicKey($uid, $keyPair['publicKey']); - $encryptedKey = $this->crypt->symmetricEncryptFileContent($keyPair['privateKey'], - $password); + $encryptedKey = $this->crypt->encryptPrivateKey($keyPair['privateKey'], $password, $uid); + $header = $this->crypt->generateHeader(); if ($encryptedKey) { @@ -226,8 +225,7 @@ class KeyManager { $keyPair['publicKey'], Encryption::ID); - $encryptedKey = $this->crypt->symmetricEncryptFileContent($keyPair['privateKey'], - $password); + $encryptedKey = $this->crypt->encryptPrivateKey($keyPair['privateKey'], $password); $header = $this->crypt->generateHeader(); if ($encryptedKey) { @@ -308,8 +306,7 @@ class KeyManager { try { $privateKey = $this->getPrivateKey($uid); - $privateKey = $this->crypt->decryptPrivateKey($privateKey, - $passPhrase); + $privateKey = $this->crypt->decryptPrivateKey($privateKey, $passPhrase, $uid); } catch (PrivateKeyMissingException $e) { return false; } catch (DecryptionFailedException $e) { diff --git a/apps/encryption/lib/recovery.php b/apps/encryption/lib/recovery.php index e31a14fba63..b22e3265628 100644 --- a/apps/encryption/lib/recovery.php +++ b/apps/encryption/lib/recovery.php @@ -136,7 +136,7 @@ class Recovery { public function changeRecoveryKeyPassword($newPassword, $oldPassword) { $recoveryKey = $this->keyManager->getSystemPrivateKey($this->keyManager->getRecoveryKeyId()); $decryptedRecoveryKey = $this->crypt->decryptPrivateKey($recoveryKey, $oldPassword); - $encryptedRecoveryKey = $this->crypt->symmetricEncryptFileContent($decryptedRecoveryKey, $newPassword); + $encryptedRecoveryKey = $this->crypt->encryptPrivateKey($decryptedRecoveryKey, $newPassword); $header = $this->crypt->generateHeader(); if ($encryptedRecoveryKey) { $this->keyManager->setSystemPrivateKey($this->keyManager->getRecoveryKeyId(), $header . $encryptedRecoveryKey); @@ -263,8 +263,7 @@ class Recovery { public function recoverUsersFiles($recoveryPassword, $user) { $encryptedKey = $this->keyManager->getSystemPrivateKey($this->keyManager->getRecoveryKeyId()); - $privateKey = $this->crypt->decryptPrivateKey($encryptedKey, - $recoveryPassword); + $privateKey = $this->crypt->decryptPrivateKey($encryptedKey, $recoveryPassword); $this->recoverAllFiles('/' . $user . '/files/', $privateKey, $user); } diff --git a/apps/encryption/tests/controller/SettingsControllerTest.php b/apps/encryption/tests/controller/SettingsControllerTest.php index ed8135b9c4d..d985c7d7d25 100644 --- a/apps/encryption/tests/controller/SettingsControllerTest.php +++ b/apps/encryption/tests/controller/SettingsControllerTest.php @@ -188,7 +188,7 @@ class SettingsControllerTest extends TestCase { $this->cryptMock ->expects($this->once()) - ->method('symmetricEncryptFileContent') + ->method('encryptPrivateKey') ->willReturn('encryptedKey'); $this->cryptMock diff --git a/apps/encryption/tests/hooks/UserHooksTest.php b/apps/encryption/tests/hooks/UserHooksTest.php index 921c924d015..aa16a4d8703 100644 --- a/apps/encryption/tests/hooks/UserHooksTest.php +++ b/apps/encryption/tests/hooks/UserHooksTest.php @@ -114,7 +114,7 @@ class UserHooksTest extends TestCase { ->willReturnOnConsecutiveCalls(true, false); $this->cryptMock->expects($this->exactly(4)) - ->method('symmetricEncryptFileContent') + ->method('encryptPrivateKey') ->willReturn(true); $this->cryptMock->expects($this->any()) diff --git a/apps/encryption/tests/lib/KeyManagerTest.php b/apps/encryption/tests/lib/KeyManagerTest.php index 0bac5e0341b..71b00cf254a 100644 --- a/apps/encryption/tests/lib/KeyManagerTest.php +++ b/apps/encryption/tests/lib/KeyManagerTest.php @@ -260,7 +260,7 @@ class KeyManagerTest extends TestCase { ->method('setSystemUserKey') ->willReturn(true); $this->cryptMock->expects($this->any()) - ->method('symmetricEncryptFileContent') + ->method('encryptPrivateKey') ->with($this->equalTo('privateKey'), $this->equalTo('pass')) ->willReturn('decryptedPrivateKey'); diff --git a/apps/encryption/tests/lib/RecoveryTest.php b/apps/encryption/tests/lib/RecoveryTest.php index 8d5d31af0b8..c0624a36be9 100644 --- a/apps/encryption/tests/lib/RecoveryTest.php +++ b/apps/encryption/tests/lib/RecoveryTest.php @@ -96,7 +96,7 @@ class RecoveryTest extends TestCase { ->method('decryptPrivateKey'); $this->cryptMock->expects($this->once()) - ->method('symmetricEncryptFileContent') + ->method('encryptPrivateKey') ->willReturn(true); $this->assertTrue($this->instance->changeRecoveryKeyPassword('password', diff --git a/apps/encryption/tests/lib/crypto/cryptTest.php b/apps/encryption/tests/lib/crypto/cryptTest.php index 14ed1513e1e..3c7767a8908 100644 --- a/apps/encryption/tests/lib/crypto/cryptTest.php +++ b/apps/encryption/tests/lib/crypto/cryptTest.php @@ -98,18 +98,41 @@ class cryptTest extends TestCase { /** - * test generateHeader + * test generateHeader with valid key formats + * + * @dataProvider dataTestGenerateHeader */ - public function testGenerateHeader() { + public function testGenerateHeader($keyFormat, $expected) { $this->config->expects($this->once()) ->method('getSystemValue') ->with($this->equalTo('cipher'), $this->equalTo('AES-256-CFB')) ->willReturn('AES-128-CFB'); - $this->assertSame('HBEGIN:cipher:AES-128-CFB:HEND', - $this->crypt->generateHeader() - ); + if ($keyFormat) { + $result = $this->crypt->generateHeader($keyFormat); + } else { + $result = $this->crypt->generateHeader(); + } + + $this->assertSame($expected, $result); + } + + /** + * test generateHeader with invalid key format + * + * @expectedException \InvalidArgumentException + */ + public function testGenerateHeaderInvalid() { + $this->crypt->generateHeader('unknown'); + } + + public function dataTestGenerateHeader() { + return [ + [null, 'HBEGIN:cipher:AES-128-CFB:keyFormat:hash:HEND'], + ['password', 'HBEGIN:cipher:AES-128-CFB:keyFormat:password:HEND'], + ['hash', 'HBEGIN:cipher:AES-128-CFB:keyFormat:hash:HEND'] + ]; } /** @@ -262,4 +285,82 @@ class cryptTest extends TestCase { } + /** + * test return values of valid ciphers + * + * @dataProvider dataTestGetKeySize + */ + public function testGetKeySize($cipher, $expected) { + $result = $this->invokePrivate($this->crypt, 'getKeySize', [$cipher]); + $this->assertSame($expected, $result); + } + + /** + * test exception if cipher is unknown + * + * @expectedException \InvalidArgumentException + */ + public function testGetKeySizeFailure() { + $this->invokePrivate($this->crypt, 'getKeySize', ['foo']); + } + + public function dataTestGetKeySize() { + return [ + ['AES-256-CFB', 32], + ['AES-128-CFB', 16], + ]; + } + + /** + * @dataProvider dataTestDecryptPrivateKey + */ + public function testDecryptPrivateKey($header, $privateKey, $expectedCipher, $isValidKey, $expected) { + /** @var \OCA\Encryption\Crypto\Crypt | \PHPUnit_Framework_MockObject_MockObject $crypt */ + $crypt = $this->getMockBuilder('OCA\Encryption\Crypto\Crypt') + ->setConstructorArgs( + [ + $this->logger, + $this->userSession, + $this->config + ] + ) + ->setMethods( + [ + 'parseHeader', + 'generatePasswordHash', + 'symmetricDecryptFileContent', + 'isValidPrivateKey' + ] + ) + ->getMock(); + + $crypt->expects($this->once())->method('parseHeader')->willReturn($header); + if (isset($header['keyFormat']) && $header['keyFormat'] === 'hash') { + $crypt->expects($this->once())->method('generatePasswordHash')->willReturn('hash'); + $password = 'hash'; + } else { + $crypt->expects($this->never())->method('generatePasswordHash'); + $password = 'password'; + } + + $crypt->expects($this->once())->method('symmetricDecryptFileContent') + ->with('privateKey', $password, $expectedCipher)->willReturn('key'); + $crypt->expects($this->once())->method('isValidPrivateKey')->willReturn($isValidKey); + + $result = $crypt->decryptPrivateKey($privateKey, 'password'); + + $this->assertSame($expected, $result); + } + + public function dataTestDecryptPrivateKey() { + return [ + [['cipher' => 'AES-128-CFB', 'keyFormat' => 'password'], 'HBEGIN:HENDprivateKey', 'AES-128-CFB', true, 'key'], + [['cipher' => 'AES-256-CFB', 'keyFormat' => 'password'], 'HBEGIN:HENDprivateKey', 'AES-256-CFB', true, 'key'], + [['cipher' => 'AES-256-CFB', 'keyFormat' => 'password'], 'HBEGIN:HENDprivateKey', 'AES-256-CFB', false, false], + [['cipher' => 'AES-256-CFB', 'keyFormat' => 'hash'], 'HBEGIN:HENDprivateKey', 'AES-256-CFB', true, 'key'], + [['cipher' => 'AES-256-CFB'], 'HBEGIN:HENDprivateKey', 'AES-256-CFB', true, 'key'], + [[], 'privateKey', 'AES-128-CFB', true, 'key'], + ]; + } + } diff --git a/apps/encryption/vendor/pbkdf2fallback.php b/apps/encryption/vendor/pbkdf2fallback.php new file mode 100644 index 00000000000..ca579f8e7dc --- /dev/null +++ b/apps/encryption/vendor/pbkdf2fallback.php @@ -0,0 +1,87 @@ +<?php +/* Note; This class can be removed as soon as we drop PHP 5.4 support. + * + * + * Password Hashing With PBKDF2 (http://crackstation.net/hashing-security.htm). + * Copyright (c) 2013, Taylor Hornby + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +namespace OCA\Encryption\Vendor; + +class PBKDF2Fallback { + + /* + * PBKDF2 key derivation function as defined by RSA's PKCS #5: https://www.ietf.org/rfc/rfc2898.txt + * $algorithm - The hash algorithm to use. Recommended: SHA256 + * $password - The password. + * $salt - A salt that is unique to the password. + * $count - Iteration count. Higher is better, but slower. Recommended: At least 1000. + * $key_length - The length of the derived key in bytes. + * $raw_output - If true, the key is returned in raw binary format. Hex encoded otherwise. + * Returns: A $key_length-byte key derived from the password and salt. + * + * Test vectors can be found here: https://www.ietf.org/rfc/rfc6070.txt + * + * This implementation of PBKDF2 was originally created by https://defuse.ca + * With improvements by http://www.variations-of-shadow.com + */ + public function pbkdf2($algorithm, $password, $salt, $count, $key_length, $raw_output = false) { + $algorithm = strtolower($algorithm); + if (!in_array($algorithm, hash_algos(), true)) + trigger_error('PBKDF2 ERROR: Invalid hash algorithm.', E_USER_ERROR); + if ($count <= 0 || $key_length <= 0) + trigger_error('PBKDF2 ERROR: Invalid parameters.', E_USER_ERROR); + + if (function_exists("hash_pbkdf2")) { + // The output length is in NIBBLES (4-bits) if $raw_output is false! + if (!$raw_output) { + $key_length = $key_length * 2; + } + return hash_pbkdf2($algorithm, $password, $salt, $count, $key_length, $raw_output); + } + + $hash_length = strlen(hash($algorithm, "", true)); + $block_count = ceil($key_length / $hash_length); + + $output = ""; + for ($i = 1; $i <= $block_count; $i++) { + // $i encoded as 4 bytes, big endian. + $last = $salt . pack("N", $i); + // first iteration + $last = $xorsum = hash_hmac($algorithm, $last, $password, true); + // perform the other $count - 1 iterations + for ($j = 1; $j < $count; $j++) { + $xorsum ^= ($last = hash_hmac($algorithm, $last, $password, true)); + } + $output .= $xorsum; + } + + if ($raw_output) + return substr($output, 0, $key_length); + else + return bin2hex(substr($output, 0, $key_length)); + } +} diff --git a/apps/files/appinfo/update.php b/apps/files/appinfo/update.php index 2691c05c348..ff1369bb1a5 100644 --- a/apps/files/appinfo/update.php +++ b/apps/files/appinfo/update.php @@ -94,3 +94,12 @@ if ($installedVersion === '1.1.9' && ( } } } + +/** + * migrate old constant DEBUG to new config value 'debug' + * + * TODO: remove this in ownCloud 8.3 + */ +if(defined('DEBUG') && DEBUG === true) { + \OC::$server->getConfig()->setSystemValue('debug', true); +} diff --git a/apps/files/appinfo/version b/apps/files/appinfo/version index 5ed5faa5f16..9ee1f786d50 100644 --- a/apps/files/appinfo/version +++ b/apps/files/appinfo/version @@ -1 +1 @@ -1.1.10 +1.1.11 diff --git a/apps/files/css/detailsView.css b/apps/files/css/detailsView.css index 76629cb790f..dafd8c24573 100644 --- a/apps/files/css/detailsView.css +++ b/apps/files/css/detailsView.css @@ -12,11 +12,11 @@ } #app-sidebar .thumbnail { - width: 50px; - height: 50px; + width: 75px; + height: 75px; float: left; margin-right: 10px; - background-size: 50px; + background-size: 75px; } #app-sidebar .ellipsis { @@ -27,7 +27,8 @@ #app-sidebar .fileName { font-size: 16px; - padding-top: 3px; + padding-top: 13px; + padding-bottom: 3px; } #app-sidebar .file-details { diff --git a/apps/files/css/files.css b/apps/files/css/files.css index 26ba86b28c8..d66eece94d9 100644 --- a/apps/files/css/files.css +++ b/apps/files/css/files.css @@ -275,6 +275,11 @@ table.multiselect thead th { font-weight: bold; border-bottom: 0; } + +#app-content.with-app-sidebar table.multiselect thead{ + margin-right: 27%; +} + table.multiselect #headerName { position: relative; width: 9999px; /* when we use 100%, the styling breaks on mobile … table styling */ diff --git a/apps/files/js/detailtabview.js b/apps/files/js/detailtabview.js index 67f8b535abd..b0e170bc4e7 100644 --- a/apps/files/js/detailtabview.js +++ b/apps/files/js/detailtabview.js @@ -88,6 +88,8 @@ }); DetailTabView._TAB_COUNT = 0; + OCA.Files = OCA.Files || {}; + OCA.Files.DetailTabView = DetailTabView; })(); diff --git a/apps/files/js/filelist.js b/apps/files/js/filelist.js index eb46f155269..e294e2f3c09 100644 --- a/apps/files/js/filelist.js +++ b/apps/files/js/filelist.js @@ -673,6 +673,7 @@ id: parseInt($el.attr('data-id'), 10), name: $el.attr('data-file'), mimetype: $el.attr('data-mime'), + mtime: parseInt($el.attr('data-mtime'), 10), type: $el.attr('data-type'), size: parseInt($el.attr('data-size'), 10), etag: $el.attr('data-etag'), diff --git a/apps/files/js/mainfileinfodetailview.js b/apps/files/js/mainfileinfodetailview.js index 6910e5f2be5..8bf22149841 100644 --- a/apps/files/js/mainfileinfodetailview.js +++ b/apps/files/js/mainfileinfodetailview.js @@ -125,8 +125,8 @@ path: this.model.getFullPath(), mime: this.model.get('mimetype'), etag: this.model.get('etag'), - x: 50, - y: 50, + x: 75, + y: 75, callback: function(previewUrl) { $iconDiv.css('background-image', 'url("' + previewUrl + '")'); } diff --git a/apps/files/tests/ajax_rename.php b/apps/files/tests/ajax_rename.php index a690c7dcb0c..8abde9094d9 100644 --- a/apps/files/tests/ajax_rename.php +++ b/apps/files/tests/ajax_rename.php @@ -116,7 +116,7 @@ class Test_OC_Files_App_Rename extends \Test\TestCase { $this->assertEquals('abcdef', $result['data']['etag']); $this->assertFalse(isset($result['data']['tags'])); $this->assertEquals('/', $result['data']['path']); - $icon = \OC_Helper::mimetypeIcon('dir'); + $icon = \OC_Helper::mimetypeIcon('dir-external'); $icon = substr($icon, 0, -3) . 'svg'; $this->assertEquals($icon, $result['data']['icon']); } diff --git a/apps/files/tests/js/filelistSpec.js b/apps/files/tests/js/filelistSpec.js index 7ed60084fa9..a6d72a88efd 100644 --- a/apps/files/tests/js/filelistSpec.js +++ b/apps/files/tests/js/filelistSpec.js @@ -98,6 +98,7 @@ describe('OCA.Files.FileList tests', function() { type: 'file', name: 'One.txt', mimetype: 'text/plain', + mtime: 123456789, size: 12, etag: 'abc', permissions: OC.PERMISSION_ALL @@ -106,6 +107,7 @@ describe('OCA.Files.FileList tests', function() { type: 'file', name: 'Two.jpg', mimetype: 'image/jpeg', + mtime: 234567890, size: 12049, etag: 'def', permissions: OC.PERMISSION_ALL @@ -114,6 +116,7 @@ describe('OCA.Files.FileList tests', function() { type: 'file', name: 'Three.pdf', mimetype: 'application/pdf', + mtime: 234560000, size: 58009, etag: '123', permissions: OC.PERMISSION_ALL @@ -122,6 +125,7 @@ describe('OCA.Files.FileList tests', function() { type: 'dir', name: 'somedir', mimetype: 'httpd/unix-directory', + mtime: 134560000, size: 250, etag: '456', permissions: OC.PERMISSION_ALL @@ -1722,6 +1726,7 @@ describe('OCA.Files.FileList tests', function() { id: 1, name: 'One.txt', mimetype: 'text/plain', + mtime: 123456789, type: 'file', size: 12, etag: 'abc', @@ -1732,6 +1737,7 @@ describe('OCA.Files.FileList tests', function() { type: 'file', name: 'Three.pdf', mimetype: 'application/pdf', + mtime: 234560000, size: 58009, etag: '123', permissions: OC.PERMISSION_ALL @@ -1741,6 +1747,7 @@ describe('OCA.Files.FileList tests', function() { type: 'dir', name: 'somedir', mimetype: 'httpd/unix-directory', + mtime: 134560000, size: 250, etag: '456', permissions: OC.PERMISSION_ALL @@ -1754,6 +1761,7 @@ describe('OCA.Files.FileList tests', function() { id: 1, name: 'One.txt', mimetype: 'text/plain', + mtime: 123456789, type: 'file', size: 12, etag: 'abc', @@ -1764,6 +1772,7 @@ describe('OCA.Files.FileList tests', function() { type: 'dir', name: 'somedir', mimetype: 'httpd/unix-directory', + mtime: 134560000, size: 250, etag: '456', permissions: OC.PERMISSION_ALL @@ -2330,4 +2339,24 @@ describe('OCA.Files.FileList tests', function() { }); }); }); + describe('elementToFile', function() { + var $tr; + + beforeEach(function() { + fileList.setFiles(testFiles); + $tr = fileList.findFileEl('One.txt'); + }); + + it('converts data attributes to file info structure', function() { + var fileInfo = fileList.elementToFile($tr); + expect(fileInfo.id).toEqual(1); + expect(fileInfo.name).toEqual('One.txt'); + expect(fileInfo.mtime).toEqual(123456789); + expect(fileInfo.etag).toEqual('abc'); + expect(fileInfo.permissions).toEqual(OC.PERMISSION_ALL); + expect(fileInfo.size).toEqual(12); + expect(fileInfo.mimetype).toEqual('text/plain'); + expect(fileInfo.type).toEqual('file'); + }); + }); }); diff --git a/apps/files_external/ajax/applicable.php b/apps/files_external/ajax/applicable.php index 1b93cc3a1aa..5ae91c8e182 100644 --- a/apps/files_external/ajax/applicable.php +++ b/apps/files_external/ajax/applicable.php @@ -37,8 +37,15 @@ if (isset($_GET['offset'])) { $offset = (int)$_GET['offset']; } -$groups = \OC_Group::getGroups($pattern, $limit, $offset); -$users = \OCP\User::getDisplayNames($pattern, $limit, $offset); +$groups = []; +foreach (\OC::$server->getGroupManager()->search($pattern, $limit, $offset) as $group) { + $groups[$group->getGID()] = $group->getGID(); +} + +$users = []; +foreach (\OC::$server->getUserManager()->searchDisplayName($pattern, $limit, $offset) as $user) { + $users[$user->getUID()] = $user->getDisplayName(); +} $results = array('groups' => $groups, 'users' => $users); diff --git a/apps/files_external/appinfo/routes.php b/apps/files_external/appinfo/routes.php index 213e7b28dc1..4462ad1f274 100644 --- a/apps/files_external/appinfo/routes.php +++ b/apps/files_external/appinfo/routes.php @@ -26,7 +26,7 @@ namespace OCA\Files_External\AppInfo; /** - * @var $this \OC\Route\Router + * @var $this \OCP\Route\IRouter **/ \OC_Mount_Config::$app->registerRoutes( $this, diff --git a/apps/files_external/js/settings.js b/apps/files_external/js/settings.js index 6bf0143f1c0..d3e20e38445 100644 --- a/apps/files_external/js/settings.js +++ b/apps/files_external/js/settings.js @@ -704,6 +704,7 @@ MountConfigListView.prototype = { var $tr = $target.closest('tr'); $el.find('tbody').append($tr.clone()); $el.find('tbody tr').last().find('.mountPoint input').val(''); + $tr.data('constructing', true); var selected = $target.find('option:selected').text(); var backend = $target.val(); $tr.find('.backend').text(selected); @@ -739,6 +740,9 @@ MountConfigListView.prototype = { $tr.removeAttr('id'); $target.remove(); addSelect2($tr.find('.applicableUsers'), this._userListLimit); + + $tr.removeData('constructing'); + this.saveStorageConfig($tr); }, _onSelectAuthMechanism: function(event) { @@ -753,6 +757,11 @@ MountConfigListView.prototype = { $.each(authMechanismConfiguration['configuration'], _.partial( this.writeParameterInput, $td, _, _, ['auth-param'] )); + + if ($tr.data('constructing') !== true) { + // row is ready, trigger recheck + this.saveStorageConfig($tr); + } }, writeParameterInput: function($td, parameter, placeholder, classes) { diff --git a/apps/files_external/lib/amazons3.php b/apps/files_external/lib/amazons3.php index 40f07b2b22a..7c6ac4cbdd5 100644 --- a/apps/files_external/lib/amazons3.php +++ b/apps/files_external/lib/amazons3.php @@ -372,7 +372,7 @@ class AmazonS3 extends \OC\Files\Storage\Common { switch ($mode) { case 'r': case 'rb': - $tmpFile = \OC_Helper::tmpFile(); + $tmpFile = \OCP\Files::tmpFile(); self::$tmpFiles[$tmpFile] = $path; try { @@ -404,7 +404,7 @@ class AmazonS3 extends \OC\Files\Storage\Common { } else { $ext = ''; } - $tmpFile = \OC_Helper::tmpFile($ext); + $tmpFile = \OCP\Files::tmpFile($ext); \OC\Files\Stream\Close::registerCallback($tmpFile, array($this, 'writeBack')); if ($this->file_exists($path)) { $source = $this->fopen($path, 'r'); @@ -464,7 +464,7 @@ class AmazonS3 extends \OC\Files\Storage\Common { ]); $this->testTimeout(); } else { - $mimeType = \OC_Helper::getMimetypeDetector()->detectPath($path); + $mimeType = \OC::$server->getMimeTypeDetector()->detectPath($path); $this->getConnection()->putObject([ 'Bucket' => $this->bucket, 'Key' => $this->cleanKey($path), @@ -629,7 +629,7 @@ class AmazonS3 extends \OC\Files\Storage\Common { 'Bucket' => $this->bucket, 'Key' => $this->cleanKey(self::$tmpFiles[$tmpFile]), 'SourceFile' => $tmpFile, - 'ContentType' => \OC_Helper::getMimeType($tmpFile), + 'ContentType' => \OC::$server->getMimeTypeDetector()->detect($tmpFile), 'ContentLength' => filesize($tmpFile) )); $this->testTimeout(); diff --git a/apps/files_external/lib/api.php b/apps/files_external/lib/api.php index b9435e33105..015c15c41ff 100644 --- a/apps/files_external/lib/api.php +++ b/apps/files_external/lib/api.php @@ -71,7 +71,7 @@ class Api { */ public static function getUserMounts($params) { $entries = array(); - $user = \OC_User::getUser(); + $user = \OC::$server->getUserSession()->getUser()->getUID(); $mounts = \OC_Mount_Config::getAbsoluteMountPoints($user); foreach($mounts as $mountPoint => $mount) { diff --git a/apps/files_external/lib/config.php b/apps/files_external/lib/config.php index 2a523144f7f..62932dc4028 100644 --- a/apps/files_external/lib/config.php +++ b/apps/files_external/lib/config.php @@ -86,10 +86,9 @@ class OC_Mount_Config { self::addStorageIdToConfig($data['user']); $user = \OC::$server->getUserManager()->get($data['user']); if (!$user) { - \OCP\Util::writeLog( - 'files_external', + \OC::$server->getLogger()->warning( 'Cannot init external mount points for non-existant user "' . $data['user'] . '".', - \OCP\Util::WARN + ['app' => 'files_external'] ); return; } @@ -281,10 +280,11 @@ class OC_Mount_Config { */ public static function readData($user = null) { if (isset($user)) { - $jsonFile = OC_User::getHome($user) . '/mount.json'; + $jsonFile = \OC::$server->getUserManager()->get($user)->getHome() . '/mount.json'; } else { - $datadir = \OC_Config::getValue('datadirectory', \OC::$SERVERROOT . '/data/'); - $jsonFile = \OC_Config::getValue('mount_file', $datadir . '/mount.json'); + $config = \OC::$server->getConfig(); + $datadir = $config->getSystemValue('datadirectory', \OC::$SERVERROOT . '/data/'); + $jsonFile = $config->getSystemValue('mount_file', $datadir . '/mount.json'); } if (is_file($jsonFile)) { $mountPoints = json_decode(file_get_contents($jsonFile), true); @@ -303,10 +303,11 @@ class OC_Mount_Config { */ public static function writeData($user, $data) { if (isset($user)) { - $file = OC_User::getHome($user) . '/mount.json'; + $file = \OC::$server->getUserManager()->get($user)->getHome() . '/mount.json'; } else { - $datadir = \OC_Config::getValue('datadirectory', \OC::$SERVERROOT . '/data/'); - $file = \OC_Config::getValue('mount_file', $datadir . '/mount.json'); + $config = \OC::$server->getConfig(); + $datadir = $config->getSystemValue('datadirectory', \OC::$SERVERROOT . '/data/'); + $file = $config->getSystemValue('mount_file', $datadir . '/mount.json'); } foreach ($data as &$applicables) { @@ -330,7 +331,7 @@ class OC_Mount_Config { * @return string */ public static function dependencyMessage($backends) { - $l = new \OC_L10N('files_external'); + $l = \OC::$server->getL10N('files_external'); $message = ''; $dependencyGroups = []; @@ -357,12 +358,12 @@ class OC_Mount_Config { /** * Returns a dependency missing message * - * @param OC_L10N $l + * @param \OCP\IL10N $l * @param string $module * @param string $backend * @return string */ - private static function getSingleDependencyMessage(OC_L10N $l, $module, $backend) { + private static function getSingleDependencyMessage(\OCP\IL10N $l, $module, $backend) { switch (strtolower($module)) { case 'curl': return $l->t('<b>Note:</b> The cURL support in PHP is not enabled or installed. Mounting of %s is not possible. Please ask your system administrator to install it.', $backend); diff --git a/apps/files_external/lib/dropbox.php b/apps/files_external/lib/dropbox.php index 2d1aea1afc8..4ab14d4f3e0 100644 --- a/apps/files_external/lib/dropbox.php +++ b/apps/files_external/lib/dropbox.php @@ -244,7 +244,7 @@ class Dropbox extends \OC\Files\Storage\Common { switch ($mode) { case 'r': case 'rb': - $tmpFile = \OC_Helper::tmpFile(); + $tmpFile = \OCP\Files::tmpFile(); try { $data = $this->dropbox->getFile($path); file_put_contents($tmpFile, $data); @@ -270,7 +270,7 @@ class Dropbox extends \OC\Files\Storage\Common { } else { $ext = ''; } - $tmpFile = \OC_Helper::tmpFile($ext); + $tmpFile = \OCP\Files::tmpFile($ext); \OC\Files\Stream\Close::registerCallback($tmpFile, array($this, 'writeBack')); if ($this->file_exists($path)) { $source = $this->fopen($path, 'r'); diff --git a/apps/files_external/lib/failedstorage.php b/apps/files_external/lib/failedstorage.php index 6afa98052c2..22ee5326491 100644 --- a/apps/files_external/lib/failedstorage.php +++ b/apps/files_external/lib/failedstorage.php @@ -197,4 +197,12 @@ class FailedStorage extends Common { throw new StorageNotAvailableException($this->e->getMessage(), $this->e->getCode(), $this->e); } + public function getAvailability() { + throw new StorageNotAvailableException($this->e->getMessage(), $this->e->getCode(), $this->e); + } + + public function setAvailability($isAvailable) { + throw new StorageNotAvailableException($this->e->getMessage(), $this->e->getCode(), $this->e); + } + } diff --git a/apps/files_external/lib/google.php b/apps/files_external/lib/google.php index 2ca550dfe7c..e29b1036244 100644 --- a/apps/files_external/lib/google.php +++ b/apps/files_external/lib/google.php @@ -429,7 +429,7 @@ class Google extends \OC\Files\Storage\Common { $request = new \Google_Http_Request($downloadUrl, 'GET', null, null); $httpRequest = $this->client->getAuth()->authenticatedRequest($request); if ($httpRequest->getResponseHttpCode() == 200) { - $tmpFile = \OC_Helper::tmpFile($ext); + $tmpFile = \OCP\Files::tmpFile($ext); $data = $httpRequest->getResponseBody(); file_put_contents($tmpFile, $data); return fopen($tmpFile, $mode); @@ -449,7 +449,7 @@ class Google extends \OC\Files\Storage\Common { case 'x+': case 'c': case 'c+': - $tmpFile = \OC_Helper::tmpFile($ext); + $tmpFile = \OCP\Files::tmpFile($ext); \OC\Files\Stream\Close::registerCallback($tmpFile, array($this, 'writeBack')); if ($this->file_exists($path)) { $source = $this->fopen($path, 'rb'); @@ -466,7 +466,7 @@ class Google extends \OC\Files\Storage\Common { $parentFolder = $this->getDriveFile(dirname($path)); if ($parentFolder) { // TODO Research resumable upload - $mimetype = \OC_Helper::getMimeType($tmpFile); + $mimetype = \OC::$server->getMimeTypeDetector()->detect($tmpFile); $data = file_get_contents($tmpFile); $params = array( 'data' => $data, diff --git a/apps/files_external/lib/sessionstoragewrapper.php b/apps/files_external/lib/sessionstoragewrapper.php index 91be7eb1903..f8f5aba53a2 100644 --- a/apps/files_external/lib/sessionstoragewrapper.php +++ b/apps/files_external/lib/sessionstoragewrapper.php @@ -33,7 +33,7 @@ class SessionStorageWrapper extends PermissionsMask { /** * @param array $arguments ['storage' => $storage] */ - public function __construct(array $arguments) { + public function __construct($arguments) { // disable sharing permission $arguments['mask'] = Constants::PERMISSION_ALL & ~Constants::PERMISSION_SHARE; parent::__construct($arguments); diff --git a/apps/files_external/lib/swift.php b/apps/files_external/lib/swift.php index d8107e58fed..6f93f3c84cd 100644 --- a/apps/files_external/lib/swift.php +++ b/apps/files_external/lib/swift.php @@ -310,7 +310,7 @@ class Swift extends \OC\Files\Storage\Common { switch ($mode) { case 'r': case 'rb': - $tmpFile = \OC_Helper::tmpFile(); + $tmpFile = \OCP\Files::tmpFile(); self::$tmpFiles[$tmpFile] = $path; try { $object = $this->getContainer()->getObject($path); @@ -348,7 +348,7 @@ class Swift extends \OC\Files\Storage\Common { } else { $ext = ''; } - $tmpFile = \OC_Helper::tmpFile($ext); + $tmpFile = \OCP\Files::tmpFile($ext); \OC\Files\Stream\Close::registerCallback($tmpFile, array($this, 'writeBack')); if ($this->file_exists($path)) { $source = $this->fopen($path, 'r'); @@ -387,7 +387,7 @@ class Swift extends \OC\Files\Storage\Common { $object->saveMetadata($metadata); return true; } else { - $mimeType = \OC_Helper::getMimetypeDetector()->detectPath($path); + $mimeType = \OC::$server->getMimeTypeDetector()->detectPath($path); $customHeaders = array('content-type' => $mimeType); $metadataHeaders = DataObject::stockHeaders($metadata); $allHeaders = $customHeaders + $metadataHeaders; diff --git a/apps/files_external/service/backendservice.php b/apps/files_external/service/backendservice.php index bee08ecbd15..382834b4c19 100644 --- a/apps/files_external/service/backendservice.php +++ b/apps/files_external/service/backendservice.php @@ -140,7 +140,7 @@ class BackendService { */ public function getAvailableBackends() { return array_filter($this->getBackends(), function($backend) { - return empty($backend->checkDependencies()); + return !($backend->checkDependencies()); }); } @@ -256,7 +256,7 @@ class BackendService { */ protected function isAllowedUserBackend(Backend $backend) { if ($this->userMountingAllowed && - !empty(array_intersect($backend->getIdentifierAliases(), $this->userMountingBackends)) + array_intersect($backend->getIdentifierAliases(), $this->userMountingBackends) ) { return true; } diff --git a/apps/files_external/service/storagesservice.php b/apps/files_external/service/storagesservice.php index 63e71627785..3e2152741e5 100644 --- a/apps/files_external/service/storagesservice.php +++ b/apps/files_external/service/storagesservice.php @@ -426,7 +426,7 @@ abstract class StoragesService { */ protected function triggerApplicableHooks($signal, $mountPoint, $mountType, $applicableArray) { foreach ($applicableArray as $applicable) { - \OC_Hook::emit( + \OCP\Util::emitHook( Filesystem::CLASSNAME, $signal, [ diff --git a/apps/files_external/service/userglobalstoragesservice.php b/apps/files_external/service/userglobalstoragesservice.php index 78520419556..c59652d057f 100644 --- a/apps/files_external/service/userglobalstoragesservice.php +++ b/apps/files_external/service/userglobalstoragesservice.php @@ -76,20 +76,25 @@ class UserGlobalStoragesService extends GlobalStoragesService { $data = parent::readLegacyConfig(); $userId = $this->getUser()->getUID(); + // don't use array_filter() with ARRAY_FILTER_USE_KEY, it's PHP 5.6+ if (isset($data[\OC_Mount_Config::MOUNT_TYPE_USER])) { - $data[\OC_Mount_Config::MOUNT_TYPE_USER] = array_filter( - $data[\OC_Mount_Config::MOUNT_TYPE_USER], function($key) use ($userId) { - return (strtolower($key) === strtolower($userId) || $key === 'all'); - }, ARRAY_FILTER_USE_KEY - ); + $newData = []; + foreach ($data[\OC_Mount_Config::MOUNT_TYPE_USER] as $key => $value) { + if (strtolower($key) === strtolower($userId) || $key === 'all') { + $newData[$key] = $value; + } + } + $data[\OC_Mount_Config::MOUNT_TYPE_USER] = $newData; } if (isset($data[\OC_Mount_Config::MOUNT_TYPE_GROUP])) { - $data[\OC_Mount_Config::MOUNT_TYPE_GROUP] = array_filter( - $data[\OC_Mount_Config::MOUNT_TYPE_GROUP], function($key) use ($userId) { - return ($this->groupManager->isInGroup($userId, $key)); - }, ARRAY_FILTER_USE_KEY - ); + $newData = []; + foreach ($data[\OC_Mount_Config::MOUNT_TYPE_GROUP] as $key => $value) { + if ($this->groupManager->isInGroup($userId, $key)) { + $newData[$key] = $value; + } + } + $data[\OC_Mount_Config::MOUNT_TYPE_GROUP] = $newData; } return $data; diff --git a/apps/files_external/settings.php b/apps/files_external/settings.php index 9cecc0c6a49..29c0553158f 100644 --- a/apps/files_external/settings.php +++ b/apps/files_external/settings.php @@ -28,7 +28,7 @@ use \OCA\Files_External\Service\BackendService; -OC_Util::checkAdminUser(); +\OCP\User::checkAdminUser(); // we must use the same container $appContainer = \OC_Mount_Config::$app->getContainer(); diff --git a/apps/files_external/templates/list.php b/apps/files_external/templates/list.php index 750bf1dae18..c2f68b9c5ba 100644 --- a/apps/files_external/templates/list.php +++ b/apps/files_external/templates/list.php @@ -1,4 +1,4 @@ -<?php /** @var $l OC_L10N */ ?> +<?php /** @var $l \OCP\IL10N */ ?> <div id="controls"> <div id="file_action_panel"></div> </div> diff --git a/apps/files_external/tests/service/userstoragesservicetest.php b/apps/files_external/tests/service/userstoragesservicetest.php index 0d5b82e2f8c..8bbfbe5a050 100644 --- a/apps/files_external/tests/service/userstoragesservicetest.php +++ b/apps/files_external/tests/service/userstoragesservicetest.php @@ -31,9 +31,14 @@ class UserStoragesServiceTest extends StoragesServiceTest { public function setUp() { parent::setUp(); + $userManager = \OC::$server->getUserManager(); + $this->userId = $this->getUniqueID('user_'); + $this->user = $userManager->createUser( + $this->userId, + $this->userId + ); - $this->user = new \OC\User\User($this->userId, null); $userSession = $this->getMock('\OCP\IUserSession'); $userSession ->expects($this->any()) @@ -48,6 +53,7 @@ class UserStoragesServiceTest extends StoragesServiceTest { public function tearDown() { @unlink($this->dataDir . '/' . $this->userId . '/mount.json'); + $this->user->delete(); parent::tearDown(); } diff --git a/apps/files_sharing/ajax/testremote.php b/apps/files_sharing/ajax/testremote.php deleted file mode 100644 index 0a0548dbbd2..00000000000 --- a/apps/files_sharing/ajax/testremote.php +++ /dev/null @@ -1,46 +0,0 @@ -<?php -/** - * @author Lukas Reschke <lukas@owncloud.com> - * @author Morris Jobke <hey@morrisjobke.de> - * @author Robin Appelman <icewind@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/> - * - */ - -OCP\JSON::callCheck(); -OCP\JSON::checkAppEnabled('files_sharing'); - -$remote = $_GET['remote']; - -function testUrl($url) { - try { - $result = file_get_contents($url); - $data = json_decode($result); - // public link mount is only supported in ownCloud 7+ - return is_object($data) and !empty($data->version) and version_compare($data->version, '7.0.0', '>='); - } catch (Exception $e) { - return false; - } -} - -if (testUrl('https://' . $remote . '/status.php')) { - echo 'https'; -} elseif (testUrl('http://' . $remote . '/status.php')) { - echo 'http'; -} else { - echo 'false'; -} diff --git a/apps/files_sharing/api/server2server.php b/apps/files_sharing/api/server2server.php index 211dc52c333..4328e3830ba 100644 --- a/apps/files_sharing/api/server2server.php +++ b/apps/files_sharing/api/server2server.php @@ -111,9 +111,14 @@ class Server2Server { if ($share) { list($file, $link) = self::getFile($share['uid_owner'], $share['file_source']); - \OC::$server->getActivityManager()->publishActivity( - Activity::FILES_SHARING_APP, Activity::SUBJECT_REMOTE_SHARE_ACCEPTED, array($share['share_with'], basename($file)), '', array(), - $file, $link, $share['uid_owner'], Activity::TYPE_REMOTE_SHARE, Activity::PRIORITY_LOW); + $event = \OC::$server->getActivityManager()->generateEvent(); + $event->setApp(Activity::FILES_SHARING_APP) + ->setType(Activity::TYPE_REMOTE_SHARE) + ->setAffectedUser($share['uid_owner']) + ->setSubject(Activity::SUBJECT_REMOTE_SHARE_ACCEPTED, [$share['share_with'], basename($file)]) + ->setObject('files', $share['file_source'], $file) + ->setLink($link); + \OC::$server->getActivityManager()->publish($event); } return new \OC_OCS_Result(); @@ -142,9 +147,14 @@ class Server2Server { list($file, $link) = $this->getFile($share['uid_owner'], $share['file_source']); - \OC::$server->getActivityManager()->publishActivity( - Activity::FILES_SHARING_APP, Activity::SUBJECT_REMOTE_SHARE_DECLINED, array($share['share_with'], basename($file)), '', array(), - $file, $link, $share['uid_owner'], Activity::TYPE_REMOTE_SHARE, Activity::PRIORITY_LOW); + $event = \OC::$server->getActivityManager()->generateEvent(); + $event->setApp(Activity::FILES_SHARING_APP) + ->setType(Activity::TYPE_REMOTE_SHARE) + ->setAffectedUser($share['uid_owner']) + ->setSubject(Activity::SUBJECT_REMOTE_SHARE_DECLINED, [$share['share_with'], basename($file)]) + ->setObject('files', $share['file_source'], $file) + ->setLink($link); + \OC::$server->getActivityManager()->publish($event); } return new \OC_OCS_Result(); diff --git a/apps/files_sharing/appinfo/application.php b/apps/files_sharing/appinfo/application.php index 2fe9019d54e..530195c65fa 100644 --- a/apps/files_sharing/appinfo/application.php +++ b/apps/files_sharing/appinfo/application.php @@ -62,7 +62,8 @@ class Application extends App { $c->query('AppName'), $c->query('Request'), $c->query('IsIncomingShareEnabled'), - $c->query('ExternalManager') + $c->query('ExternalManager'), + $c->query('HttpClientService') ); }); @@ -78,6 +79,9 @@ class Application extends App { $container->registerService('UserManager', function (SimpleContainer $c) use ($server) { return $server->getUserManager(); }); + $container->registerService('HttpClientService', function (SimpleContainer $c) use ($server) { + return $server->getHTTPClientService(); + }); $container->registerService('IsIncomingShareEnabled', function (SimpleContainer $c) { return Helper::isIncomingServer2serverShareEnabled(); }); diff --git a/apps/files_sharing/appinfo/routes.php b/apps/files_sharing/appinfo/routes.php index 1e99267a43a..184ad29bba4 100644 --- a/apps/files_sharing/appinfo/routes.php +++ b/apps/files_sharing/appinfo/routes.php @@ -33,7 +33,14 @@ $application = new Application(); $application->registerRoutes($this, [ 'resources' => [ 'ExternalShares' => ['url' => '/api/externalShares'], - ] + ], + 'routes' => [ + [ + 'name' => 'externalShares#testRemote', + 'url' => '/testremote', + 'verb' => 'GET' + ], + ], ]); /** @var $this \OCP\Route\IRouter */ @@ -50,8 +57,6 @@ $this->create('sharing_external_shareinfo', '/shareinfo') ->actionInclude('files_sharing/ajax/shareinfo.php'); $this->create('sharing_external_add', '/external') ->actionInclude('files_sharing/ajax/external.php'); -$this->create('sharing_external_test_remote', '/testremote') - ->actionInclude('files_sharing/ajax/testremote.php'); // OCS API diff --git a/apps/files_sharing/css/settings-personal.css b/apps/files_sharing/css/settings-personal.css index c9af6c08c40..f53365c9371 100644 --- a/apps/files_sharing/css/settings-personal.css +++ b/apps/files_sharing/css/settings-personal.css @@ -4,6 +4,7 @@ #fileSharingSettings xmp { margin-top: 0; + white-space: pre-wrap; } [class^="social-"], [class*=" social-"] { diff --git a/apps/files_sharing/lib/controllers/externalsharescontroller.php b/apps/files_sharing/lib/controllers/externalsharescontroller.php index 494a544b2b8..6eb9d3c13d9 100644 --- a/apps/files_sharing/lib/controllers/externalsharescontroller.php +++ b/apps/files_sharing/lib/controllers/externalsharescontroller.php @@ -23,11 +23,11 @@ namespace OCA\Files_Sharing\Controllers; -use OC; -use OCP; use OCP\AppFramework\Controller; use OCP\IRequest; use OCP\AppFramework\Http\JSONResponse; +use OCP\Http\Client\IClientService; +use OCP\AppFramework\Http\DataResponse; /** * Class ExternalSharesController @@ -40,20 +40,25 @@ class ExternalSharesController extends Controller { private $incomingShareEnabled; /** @var \OCA\Files_Sharing\External\Manager */ private $externalManager; + /** @var IClientService */ + private $clientService; /** * @param string $appName * @param IRequest $request * @param bool $incomingShareEnabled * @param \OCA\Files_Sharing\External\Manager $externalManager + * @param IClientService $clientService */ public function __construct($appName, IRequest $request, $incomingShareEnabled, - \OCA\Files_Sharing\External\Manager $externalManager) { + \OCA\Files_Sharing\External\Manager $externalManager, + IClientService $clientService) { parent::__construct($appName, $request); $this->incomingShareEnabled = $incomingShareEnabled; $this->externalManager = $externalManager; + $this->clientService = $clientService; } /** @@ -97,4 +102,43 @@ class ExternalSharesController extends Controller { return new JSONResponse(); } + /** + * Test whether the specified remote is accessible + * + * @param string $remote + * @return bool + */ + protected function testUrl($remote) { + try { + $client = $this->clientService->newClient(); + $response = json_decode($client->get( + $remote, + [ + 'timeout' => 3, + 'connect_timeout' => 3, + ] + )->getBody()); + + return !empty($response->version) && version_compare($response->version, '7.0.0', '>='); + } catch (\Exception $e) { + return false; + } + } + + /** + * @PublicPage + * + * @param string $remote + * @return DataResponse + */ + public function testRemote($remote) { + if ($this->testUrl('https://' . $remote . '/status.php')) { + return new DataResponse('https'); + } elseif ($this->testUrl('http://' . $remote . '/status.php')) { + return new DataResponse('http'); + } else { + return new DataResponse(false); + } + } + } diff --git a/apps/files_sharing/lib/external/storage.php b/apps/files_sharing/lib/external/storage.php index dc8d1738b05..270d8b6d1b8 100644 --- a/apps/files_sharing/lib/external/storage.php +++ b/apps/files_sharing/lib/external/storage.php @@ -88,6 +88,8 @@ class Storage extends DAV implements ISharedStorage { 'user' => $options['token'], 'password' => (string)$options['password'] )); + + $this->getWatcher()->setPolicy(\OC\Files\Cache\Watcher::CHECK_ONCE); } public function getRemoteUser() { diff --git a/apps/files_sharing/lib/sharedstorage.php b/apps/files_sharing/lib/sharedstorage.php index 66803db1425..c7529df0617 100644 --- a/apps/files_sharing/lib/sharedstorage.php +++ b/apps/files_sharing/lib/sharedstorage.php @@ -662,4 +662,22 @@ class Shared extends \OC\Files\Storage\Common implements ISharedStorage { list($targetStorage, $targetInternalPath) = $this->resolvePath($path); $targetStorage->changeLock($targetInternalPath, $type, $provider); } + + /** + * @return array [ available, last_checked ] + */ + public function getAvailability() { + // shares do not participate in availability logic + return [ + 'available' => true, + 'last_checked' => 0 + ]; + } + + /** + * @param bool $available + */ + public function setAvailability($available) { + // shares do not participate in availability logic + } } diff --git a/apps/files_sharing/tests/controller/externalsharecontroller.php b/apps/files_sharing/tests/controller/externalsharecontroller.php new file mode 100644 index 00000000000..3dc34bca7a4 --- /dev/null +++ b/apps/files_sharing/tests/controller/externalsharecontroller.php @@ -0,0 +1,187 @@ +<?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 OCA\Files_Sharing\Controllers; + +use OCP\AppFramework\Http\DataResponse; +use OCP\AppFramework\Http\JSONResponse; +use OCP\Http\Client\IClientService; +use OCP\IRequest; + +/** + * Class ExternalShareControllerTest + * + * @package OCA\Files_Sharing\Controllers + */ +class ExternalShareControllerTest extends \Test\TestCase { + /** @var bool */ + private $incomingShareEnabled; + /** @var IRequest */ + private $request; + /** @var \OCA\Files_Sharing\External\Manager */ + private $externalManager; + /** @var IClientService */ + private $clientService; + + public function setUp() { + $this->request = $this->getMockBuilder('\\OCP\\IRequest') + ->disableOriginalConstructor()->getMock(); + $this->externalManager = $this->getMockBuilder('\\OCA\\Files_Sharing\\External\\Manager') + ->disableOriginalConstructor()->getMock(); + $this->clientService = $this->getMockBuilder('\\OCP\Http\\Client\\IClientService') + ->disableOriginalConstructor()->getMock(); + } + + /** + * @return ExternalSharesController + */ + public function getExternalShareController() { + return new ExternalSharesController( + 'files_sharing', + $this->request, + $this->incomingShareEnabled, + $this->externalManager, + $this->clientService + ); + } + + public function testIndexDisabled() { + $this->externalManager + ->expects($this->never()) + ->method('getOpenShares'); + + $this->assertEquals(new JSONResponse(), $this->getExternalShareController()->index()); + } + + public function testIndexEnabled() { + $this->incomingShareEnabled = true; + $this->externalManager + ->expects($this->once()) + ->method('getOpenShares') + ->will($this->returnValue(['MyDummyArray'])); + + $this->assertEquals(new JSONResponse(['MyDummyArray']), $this->getExternalShareController()->index()); + } + + public function testCreateDisabled() { + $this->externalManager + ->expects($this->never()) + ->method('acceptShare'); + + $this->assertEquals(new JSONResponse(), $this->getExternalShareController()->create(4)); + } + + public function testCreateEnabled() { + $this->incomingShareEnabled = true; + $this->externalManager + ->expects($this->once()) + ->method('acceptShare') + ->with(4); + + $this->assertEquals(new JSONResponse(), $this->getExternalShareController()->create(4)); + } + + public function testDestroyDisabled() { + $this->externalManager + ->expects($this->never()) + ->method('destroy'); + + $this->assertEquals(new JSONResponse(), $this->getExternalShareController()->destroy(4)); + } + + public function testDestroyEnabled() { + $this->incomingShareEnabled = true; + $this->externalManager + ->expects($this->once()) + ->method('declineShare') + ->with(4); + + $this->assertEquals(new JSONResponse(), $this->getExternalShareController()->destroy(4)); + } + + public function testRemoteWithValidHttps() { + $client = $this->getMockBuilder('\\OCP\\Http\\Client\\IClient') + ->disableOriginalConstructor()->getMock(); + $response = $this->getMockBuilder('\\OCP\\Http\\Client\\IResponse') + ->disableOriginalConstructor()->getMock(); + $client + ->expects($this->once()) + ->method('get') + ->with( + 'https://owncloud.org/status.php', + [ + 'timeout' => 3, + 'connect_timeout' => 3, + ] + )->will($this->returnValue($response)); + $response + ->expects($this->once()) + ->method('getBody') + ->will($this->returnValue('{"installed":true,"maintenance":false,"version":"8.1.0.8","versionstring":"8.1.0","edition":""}')); + + $this->clientService + ->expects($this->once()) + ->method('newClient') + ->will($this->returnValue($client)); + + $this->assertEquals(new DataResponse('https'), $this->getExternalShareController()->testRemote('owncloud.org')); + } + + public function testRemoteWithWorkingHttp() { + $client = $this->getMockBuilder('\\OCP\\Http\\Client\\IClient') + ->disableOriginalConstructor()->getMock(); + $response = $this->getMockBuilder('\\OCP\\Http\\Client\\IResponse') + ->disableOriginalConstructor()->getMock(); + $client + ->method('get') + ->will($this->onConsecutiveCalls($response, $response)); + $response + ->expects($this->exactly(2)) + ->method('getBody') + ->will($this->onConsecutiveCalls('Certainly not a JSON string', '{"installed":true,"maintenance":false,"version":"8.1.0.8","versionstring":"8.1.0","edition":""}')); + $this->clientService + ->expects($this->exactly(2)) + ->method('newClient') + ->will($this->returnValue($client)); + + $this->assertEquals(new DataResponse('http'), $this->getExternalShareController()->testRemote('owncloud.org')); + } + + public function testRemoteWithInvalidRemote() { + $client = $this->getMockBuilder('\\OCP\\Http\\Client\\IClient') + ->disableOriginalConstructor()->getMock(); + $response = $this->getMockBuilder('\\OCP\\Http\\Client\\IResponse') + ->disableOriginalConstructor()->getMock(); + $client + ->method('get') + ->will($this->onConsecutiveCalls($response, $response)); + $response + ->expects($this->exactly(2)) + ->method('getBody') + ->will($this->returnValue('Certainly not a JSON string')); + $this->clientService + ->expects($this->exactly(2)) + ->method('newClient') + ->will($this->returnValue($client)); + + $this->assertEquals(new DataResponse(false), $this->getExternalShareController()->testRemote('owncloud.org')); + } +} diff --git a/apps/user_ldap/ajax/setConfiguration.php b/apps/user_ldap/ajax/setConfiguration.php index 8e6994d8f94..9311d72d21f 100644 --- a/apps/user_ldap/ajax/setConfiguration.php +++ b/apps/user_ldap/ajax/setConfiguration.php @@ -33,7 +33,7 @@ $prefix = (string)$_POST['ldap_serverconfig_chooser']; // only legacy checkboxes (Advanced and Expert tab) need to be handled here, // the Wizard-like tabs handle it on their own $chkboxes = array('ldap_configuration_active', 'ldap_override_main_server', - 'ldap_nocase', 'ldap_turn_off_cert_check'); + 'ldap_turn_off_cert_check'); foreach($chkboxes as $boxid) { if(!isset($_POST[$boxid])) { $_POST[$boxid] = 0; diff --git a/apps/user_ldap/appinfo/update.php b/apps/user_ldap/appinfo/update.php index b904bce072e..4907db0cdae 100644 --- a/apps/user_ldap/appinfo/update.php +++ b/apps/user_ldap/appinfo/update.php @@ -24,3 +24,13 @@ $installedVersion = \OC::$server->getConfig()->getAppValue('user_ldap', 'install if (version_compare($installedVersion, '0.6.1', '<')) { \OC::$server->getConfig()->setAppValue('user_ldap', 'enforce_home_folder_naming_rule', false); } + +if(version_compare($installedVersion, '0.6.2', '<')) { + // Remove LDAP case insensitive setting from DB as it is no longer beeing used. + $helper = new \OCA\user_ldap\lib\Helper(); + $prefixes = $helper->getServerConfigurationPrefixes(); + + foreach($prefixes as $prefix) { + \OC::$server->getConfig()->deleteAppValue('user_ldap', $prefix . "ldap_nocase"); + } +} diff --git a/apps/user_ldap/appinfo/version b/apps/user_ldap/appinfo/version index ee6cdce3c29..b6160487433 100644 --- a/apps/user_ldap/appinfo/version +++ b/apps/user_ldap/appinfo/version @@ -1 +1 @@ -0.6.1 +0.6.2 diff --git a/apps/user_ldap/js/wizard/wizardTabAdvanced.js b/apps/user_ldap/js/wizard/wizardTabAdvanced.js index a27ec87b7c4..7367bfe87ae 100644 --- a/apps/user_ldap/js/wizard/wizardTabAdvanced.js +++ b/apps/user_ldap/js/wizard/wizardTabAdvanced.js @@ -41,10 +41,6 @@ OCA = OCA || {}; $element: $('#ldap_override_main_server'), setMethod: 'setOverrideMainServerState' }, - ldap_nocase: { - $element: $('#ldap_nocase'), - setMethod: 'setNoCase' - }, ldap_turn_off_cert_check: { $element: $('#ldap_turn_off_cert_check'), setMethod: 'setCertCheckDisabled' @@ -166,16 +162,6 @@ OCA = OCA || {}; }, /** - * whether the server is case insensitive. This setting does not play - * a role anymore (probably never had). - * - * @param {string} noCase contains an int - */ - setNoCase: function(noCase) { - this.setElementValue(this.managedItems.ldap_nocase.$element, noCase); - }, - - /** * sets whether the SSL/TLS certification check shout be disabled * * @param {string} doCertCheck contains an int diff --git a/apps/user_ldap/lib/configuration.php b/apps/user_ldap/lib/configuration.php index 0af819ff66f..1cbe45a82c2 100644 --- a/apps/user_ldap/lib/configuration.php +++ b/apps/user_ldap/lib/configuration.php @@ -43,7 +43,6 @@ class Configuration { 'ldapAgentName' => null, 'ldapAgentPassword' => null, 'ldapTLS' => null, - 'ldapNoCase' => null, 'turnOffCertCheck' => null, 'ldapIgnoreNamingRules' => null, 'ldapUserDisplayName' => null, @@ -379,7 +378,6 @@ class Configuration { 'ldap_display_name' => 'displayName', 'ldap_group_display_name' => 'cn', 'ldap_tls' => 0, - 'ldap_nocase' => 0, 'ldap_quota_def' => '', 'ldap_quota_attr' => '', 'ldap_email_attr' => '', @@ -436,7 +434,6 @@ class Configuration { 'ldap_display_name' => 'ldapUserDisplayName', 'ldap_group_display_name' => 'ldapGroupDisplayName', 'ldap_tls' => 'ldapTLS', - 'ldap_nocase' => 'ldapNoCase', 'ldap_quota_def' => 'ldapQuotaDefault', 'ldap_quota_attr' => 'ldapQuotaAttribute', 'ldap_email_attr' => 'ldapEmailAttribute', diff --git a/apps/user_ldap/templates/settings.php b/apps/user_ldap/templates/settings.php index f40eba005d8..88900e22bf7 100644 --- a/apps/user_ldap/templates/settings.php +++ b/apps/user_ldap/templates/settings.php @@ -78,7 +78,6 @@ style('user_ldap', 'settings'); <p><label for="ldap_backup_host"><?php p($l->t('Backup (Replica) Host'));?></label><input type="text" id="ldap_backup_host" name="ldap_backup_host" data-default="<?php p($_['ldap_backup_host_default']); ?>" title="<?php p($l->t('Give an optional backup host. It must be a replica of the main LDAP/AD server.'));?>"></p> <p><label for="ldap_backup_port"><?php p($l->t('Backup (Replica) Port'));?></label><input type="number" id="ldap_backup_port" name="ldap_backup_port" data-default="<?php p($_['ldap_backup_port_default']); ?>" /></p> <p><label for="ldap_override_main_server"><?php p($l->t('Disable Main Server'));?></label><input type="checkbox" id="ldap_override_main_server" name="ldap_override_main_server" value="1" data-default="<?php p($_['ldap_override_main_server_default']); ?>" title="<?php p($l->t('Only connect to the replica server.'));?>" /></p> - <p><label for="ldap_nocase"><?php p($l->t('Case insensitive LDAP server (Windows)'));?></label><input type="checkbox" id="ldap_nocase" name="ldap_nocase" data-default="<?php p($_['ldap_nocase_default']); ?>" value="1"<?php if (isset($_['ldap_nocase']) && ($_['ldap_nocase'])) p(' checked'); ?>></p> <p><label for="ldap_turn_off_cert_check"><?php p($l->t('Turn off SSL certificate validation.'));?></label><input type="checkbox" id="ldap_turn_off_cert_check" name="ldap_turn_off_cert_check" title="<?php p($l->t('Not recommended, use it for testing only! If connection only works with this option, import the LDAP server\'s SSL certificate in your %s server.', $theme->getName() ));?>" data-default="<?php p($_['ldap_turn_off_cert_check_default']); ?>" value="1"><br/></p> <p><label for="ldap_cache_ttl"><?php p($l->t('Cache Time-To-Live'));?></label><input type="number" id="ldap_cache_ttl" name="ldap_cache_ttl" title="<?php p($l->t('in seconds. A change empties the cache.'));?>" data-default="<?php p($_['ldap_cache_ttl_default']); ?>" /></p> </div> diff --git a/config/config.sample.php b/config/config.sample.php index 2b4873d5310..234bf8e0fa3 100644 --- a/config/config.sample.php +++ b/config/config.sample.php @@ -20,12 +20,6 @@ * * use RST syntax */ -/** - * Only enable this for local development and not in production environments - * This will disable the minifier and outputs some additional debug informations - */ -define('DEBUG', true); - $CONFIG = array( @@ -494,9 +488,10 @@ $CONFIG = array( 'log_type' => 'owncloud', /** - * Change the ownCloud logfile name from ``owncloud.log`` to something else. + * Log file path for the ownCloud logging type. + * Defaults to ``[datadirectory]/owncloud.log`` */ -'logfile' => 'owncloud.log', +'logfile' => '/var/log/owncloud.log', /** * Loglevel to start logging at. Valid values are: 0 = Debug, 1 = Info, 2 = @@ -562,8 +557,8 @@ $CONFIG = array( * Enables log rotation and limits the total size of logfiles. The default is 0, * or no rotation. Specify a size in bytes, for example 104857600 (100 megabytes * = 100 * 1024 * 1024 bytes). A new logfile is created with a new name when the - * old logfile reaches your limit. The total size of all logfiles is double the - * ``log_rotate_sizerotation`` value. + * old logfile reaches your limit. If a rotated log file is already present, it + * will be overwritten. */ 'log_rotate_size' => false, @@ -1079,6 +1074,14 @@ $CONFIG = array( 'memcache.locking' => '\\OC\\Memcache\\Redis', /** + * Set this ownCloud instance to debugging mode + * + * Only enable this for local development and not in production environments + * This will disable the minifier and outputs some additional debug information + */ +'debug' => false, + +/** * This entry is just here to show a warning in case somebody copied the sample * configuration. DO NOT ADD THIS SWITCH TO YOUR CONFIGURATION! * diff --git a/core/application.php b/core/application.php index 373965e7fd7..12ec6b63fd4 100644 --- a/core/application.php +++ b/core/application.php @@ -27,6 +27,7 @@ namespace OC\Core; use OC\AppFramework\Utility\SimpleContainer; +use OC\AppFramework\Utility\TimeFactory; use \OCP\AppFramework\App; use OC\Core\LostPassword\Controller\LostController; use OC\Core\User\UserController; @@ -63,7 +64,8 @@ class Application extends App { $c->query('SecureRandom'), $c->query('DefaultEmailAddress'), $c->query('IsEncryptionEnabled'), - $c->query('Mailer') + $c->query('Mailer'), + $c->query('TimeFactory') ); }); $container->registerService('UserController', function(SimpleContainer $c) { @@ -120,15 +122,15 @@ class Application extends App { $container->registerService('UserFolder', function(SimpleContainer $c) { return $c->query('ServerContainer')->getUserFolder(); }); - - - $container->registerService('Defaults', function() { return new \OC_Defaults; }); $container->registerService('Mailer', function(SimpleContainer $c) { return $c->query('ServerContainer')->getMailer(); }); + $container->registerService('TimeFactory', function(SimpleContainer $c) { + return new TimeFactory(); + }); $container->registerService('DefaultEmailAddress', function() { return Util::getDefaultEmailAddress('lostpassword-noreply'); }); diff --git a/core/avatar/avatarcontroller.php b/core/avatar/avatarcontroller.php index a0c9ebbd785..945e022600a 100644 --- a/core/avatar/avatarcontroller.php +++ b/core/avatar/avatarcontroller.php @@ -91,6 +91,7 @@ class AvatarController extends Controller { /** * @NoAdminRequired + * @NoCSRFRequired * * @param string $userId * @param int $size diff --git a/core/command/log/manage.php b/core/command/log/manage.php new file mode 100644 index 00000000000..f3d18cffca0 --- /dev/null +++ b/core/command/log/manage.php @@ -0,0 +1,171 @@ +<?php +/** + * @author Robin McCorkell <rmccorkell@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\Core\Command\Log; + +use \OCP\IConfig; + +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Console\Input\InputOption; +use Symfony\Component\Console\Output\OutputInterface; + +class Manage extends Command { + + const DEFAULT_BACKEND = 'owncloud'; + const DEFAULT_LOG_LEVEL = 2; + const DEFAULT_TIMEZONE = 'UTC'; + + /** @var IConfig */ + protected $config; + + public function __construct(IConfig $config) { + $this->config = $config; + parent::__construct(); + } + + protected function configure() { + $this + ->setName('log:manage') + ->setDescription('manage logging configuration') + ->addOption( + 'backend', + null, + InputOption::VALUE_REQUIRED, + 'set the logging backend [owncloud, syslog, errorlog]' + ) + ->addOption( + 'level', + null, + InputOption::VALUE_REQUIRED, + 'set the log level [debug, info, warning, error]' + ) + ->addOption( + 'timezone', + null, + InputOption::VALUE_REQUIRED, + 'set the logging timezone' + ) + ; + } + + protected function execute(InputInterface $input, OutputInterface $output) { + // collate config setting to the end, to avoid partial configuration + $toBeSet = []; + + if ($backend = $input->getOption('backend')) { + $this->validateBackend($backend); + $toBeSet['log_type'] = $backend; + } + + if ($level = $input->getOption('level')) { + if (is_numeric($level)) { + $levelNum = $level; + // sanity check + $this->convertLevelNumber($levelNum); + } else { + $levelNum = $this->convertLevelString($level); + } + $toBeSet['loglevel'] = $levelNum; + } + + if ($timezone = $input->getOption('timezone')) { + $this->validateTimezone($timezone); + $toBeSet['logtimezone'] = $timezone; + } + + // set config + foreach ($toBeSet as $option => $value) { + $this->config->setSystemValue($option, $value); + } + + // display configuration + $backend = $this->config->getSystemValue('log_type', self::DEFAULT_BACKEND); + $output->writeln('Enabled logging backend: '.$backend); + + $levelNum = $this->config->getSystemValue('loglevel', self::DEFAULT_LOG_LEVEL); + $level = $this->convertLevelNumber($levelNum); + $output->writeln('Log level: '.$level.' ('.$levelNum.')'); + + $timezone = $this->config->getSystemValue('logtimezone', self::DEFAULT_TIMEZONE); + $output->writeln('Log timezone: '.$timezone); + } + + /** + * @param string $backend + * @throws \InvalidArgumentException + */ + protected function validateBackend($backend) { + if (!class_exists('OC_Log_'.$backend)) { + throw new \InvalidArgumentException('Invalid backend'); + } + } + + /** + * @param string $timezone + * @throws \Exception + */ + protected function validateTimezone($timezone) { + new \DateTimeZone($timezone); + } + + /** + * @param string $level + * @return int + * @throws \InvalidArgumentException + */ + protected function convertLevelString($level) { + $level = strtolower($level); + switch ($level) { + case 'debug': + return 0; + case 'info': + return 1; + case 'warning': + case 'warn': + return 2; + case 'error': + case 'err': + return 3; + } + throw new \InvalidArgumentException('Invalid log level string'); + } + + /** + * @param int $levelNum + * @return string + * @throws \InvalidArgumentException + */ + protected function convertLevelNumber($levelNum) { + switch ($levelNum) { + case 0: + return 'Debug'; + case 1: + return 'Info'; + case 2: + return 'Warning'; + case 3: + return 'Error'; + } + throw new \InvalidArgumentException('Invalid log level number'); + } +} diff --git a/core/command/log/owncloud.php b/core/command/log/owncloud.php new file mode 100644 index 00000000000..a2d9e4bc7c8 --- /dev/null +++ b/core/command/log/owncloud.php @@ -0,0 +1,124 @@ +<?php +/** + * @author Robin McCorkell <rmccorkell@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\Core\Command\Log; + +use \OCP\IConfig; + +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Console\Input\InputOption; +use Symfony\Component\Console\Output\OutputInterface; + +class OwnCloud extends Command { + + /** @var IConfig */ + protected $config; + + public function __construct(IConfig $config) { + $this->config = $config; + parent::__construct(); + } + + protected function configure() { + $this + ->setName('log:owncloud') + ->setDescription('manipulate ownCloud logging backend') + ->addOption( + 'enable', + null, + InputOption::VALUE_NONE, + 'enable this logging backend' + ) + ->addOption( + 'file', + null, + InputOption::VALUE_REQUIRED, + 'set the log file path' + ) + ->addOption( + 'rotate-size', + null, + InputOption::VALUE_REQUIRED, + 'set the file size for log rotation, 0 = disabled' + ) + ; + } + + protected function execute(InputInterface $input, OutputInterface $output) { + $toBeSet = []; + + if ($input->getOption('enable')) { + $toBeSet['log_type'] = 'owncloud'; + } + + if ($file = $input->getOption('file')) { + $toBeSet['logfile'] = $file; + } + + if (($rotateSize = $input->getOption('rotate-size')) !== null) { + $rotateSize = \OCP\Util::computerFileSize($rotateSize); + $this->validateRotateSize($rotateSize); + $toBeSet['log_rotate_size'] = $rotateSize; + } + + // set config + foreach ($toBeSet as $option => $value) { + $this->config->setSystemValue($option, $value); + } + + // display config + if ($this->config->getSystemValue('log_type', 'owncloud') === 'owncloud') { + $enabledText = 'enabled'; + } else { + $enabledText = 'disabled'; + } + $output->writeln('Log backend ownCloud: '.$enabledText); + + $dataDir = $this->config->getSystemValue('datadirectory', \OC::$SERVERROOT.'/data'); + $defaultLogFile = rtrim($dataDir, '/').'/owncloud.log'; + $output->writeln('Log file: '.$this->config->getSystemValue('logfile', $defaultLogFile)); + + $rotateSize = $this->config->getSystemValue('log_rotate_size', 0); + if ($rotateSize) { + $rotateString = \OCP\Util::humanFileSize($rotateSize); + } else { + $rotateString = 'disabled'; + } + $output->writeln('Rotate at: '.$rotateString); + } + + /** + * @param mixed $rotateSize + * @throws \InvalidArgumentException + */ + protected function validateRotateSize(&$rotateSize) { + if ($rotateSize === false) { + throw new \InvalidArgumentException('Error parsing log rotation file size'); + } + $rotateSize = (int) $rotateSize; + if ($rotateSize < 0) { + throw new \InvalidArgumentException('Log rotation file size must be non-negative'); + } + } + +} diff --git a/core/css/apps.css b/core/css/apps.css index 300b186bba2..b3d7eaf6599 100644 --- a/core/css/apps.css +++ b/core/css/apps.css @@ -440,8 +440,10 @@ left: auto; bottom: 0; width: 27%; + min-width: 250px; display: block; - background: #eee; + background: #fff; + border-left: 1px solid #eee; -webkit-transition: margin-right 300ms; -moz-transition: margin-right 300ms; -o-transition: margin-right 300ms; @@ -600,47 +602,31 @@ em { /* generic tab styles */ .tabHeaders { margin: 15px; - background-color: #1D2D44; } - .tabHeaders .tabHeader { float: left; - border: 1px solid #ddd; padding: 5px; cursor: pointer; - background-color: #f8f8f8; - font-weight: bold; } .tabHeaders .tabHeader, .tabHeaders .tabHeader a { color: #888; } - -.tabHeaders .tabHeader:first-child { - border-top-left-radius: 4px; - border-bottom-left-radius: 4px; -} - -.tabHeaders .tabHeader:last-child { - border-top-right-radius: 4px; - border-bottom-right-radius: 4px; +.tabHeaders .tabHeader.selected { + font-weight: 600; } - .tabHeaders .tabHeader.selected, .tabHeaders .tabHeader:hover { - background-color: #e8e8e8; + border-bottom: 1px solid #333; } - .tabHeaders .tabHeader.selected, .tabHeaders .tabHeader.selected a, .tabHeaders .tabHeader:hover, .tabHeaders .tabHeader:hover a { color: #000; } - .tabsContainer { clear: left; } - .tabsContainer .tab { padding: 15px; } diff --git a/core/css/mobile.css b/core/css/mobile.css index 2256d821d73..9008e39b89c 100644 --- a/core/css/mobile.css +++ b/core/css/mobile.css @@ -31,7 +31,6 @@ width: 0; cursor: pointer; background-color: transparent; - background-image: url('../img/actions/search-white.svg'); -webkit-transition: all 100ms; -moz-transition: all 100ms; -o-transition: all 100ms; @@ -44,8 +43,7 @@ width: 155px; max-width: 50%; cursor: text; - background-color: #fff; - background-image: url('../img/actions/search.svg'); + background-color: #112; -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=100)"; opacity: 1; } diff --git a/core/css/styles.css b/core/css/styles.css index db81f850303..619a9df7433 100644 --- a/core/css/styles.css +++ b/core/css/styles.css @@ -258,7 +258,8 @@ input:disabled+label, input:disabled:hover+label, input:disabled:focus+label { font-size: 1.2em; padding: 3px; padding-left: 25px; - background: #fff url('../img/actions/search.svg') no-repeat 6px center; + background: #112 url('../img/actions/search-white.svg') no-repeat 6px center; + color: #fff; border: 0; border-radius: 3px; -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=30)"; @@ -271,6 +272,7 @@ input:disabled+label, input:disabled:hover+label, input:disabled:focus+label { .searchbox input[type="search"]:active { -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=70)"; opacity: .7; + color: #fff; } input[type="submit"].enabled { @@ -680,6 +682,13 @@ label.infield { color: #ccc; } +#body-login .update .appList { + list-style: disc; + text-align: left; + margin-left: 25px; + margin-right: 25px; +} + #body-login .v-align { width: inherit; } diff --git a/core/js/config.php b/core/js/config.php index cecbf27e4b2..5da610698df 100644 --- a/core/js/config.php +++ b/core/js/config.php @@ -62,7 +62,7 @@ if ($defaultExpireDateEnabled) { $outgoingServer2serverShareEnabled = $config->getAppValue('files_sharing', 'outgoing_server2server_share_enabled', 'yes') === 'yes'; $array = array( - "oc_debug" => (defined('DEBUG') && DEBUG) ? 'true' : 'false', + "oc_debug" => $config->getSystemValue('debug', false) ? 'true' : 'false', "oc_isadmin" => OC_User::isAdminUser(OC_User::getUser()) ? 'true' : 'false', "oc_webroot" => "\"".OC::$WEBROOT."\"", "oc_appswebroots" => str_replace('\\/', '/', json_encode($apps_paths)), // Ugly unescape slashes waiting for better solution @@ -78,6 +78,28 @@ $array = array( (string)$l->t('Saturday') ) ), + "dayNamesShort" => json_encode( + array( + (string)$l->t('Sun.'), + (string)$l->t('Mon.'), + (string)$l->t('Tue.'), + (string)$l->t('Wed.'), + (string)$l->t('Thu.'), + (string)$l->t('Fri.'), + (string)$l->t('Sat.') + ) + ), + "dayNamesMin" => json_encode( + array( + (string)$l->t('Su'), + (string)$l->t('Mo'), + (string)$l->t('Tu'), + (string)$l->t('We'), + (string)$l->t('Th'), + (string)$l->t('Fr'), + (string)$l->t('Sa') + ) + ), "monthNames" => json_encode( array( (string)$l->t('January'), @@ -94,6 +116,22 @@ $array = array( (string)$l->t('December') ) ), + "monthNamesShort" => json_encode( + array( + (string)$l->t('Jan.'), + (string)$l->t('Feb.'), + (string)$l->t('Mar.'), + (string)$l->t('Apr.'), + (string)$l->t('May.'), + (string)$l->t('Jun.'), + (string)$l->t('Jul.'), + (string)$l->t('Aug.'), + (string)$l->t('Sep.'), + (string)$l->t('Oct.'), + (string)$l->t('Nov.'), + (string)$l->t('Dec.') + ) + ), "firstDay" => json_encode($l->getFirstWeekDay()) , "oc_config" => json_encode( array( diff --git a/core/js/jquery.avatar.js b/core/js/jquery.avatar.js index 74acaac7927..b0d1ca7d88f 100644 --- a/core/js/jquery.avatar.js +++ b/core/js/jquery.avatar.js @@ -76,8 +76,8 @@ var $div = this; var url = OC.generateUrl( - '/avatar/{user}/{size}?requesttoken={requesttoken}', - {user: user, size: size * window.devicePixelRatio, requesttoken: oc_requesttoken}); + '/avatar/{user}/{size}', + {user: user, size: size * window.devicePixelRatio}); $.get(url, function(result) { if (typeof(result) === 'object') { diff --git a/core/js/share.js b/core/js/share.js index 57dd0dd6553..bf9250b3c3a 100644 --- a/core/js/share.js +++ b/core/js/share.js @@ -905,10 +905,10 @@ $(document).ready(function() { minDate.setDate(minDate.getDate()+1); $.datepicker.setDefaults({ monthNames: monthNames, - monthNamesShort: $.map(monthNames, function(v) { return v.slice(0,3)+'.'; }), + monthNamesShort: monthNamesShort, dayNames: dayNames, - dayNamesMin: $.map(dayNames, function(v) { return v.slice(0,2); }), - dayNamesShort: $.map(dayNames, function(v) { return v.slice(0,3)+'.'; }), + dayNamesMin: dayNamesMin, + dayNamesShort: dayNamesShort, firstDay: firstDay, minDate : minDate }); diff --git a/core/js/tests/specHelper.js b/core/js/tests/specHelper.js index dbe005ba2e9..cd387d76ce8 100644 --- a/core/js/tests/specHelper.js +++ b/core/js/tests/specHelper.js @@ -35,6 +35,24 @@ window.dayNames = [ 'Friday', 'Saturday' ]; +window.dayNamesShort = [ + 'Sun.', + 'Mon.', + 'Tue.', + 'Wed.', + 'Thu.', + 'Fri.', + 'Sat.' +]; +window.dayNamesMin = [ + 'Su', + 'Mo', + 'Tu', + 'We', + 'Th', + 'Fr', + 'Sa' +]; window.monthNames = [ 'January', 'February', @@ -49,6 +67,20 @@ window.monthNames = [ 'November', 'December' ]; +window.monthNamesShort = [ + 'Jan.', + 'Feb.', + 'Mar.', + 'Apr.', + 'May.', + 'Jun.', + 'Jul.', + 'Aug.', + 'Sep.', + 'Oct.', + 'Nov.', + 'Dec.' +]; window.firstDay = 0; // setup dummy webroots diff --git a/core/lostpassword/controller/lostcontroller.php b/core/lostpassword/controller/lostcontroller.php index 4c5e58c7b60..7d983bd7e30 100644 --- a/core/lostpassword/controller/lostcontroller.php +++ b/core/lostpassword/controller/lostcontroller.php @@ -28,6 +28,7 @@ namespace OC\Core\LostPassword\Controller; use \OCP\AppFramework\Controller; use \OCP\AppFramework\Http\TemplateResponse; +use OCP\AppFramework\Utility\ITimeFactory; use \OCP\IURLGenerator; use \OCP\IRequest; use \OCP\IL10N; @@ -66,6 +67,8 @@ class LostController extends Controller { protected $secureRandom; /** @var IMailer */ protected $mailer; + /** @var ITimeFactory */ + protected $timeFactory; /** * @param string $appName @@ -79,6 +82,7 @@ class LostController extends Controller { * @param string $from * @param string $isDataEncrypted * @param IMailer $mailer + * @param ITimeFactory $timeFactory */ public function __construct($appName, IRequest $request, @@ -90,7 +94,8 @@ class LostController extends Controller { ISecureRandom $secureRandom, $from, $isDataEncrypted, - IMailer $mailer) { + IMailer $mailer, + ITimeFactory $timeFactory) { parent::__construct($appName, $request); $this->urlGenerator = $urlGenerator; $this->userManager = $userManager; @@ -101,6 +106,7 @@ class LostController extends Controller { $this->isDataEncrypted = $isDataEncrypted; $this->config = $config; $this->mailer = $mailer; + $this->timeFactory = $timeFactory; } /** @@ -173,7 +179,17 @@ class LostController extends Controller { try { $user = $this->userManager->get($userId); - if (!StringUtils::equals($this->config->getUserValue($userId, 'owncloud', 'lostpassword', null), $token)) { + $splittedToken = explode(':', $this->config->getUserValue($userId, 'owncloud', 'lostpassword', null)); + if(count($splittedToken) !== 2) { + throw new \Exception($this->l10n->t('Couldn\'t reset password because the token is invalid')); + } + + if ($splittedToken[0] < ($this->timeFactory->getTime() - 60*60*12) || + $user->getLastLogin() > $splittedToken[0]) { + throw new \Exception($this->l10n->t('Couldn\'t reset password because the token is expired')); + } + + if (!StringUtils::equals($splittedToken[1], $token)) { throw new \Exception($this->l10n->t('Couldn\'t reset password because the token is invalid')); } @@ -216,12 +232,12 @@ class LostController extends Controller { ISecureRandom::CHAR_DIGITS. ISecureRandom::CHAR_LOWER. ISecureRandom::CHAR_UPPER); - $this->config->setUserValue($user, 'owncloud', 'lostpassword', $token); + $this->config->setUserValue($user, 'owncloud', 'lostpassword', $this->timeFactory->getTime() .':'. $token); $link = $this->urlGenerator->linkToRouteAbsolute('core.lost.resetform', array('userId' => $user, 'token' => $token)); $tmpl = new \OC_Template('core/lostpassword', 'email'); - $tmpl->assign('link', $link, false); + $tmpl->assign('link', $link); $msg = $tmpl->fetchPage(); try { diff --git a/core/register_command.php b/core/register_command.php index 6cd81b4c3b7..9c13c0967f8 100644 --- a/core/register_command.php +++ b/core/register_command.php @@ -58,6 +58,9 @@ if (\OC::$server->getConfig()->getSystemValue('installed', false)) { $application->add(new OC\Core\Command\Encryption\SetDefaultModule(\OC::$server->getEncryptionManager())); $application->add(new OC\Core\Command\Encryption\Status(\OC::$server->getEncryptionManager())); + $application->add(new OC\Core\Command\Log\Manage(\OC::$server->getConfig())); + $application->add(new OC\Core\Command\Log\OwnCloud(\OC::$server->getConfig())); + $application->add(new OC\Core\Command\Maintenance\MimeTypesJS()); $application->add(new OC\Core\Command\Maintenance\Mode(\OC::$server->getConfig())); $application->add(new OC\Core\Command\Maintenance\Repair(new \OC\Repair(\OC\Repair::getRepairSteps()), \OC::$server->getConfig())); diff --git a/core/templates/layout.user.php b/core/templates/layout.user.php index 2f93a30ba6a..59fced353d1 100644 --- a/core/templates/layout.user.php +++ b/core/templates/layout.user.php @@ -100,7 +100,7 @@ <?php p($l->t('Search'));?> </label> <input id="searchbox" class="svg" type="search" name="query" - value="<?php if(isset($_POST['query'])) {p($_POST['query']);};?>" + value="" autocomplete="off" tabindex="5"> </form> </div></header> diff --git a/core/templates/update.admin.php b/core/templates/update.admin.php index ccd5d236828..fbd3025f2e0 100644 --- a/core/templates/update.admin.php +++ b/core/templates/update.admin.php @@ -1,12 +1,26 @@ <div class="update" data-productname="<?php p($_['productName']) ?>" data-version="<?php p($_['version']) ?>"> <div class="updateOverview"> - <h2 class="title bold"><?php p($l->t('%s will be updated to version %s.', + <?php if ($_['isAppsOnlyUpgrade']) { ?> + <h2 class="title bold"><?php p($l->t('App update required')); ?></h2> + <?php } else { ?> + <h2 class="title bold"><?php p($l->t('%s will be updated to version %s', array($_['productName'], $_['version']))); ?></h2> - <?php if (!empty($_['appList'])) { ?> + <?php } ?> + <?php if (!empty($_['appsToUpgrade'])) { ?> + <div class="infogroup"> + <span class="bold"><?php p($l->t('These apps will be updated:')); ?></span> + <ul class="content appList"> + <?php foreach ($_['appsToUpgrade'] as $appInfo) { ?> + <li><?php p($appInfo['name']) ?> (<?php p($appInfo['id']) ?>)</li> + <?php } ?> + </ul> + </div> + <?php } ?> + <?php if (!empty($_['incompatibleAppsList'])) { ?> <div class="infogroup"> - <span class="bold"><?php p($l->t('The following apps will be disabled:')) ?></span> + <span class="bold"><?php p($l->t('These incompatible apps will be disabled:')) ?></span> <ul class="content appList"> - <?php foreach ($_['appList'] as $appInfo) { ?> + <?php foreach ($_['incompatibleAppsList'] as $appInfo) { ?> <li><?php p($appInfo['name']) ?> (<?php p($appInfo['id']) ?>)</li> <?php } ?> </ul> @@ -52,7 +52,10 @@ try { \OC::$server->getSession()->close(); // initialize a dummy memory session - \OC::$server->setSession(new \OC\Session\Memory('')); + $session = new \OC\Session\Memory(''); + $cryptoWrapper = \OC::$server->getSessionCryptoWrapper(); + $session = $cryptoWrapper->wrapSession($session); + \OC::$server->setSession($session); $logger = \OC::$server->getLogger(); diff --git a/lib/base.php b/lib/base.php index c0f3e50142e..aceac2e53c3 100644 --- a/lib/base.php +++ b/lib/base.php @@ -134,18 +134,7 @@ class OC { OC_Config::$object = new \OC\Config(self::$configDir); OC::$SUBURI = str_replace("\\", "/", substr(realpath($_SERVER["SCRIPT_FILENAME"]), strlen(OC::$SERVERROOT))); - /** - * FIXME: The following line is required because of a cyclic dependency - * on IRequest. - */ - $params = [ - 'server' => [ - 'SCRIPT_NAME' => $_SERVER['SCRIPT_NAME'], - 'SCRIPT_FILENAME' => $_SERVER['SCRIPT_FILENAME'], - ], - ]; - $fakeRequest = new \OC\AppFramework\Http\Request($params, null, new \OC\AllConfig(new \OC\SystemConfig())); - $scriptName = $fakeRequest->getScriptName(); + $scriptName = $_SERVER['SCRIPT_NAME']; if (substr($scriptName, -1) == '/') { $scriptName .= 'index.php'; //make sure suburi follows the same rules as scriptName @@ -346,27 +335,7 @@ class OC { if (\OCP\Util::needUpgrade()) { $systemConfig = \OC::$server->getSystemConfig(); if ($showTemplate && !$systemConfig->getValue('maintenance', false)) { - $version = OC_Util::getVersion(); - $oldTheme = $systemConfig->getValue('theme'); - $systemConfig->setValue('theme', ''); - OC_Util::addScript('config'); // needed for web root - OC_Util::addScript('update'); - $tmpl = new OC_Template('', 'update.admin', 'guest'); - $tmpl->assign('version', OC_Util::getVersionString()); - - // get third party apps - $apps = OC_App::getEnabledApps(); - $incompatibleApps = array(); - foreach ($apps as $appId) { - $info = OC_App::getAppInfo($appId); - if(!OC_App::isAppCompatible($version, $info)) { - $incompatibleApps[] = $info; - } - } - $tmpl->assign('appList', $incompatibleApps); - $tmpl->assign('productName', 'ownCloud'); // for now - $tmpl->assign('oldTheme', $oldTheme); - $tmpl->printPage(); + self::printUpgradePage(); exit(); } else { return true; @@ -375,6 +344,41 @@ class OC { return false; } + /** + * Prints the upgrade page + */ + private static function printUpgradePage() { + $systemConfig = \OC::$server->getSystemConfig(); + $oldTheme = $systemConfig->getValue('theme'); + $systemConfig->setValue('theme', ''); + \OCP\Util::addScript('config'); // needed for web root + \OCP\Util::addScript('update'); + + // check whether this is a core update or apps update + $installedVersion = $systemConfig->getValue('version', '0.0.0'); + $currentVersion = implode('.', OC_Util::getVersion()); + + $appManager = \OC::$server->getAppManager(); + + $tmpl = new OC_Template('', 'update.admin', 'guest'); + $tmpl->assign('version', OC_Util::getVersionString()); + + // if not a core upgrade, then it's apps upgrade + if (version_compare($currentVersion, $installedVersion, '=')) { + $tmpl->assign('isAppsOnlyUpgrade', true); + } else { + $tmpl->assign('isAppsOnlyUpgrade', false); + } + + // get third party apps + $ocVersion = OC_Util::getVersion(); + $tmpl->assign('appsToUpgrade', $appManager->getAppsNeedingUpgrade($ocVersion)); + $tmpl->assign('incompatibleAppsList', $appManager->getIncompatibleApps($ocVersion)); + $tmpl->assign('productName', 'ownCloud'); // for now + $tmpl->assign('oldTheme', $oldTheme); + $tmpl->printPage(); + } + public static function initTemplateEngine() { // Add the stuff we need always // following logic will import all vendor libraries that are @@ -448,13 +452,15 @@ class OC { $useCustomSession = false; $session = self::$server->getSession(); OC_Hook::emit('OC', 'initSession', array('session' => &$session, 'sessionName' => &$sessionName, 'useCustomSession' => &$useCustomSession)); - if($useCustomSession) { - // use the session reference as the new Session - self::$server->setSession($session); - } else { + if (!$useCustomSession) { // set the session name to the instance id - which is unique - self::$server->setSession(new \OC\Session\Internal($sessionName)); + $session = new \OC\Session\Internal($sessionName); } + + $cryptoWrapper = \OC::$server->getSessionCryptoWrapper(); + $session = $cryptoWrapper->wrapSession($session); + self::$server->setSession($session); + // if session cant be started break with http 500 error } catch (Exception $e) { \OCP\Util::logException('base', $e); @@ -576,7 +582,7 @@ class OC { if (!defined('PHPUNIT_RUN')) { $logger = \OC::$server->getLogger(); OC\Log\ErrorHandler::setLogger($logger); - if (defined('DEBUG') and DEBUG) { + if (\OC::$server->getConfig()->getSystemValue('debug', false)) { OC\Log\ErrorHandler::register(true); set_exception_handler(array('OC_Template', 'printExceptionErrorPage')); } else { @@ -1034,7 +1040,7 @@ class OC { return false; } - if (defined("DEBUG") && DEBUG) { + if (\OC::$server->getConfig()->getSystemValue('debug', false)) { \OCP\Util::writeLog('core', 'Trying to login from cookie', \OCP\Util::DEBUG); } @@ -1087,11 +1093,12 @@ class OC { self::cleanupLoginTokens($userId); if (!empty($_POST["remember_login"])) { - if (defined("DEBUG") && DEBUG) { + $config = self::$server->getConfig(); + if ($config->getSystemValue('debug', false)) { self::$server->getLogger()->debug('Setting remember login to cookie', array('app' => 'core')); } $token = \OC::$server->getSecureRandom()->getMediumStrengthGenerator()->generate(32); - self::$server->getConfig()->setUserValue($userId, 'login_token', $token, time()); + $config->setUserValue($userId, 'login_token', $token, time()); OC_User::setMagicInCookie($userId, $token); } else { OC_User::unsetMagicInCookie(); diff --git a/lib/private/activity/event.php b/lib/private/activity/event.php new file mode 100644 index 00000000000..fe6fc485b7b --- /dev/null +++ b/lib/private/activity/event.php @@ -0,0 +1,250 @@ +<?php +/** + * @author Joas Schilling <nickvergessen@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\Activity; + +use OCP\Activity\IEvent; + +class Event implements IEvent { + /** @var array */ + protected $data = [ + 'app' => null, + 'type' => null, + 'affected_user' => null, + 'author' => null, + 'timestamp' => null, + 'subject' => null, + 'subject_parameters' => null, + 'message' => '', + 'message_parameters' => [], + 'object_type' => '', + 'object_id' => 0, + 'object_name' => '', + 'link' => '', + ]; + + /** + * Set the app of the activity + * + * @param string $app + * @return IEvent + * @since 8.2.0 + */ + public function setApp($app) { + $this->data['app'] = (string) $app; + return $this; + } + + /** + * Set the type of the activity + * + * @param string $type + * @return IEvent + * @since 8.2.0 + */ + public function setType($type) { + $this->data['type'] = (string) $type; + return $this; + } + + /** + * Set the affected user of the activity + * + * @param string $affectedUser + * @return IEvent + * @since 8.2.0 + */ + public function setAffectedUser($affectedUser) { + $this->data['affected_user'] = (string) $affectedUser; + return $this; + } + + /** + * Set the author of the activity + * + * @param string $author + * @return IEvent + * @since 8.2.0 + */ + public function setAuthor($author) { + $this->data['author'] = (string) $author; + return $this; + } + + /** + * Set the author of the activity + * + * @param int $timestamp + * @return IEvent + * @since 8.2.0 + */ + public function setTimestamp($timestamp) { + $this->data['timestamp'] = (int) $timestamp; + return $this; + } + + /** + * Set the subject of the activity + * + * @param string $subject + * @param array $parameters + * @return IEvent + * @since 8.2.0 + */ + public function setSubject($subject, array $parameters = []) { + $this->data['subject'] = (string) $subject; + $this->data['subject_parameters'] = $parameters; + return $this; + } + + /** + * Set the message of the activity + * + * @param string $message + * @param array $parameters + * @return IEvent + * @since 8.2.0 + */ + public function setMessage($message, array $parameters = []) { + $this->data['message'] = (string) $message; + $this->data['message_parameters'] = $parameters; + return $this; + } + + /** + * Set the object of the activity + * + * @param string $objectType + * @param int $objectId + * @param string $objectName + * @return IEvent + * @since 8.2.0 + */ + public function setObject($objectType, $objectId, $objectName = '') { + $this->data['object_type'] = (string) $objectType; + $this->data['object_id'] = (int) $objectId; + $this->data['object_name'] = (string) $objectName; + return $this; + } + + /** + * Set the link of the activity + * + * @param string $link + * @return IEvent + * @since 8.2.0 + */ + public function setLink($link) { + $this->data['link'] = (string) $link; + return $this; + } + + /** + * @return string + */ + public function getApp() { + return $this->data['app']; + } + + /** + * @return string + */ + public function getType() { + return $this->data['type']; + } + + /** + * @return string + */ + public function getAffectedUser() { + return $this->data['affected_user']; + } + + /** + * @return string + */ + public function getAuthor() { + return $this->data['author']; + } + + /** + * @return int + */ + public function getTimestamp() { + return $this->data['timestamp']; + } + + /** + * @return string + */ + public function getSubject() { + return $this->data['subject']; + } + + /** + * @return array + */ + public function getSubjectParameters() { + return $this->data['subject_parameters']; + } + + /** + * @return string + */ + public function getMessage() { + return $this->data['message']; + } + + /** + * @return array + */ + public function getMessageParameters() { + return $this->data['message_parameters']; + } + + /** + * @return string + */ + public function getObjectType() { + return $this->data['object_type']; + } + + /** + * @return string + */ + public function getObjectId() { + return $this->data['object_id']; + } + + /** + * @return string + */ + public function getObjectName() { + return $this->data['object_name']; + } + + /** + * @return string + */ + public function getLink() { + return $this->data['link']; + } +} diff --git a/lib/private/activitymanager.php b/lib/private/activitymanager.php index 938335a87e1..a973db7206f 100644 --- a/lib/private/activitymanager.php +++ b/lib/private/activitymanager.php @@ -24,11 +24,14 @@ namespace OC; +use OC\Activity\Event; use OCP\Activity\IConsumer; +use OCP\Activity\IEvent; use OCP\Activity\IExtension; use OCP\Activity\IManager; use OCP\IConfig; use OCP\IRequest; +use OCP\IUser; use OCP\IUserSession; class ActivityManager implements IManager { @@ -124,36 +127,87 @@ class ActivityManager implements IManager { } /** - * @param $app - * @param $subject - * @param $subjectParams - * @param $message - * @param $messageParams - * @param $file - * @param $link - * @param $affectedUser - * @param $type - * @param $priority - * @return mixed + * Generates a new IEvent object + * + * Make sure to call at least the following methods before sending it to the + * app with via the publish() method: + * - setApp() + * - setType() + * - setAffectedUser() + * - setSubject() + * + * @return IEvent */ - function publishActivity($app, $subject, $subjectParams, $message, $messageParams, $file, $link, $affectedUser, $type, $priority) { - foreach($this->getConsumers() as $c) { - try { - $c->receive( - $app, - $subject, - $subjectParams, - $message, - $messageParams, - $file, - $link, - $affectedUser, - $type, - $priority); - } catch (\Exception $ex) { - // TODO: log the exception + public function generateEvent() { + return new Event(); + } + + /** + * Publish an event to the activity consumers + * + * Make sure to call at least the following methods before sending an Event: + * - setApp() + * - setType() + * - setAffectedUser() + * - setSubject() + * + * @param IEvent $event + * @return null + * @throws \BadMethodCallException if required values have not been set + */ + public function publish(IEvent $event) { + if (!$event->getApp()) { + throw new \BadMethodCallException('App not set', 10); + } + if (!$event->getType()) { + throw new \BadMethodCallException('Type not set', 11); + } + if ($event->getAffectedUser() === null) { + throw new \BadMethodCallException('Affected user not set', 12); + } + if ($event->getSubject() === null || $event->getSubjectParameters() === null) { + throw new \BadMethodCallException('Subject not set', 13); + } + + if ($event->getAuthor() === null) { + if ($this->session->getUser() instanceof IUser) { + $event->setAuthor($this->session->getUser()->getUID()); } } + + if (!$event->getTimestamp()) { + $event->setTimestamp(time()); + } + + foreach ($this->getConsumers() as $c) { + $c->receive($event); + } + } + + /** + * @param string $app The app where this event is associated with + * @param string $subject A short description of the event + * @param array $subjectParams Array with parameters that are filled in the subject + * @param string $message A longer description of the event + * @param array $messageParams Array with parameters that are filled in the message + * @param string $file The file including path where this event is associated with + * @param string $link A link where this event is associated with + * @param string $affectedUser Recipient of the activity + * @param string $type Type of the notification + * @param int $priority Priority of the notification + * @return null + */ + public function publishActivity($app, $subject, $subjectParams, $message, $messageParams, $file, $link, $affectedUser, $type, $priority) { + $event = $this->generateEvent(); + $event->setApp($app) + ->setType($type) + ->setAffectedUser($affectedUser) + ->setSubject($subject, $subjectParams) + ->setMessage($message, $messageParams) + ->setObject('', 0, $file) + ->setLink($link); + + $this->publish($event); } /** @@ -164,7 +218,7 @@ class ActivityManager implements IManager { * * @param \Closure $callable */ - function registerConsumer(\Closure $callable) { + public function registerConsumer(\Closure $callable) { array_push($this->consumersClosures, $callable); $this->consumers = []; } @@ -178,7 +232,7 @@ class ActivityManager implements IManager { * @param \Closure $callable * @return void */ - function registerExtension(\Closure $callable) { + public function registerExtension(\Closure $callable) { array_push($this->extensionsClosures, $callable); $this->extensions = []; } @@ -189,7 +243,7 @@ class ActivityManager implements IManager { * @param string $languageCode * @return array */ - function getNotificationTypes($languageCode) { + public function getNotificationTypes($languageCode) { $notificationTypes = array(); foreach ($this->getExtensions() as $c) { $result = $c->getNotificationTypes($languageCode); @@ -205,7 +259,7 @@ class ActivityManager implements IManager { * @param string $method * @return array */ - function getDefaultTypes($method) { + public function getDefaultTypes($method) { $defaultTypes = array(); foreach ($this->getExtensions() as $c) { $types = $c->getDefaultTypes($method); @@ -220,7 +274,7 @@ class ActivityManager implements IManager { * @param string $type * @return string */ - function getTypeIcon($type) { + public function getTypeIcon($type) { if (isset($this->typeIcons[$type])) { return $this->typeIcons[$type]; } @@ -246,7 +300,7 @@ class ActivityManager implements IManager { * @param string $languageCode * @return string|false */ - function translate($app, $text, $params, $stripPath, $highlightParams, $languageCode) { + public function translate($app, $text, $params, $stripPath, $highlightParams, $languageCode) { foreach ($this->getExtensions() as $c) { $translation = $c->translate($app, $text, $params, $stripPath, $highlightParams, $languageCode); if (is_string($translation)) { @@ -262,7 +316,7 @@ class ActivityManager implements IManager { * @param string $text * @return array|false */ - function getSpecialParameterList($app, $text) { + public function getSpecialParameterList($app, $text) { if (isset($this->specialParameters[$app][$text])) { return $this->specialParameters[$app][$text]; } @@ -287,7 +341,7 @@ class ActivityManager implements IManager { * @param array $activity * @return integer|false */ - function getGroupParameter($activity) { + public function getGroupParameter($activity) { foreach ($this->getExtensions() as $c) { $parameter = $c->getGroupParameter($activity); if ($parameter !== false) { @@ -301,7 +355,7 @@ class ActivityManager implements IManager { /** * @return array */ - function getNavigation() { + public function getNavigation() { $entries = array( 'apps' => array(), 'top' => array(), @@ -321,7 +375,7 @@ class ActivityManager implements IManager { * @param string $filterValue * @return boolean */ - function isFilterValid($filterValue) { + public function isFilterValid($filterValue) { if (isset($this->validFilters[$filterValue])) { return $this->validFilters[$filterValue]; } @@ -342,7 +396,7 @@ class ActivityManager implements IManager { * @param string $filter * @return array */ - function filterNotificationTypes($types, $filter) { + public function filterNotificationTypes($types, $filter) { if (!$this->isFilterValid($filter)) { return $types; } @@ -360,7 +414,7 @@ class ActivityManager implements IManager { * @param string $filter * @return array */ - function getQueryForFilter($filter) { + public function getQueryForFilter($filter) { if (!$this->isFilterValid($filter)) { return [null, null]; } diff --git a/lib/private/app.php b/lib/private/app.php index 9de1c66ee55..f1a1d27ae66 100644 --- a/lib/private/app.php +++ b/lib/private/app.php @@ -1139,7 +1139,7 @@ class OC_App { // check for required dependencies $dependencyAnalyzer = new DependencyAnalyzer(new Platform($config), $l); - $missing = $dependencyAnalyzer->analyze($app); + $missing = $dependencyAnalyzer->analyze($info); if (!empty($missing)) { $missingMsg = join(PHP_EOL, $missing); throw new \Exception( diff --git a/lib/private/app/appmanager.php b/lib/private/app/appmanager.php index 7a61cd53c59..75b1c0a7865 100644 --- a/lib/private/app/appmanager.php +++ b/lib/private/app/appmanager.php @@ -209,4 +209,73 @@ class AppManager implements IAppManager { $settingsMemCache = $this->memCacheFactory->create('settings'); $settingsMemCache->clear('listApps'); } + + /** + * Returns a list of apps that need upgrade + * + * @param array $version ownCloud version as array of version components + * @return array list of app info from apps that need an upgrade + * + * @internal + */ + public function getAppsNeedingUpgrade($ocVersion) { + $appsToUpgrade = []; + $apps = $this->getInstalledApps(); + foreach ($apps as $appId) { + $appInfo = $this->getAppInfo($appId); + $appDbVersion = $this->appConfig->getValue($appId, 'installed_version'); + if ($appDbVersion + && isset($appInfo['version']) + && version_compare($appInfo['version'], $appDbVersion, '>') + && \OC_App::isAppCompatible($ocVersion, $appInfo) + ) { + $appsToUpgrade[] = $appInfo; + } + } + + return $appsToUpgrade; + } + + /** + * Returns the app information from "appinfo/info.xml". + * + * If no version was present in "appinfo/info.xml", reads it + * from the external "appinfo/version" file instead. + * + * @param string $appId app id + * + * @return array app iinfo + * + * @internal + */ + public function getAppInfo($appId) { + $appInfo = \OC_App::getAppInfo($appId); + if (!isset($appInfo['version'])) { + // read version from separate file + $appInfo['version'] = \OC_App::getAppVersion($appId); + } + return $appInfo; + } + + /** + * Returns a list of apps incompatible with the given version + * + * @param array $version ownCloud version as array of version components + * + * @return array list of app info from incompatible apps + * + * @internal + */ + public function getIncompatibleApps($version) { + $apps = $this->getInstalledApps(); + $incompatibleApps = array(); + foreach ($apps as $appId) { + $info = $this->getAppInfo($appId); + if (!\OC_App::isAppCompatible($version, $info)) { + $incompatibleApps[] = $info; + } + } + return $incompatibleApps; + } + } diff --git a/lib/private/appframework/http/request.php b/lib/private/appframework/http/request.php index aaad286e843..a2109439177 100644 --- a/lib/private/appframework/http/request.php +++ b/lib/private/appframework/http/request.php @@ -32,6 +32,7 @@ namespace OC\AppFramework\Http; use OC\Security\TrustedDomainHelper; use OCP\IConfig; use OCP\IRequest; +use OCP\Security\ICrypto; use OCP\Security\ISecureRandom; /** @@ -67,6 +68,8 @@ class Request implements \ArrayAccess, \Countable, IRequest { protected $config; /** @var string */ protected $requestId = ''; + /** @var ICrypto */ + protected $crypto; /** * @param array $vars An associative array with the following optional values: @@ -80,17 +83,20 @@ class Request implements \ArrayAccess, \Countable, IRequest { * - string 'method' the request method (GET, POST etc) * - string|false 'requesttoken' the requesttoken or false when not available * @param ISecureRandom $secureRandom + * @param ICrypto $crypto * @param IConfig $config * @param string $stream * @see http://www.php.net/manual/en/reserved.variables.php */ public function __construct(array $vars=array(), ISecureRandom $secureRandom = null, + ICrypto $crypto, IConfig $config, $stream='php://input') { $this->inputStream = $stream; $this->items['params'] = array(); $this->secureRandom = $secureRandom; + $this->crypto = $crypto; $this->config = $config; if(!array_key_exists('method', $vars)) { @@ -415,8 +421,22 @@ class Request implements \ArrayAccess, \Countable, IRequest { return false; } + // Decrypt token to prevent BREACH like attacks + $token = explode(':', $token); + if (count($token) !== 2) { + return false; + } + + $encryptedToken = $token[0]; + $secret = $token[1]; + try { + $decryptedToken = $this->crypto->decrypt($encryptedToken, $secret); + } catch (\Exception $e) { + return false; + } + // Check if the token is valid - if(\OCP\Security\StringUtils::equals($token, $this->items['requesttoken'])) { + if(\OCP\Security\StringUtils::equals($decryptedToken, $this->items['requesttoken'])) { return true; } else { return false; diff --git a/lib/private/backgroundjob/joblist.php b/lib/private/backgroundjob/joblist.php index e8915b47f24..f297bccbc7d 100644 --- a/lib/private/backgroundjob/joblist.php +++ b/lib/private/backgroundjob/joblist.php @@ -87,6 +87,11 @@ class JobList implements IJobList { } } + protected function removeById($id) { + $query = $this->conn->prepare('DELETE FROM `*PREFIX*jobs` WHERE `id` = ?'); + $query->execute([$id]); + } + /** * check if a job is in the list * @@ -134,17 +139,25 @@ class JobList implements IJobList { $query = $this->conn->prepare('SELECT `id`, `class`, `last_run`, `argument` FROM `*PREFIX*jobs` WHERE `id` > ? ORDER BY `id` ASC', 1); $query->execute(array($lastId)); if ($row = $query->fetch()) { - return $this->buildJob($row); + $jobId = $row['id']; + $job = $this->buildJob($row); } else { //begin at the start of the queue $query = $this->conn->prepare('SELECT `id`, `class`, `last_run`, `argument` FROM `*PREFIX*jobs` ORDER BY `id` ASC', 1); $query->execute(); if ($row = $query->fetch()) { - return $this->buildJob($row); + $jobId = $row['id']; + $job = $this->buildJob($row); } else { return null; //empty job list } } + if (is_null($job)) { + $this->removeById($jobId); + return $this->getNext(); + } else { + return $job; + } } /** diff --git a/lib/private/config.php b/lib/private/config.php index 20ff02c1abf..3ad800a00be 100644 --- a/lib/private/config.php +++ b/lib/private/config.php @@ -47,8 +47,6 @@ class Config { protected $configFilePath; /** @var string */ protected $configFileName; - /** @var bool */ - protected $debugMode; /** * @param string $configDir Path to the config dir, needs to end with '/' @@ -59,7 +57,6 @@ class Config { $this->configFilePath = $this->configDir.$fileName; $this->configFileName = $fileName; $this->readData(); - $this->debugMode = (defined('DEBUG') && DEBUG); } /** @@ -225,9 +222,6 @@ class Config { private function writeData() { // Create a php file ... $content = "<?php\n"; - if ($this->debugMode) { - $content .= "define('DEBUG',true);\n"; - } $content .= '$CONFIG = '; $content .= var_export($this->cache, true); $content .= ";\n"; diff --git a/lib/private/files/fileinfo.php b/lib/private/files/fileinfo.php index 82c8f3de690..b333844f8c8 100644 --- a/lib/private/files/fileinfo.php +++ b/lib/private/files/fileinfo.php @@ -252,7 +252,7 @@ class FileInfo implements \OCP\Files\FileInfo, \ArrayAccess { $sid = $this->getStorage()->getId(); if (!is_null($sid)) { $sid = explode(':', $sid); - return ($sid[0] !== 'local' and $sid[0] !== 'home' and $sid[0] !== 'shared'); + return ($sid[0] !== 'home' and $sid[0] !== 'shared'); } return false; diff --git a/lib/private/http/client/client.php b/lib/private/http/client/client.php index fb3e06f3c46..323fc0d324f 100644 --- a/lib/private/http/client/client.php +++ b/lib/private/http/client/client.php @@ -144,6 +144,7 @@ class Client implements IClient { * 'debug' => true, * 'timeout' => 5, * @return Response + * @throws \Exception If the request could not get completed */ public function head($uri, $options = []) { $response = $this->client->head($uri, $options); @@ -176,6 +177,7 @@ class Client implements IClient { * 'debug' => true, * 'timeout' => 5, * @return Response + * @throws \Exception If the request could not get completed */ public function post($uri, array $options = []) { $response = $this->client->post($uri, $options); @@ -208,6 +210,7 @@ class Client implements IClient { * 'debug' => true, * 'timeout' => 5, * @return Response + * @throws \Exception If the request could not get completed */ public function put($uri, array $options = []) { $response = $this->client->put($uri, $options); @@ -240,6 +243,7 @@ class Client implements IClient { * 'debug' => true, * 'timeout' => 5, * @return Response + * @throws \Exception If the request could not get completed */ public function delete($uri, array $options = []) { $response = $this->client->delete($uri, $options); @@ -273,6 +277,7 @@ class Client implements IClient { * 'debug' => true, * 'timeout' => 5, * @return Response + * @throws \Exception If the request could not get completed */ public function options($uri, array $options = []) { $response = $this->client->options($uri, $options); diff --git a/lib/private/server.php b/lib/private/server.php index 89001567219..5a3a6328fae 100644 --- a/lib/private/server.php +++ b/lib/private/server.php @@ -40,6 +40,7 @@ use bantu\IniGetWrapper\IniGetWrapper; use OC\AppFramework\Http\Request; use OC\AppFramework\Db\Db; use OC\AppFramework\Utility\SimpleContainer; +use OC\AppFramework\Utility\TimeFactory; use OC\Command\AsyncBus; use OC\Diagnostics\EventLogger; use OC\Diagnostics\NullEventLogger; @@ -56,6 +57,7 @@ use OC\Security\Crypto; use OC\Security\Hasher; use OC\Security\SecureRandom; use OC\Security\TrustedDomainHelper; +use OC\Session\CryptoWrapper; use OC\Tagging\TagMapper; use OCP\IServerContainer; use Symfony\Component\EventDispatcher\EventDispatcher; @@ -159,7 +161,12 @@ class Server extends SimpleContainer implements IServerContainer { }); $this->registerService('UserSession', function (Server $c) { $manager = $c->getUserManager(); - $userSession = new \OC\User\Session($manager, new \OC\Session\Memory('')); + + $session = new \OC\Session\Memory(''); + $cryptoWrapper = $c->getSessionCryptoWrapper(); + $session = $cryptoWrapper->wrapSession($session); + + $userSession = new \OC\User\Session($manager, $session); $userSession->listen('\OC\User', 'preCreateUser', function ($uid, $password) { \OC_Hook::emit('OC_User', 'pre_createUser', array('run' => true, 'uid' => $uid, 'password' => $password)); }); @@ -322,14 +329,14 @@ class Server extends SimpleContainer implements IServerContainer { ); }); $this->registerService('EventLogger', function (Server $c) { - if (defined('DEBUG') and DEBUG) { + if ($c->getSystemConfig()->getValue('debug', false)) { return new EventLogger(); } else { return new NullEventLogger(); } }); - $this->registerService('QueryLogger', function ($c) { - if (defined('DEBUG') and DEBUG) { + $this->registerService('QueryLogger', function (Server $c) { + if ($c->getSystemConfig()->getValue('debug', false)) { return new QueryLogger(); } else { return new NullQueryLogger(); @@ -410,6 +417,7 @@ class Server extends SimpleContainer implements IServerContainer { 'requesttoken' => $requestToken, ], $this->getSecureRandom(), + $this->getCrypto(), $this->getConfig(), $stream ); @@ -461,6 +469,32 @@ class Server extends SimpleContainer implements IServerContainer { $this->registerService('EventDispatcher', function() { return new EventDispatcher(); }); + $this->registerService('CryptoWrapper', function (Server $c) { + // FIXME: Instantiiated here due to cyclic dependency + $request = new Request( + [ + 'get' => $_GET, + 'post' => $_POST, + 'files' => $_FILES, + 'server' => $_SERVER, + 'env' => $_ENV, + 'cookies' => $_COOKIE, + 'method' => (isset($_SERVER) && isset($_SERVER['REQUEST_METHOD'])) + ? $_SERVER['REQUEST_METHOD'] + : null, + ], + new SecureRandom(), + $c->getCrypto(), + $c->getConfig() + ); + + return new CryptoWrapper( + $c->getConfig(), + $c->getCrypto(), + $c->getSecureRandom(), + $request + ); + }); } /** @@ -976,4 +1010,11 @@ class Server extends SimpleContainer implements IServerContainer { public function getEventDispatcher() { return $this->query('EventDispatcher'); } + + /** + * @return \OC\Session\CryptoWrapper + */ + public function getSessionCryptoWrapper() { + return $this->query('CryptoWrapper'); + } } diff --git a/lib/private/session/cryptosessiondata.php b/lib/private/session/cryptosessiondata.php new file mode 100644 index 00000000000..60d22b25e97 --- /dev/null +++ b/lib/private/session/cryptosessiondata.php @@ -0,0 +1,147 @@ +<?php +/** + * @author Joas Schilling <nickvergessen@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\Session; + +use OCP\ISession; +use OCP\Security\ICrypto; + +/** + * Class CryptoSessionData + * + * @package OC\Session + */ +class CryptoSessionData implements \ArrayAccess, ISession { + /** @var ISession */ + protected $session; + + /** @var \OCP\Security\ICrypto */ + protected $crypto; + + /** @var string */ + protected $passphrase; + + /** + * @param ISession $session + * @param ICrypto $crypto + * @param string $passphrase + */ + public function __construct(ISession $session, ICrypto $crypto, $passphrase) { + $this->crypto = $crypto; + $this->session = $session; + $this->passphrase = $passphrase; + } + + /** + * Set a value in the session + * + * @param string $key + * @param mixed $value + */ + public function set($key, $value) { + $encryptedValue = $this->crypto->encrypt(json_encode($value), $this->passphrase); + $this->session->set($key, $encryptedValue); + } + + /** + * Get a value from the session + * + * @param string $key + * @return string|null Either the value or null + */ + public function get($key) { + $encryptedValue = $this->session->get($key); + if ($encryptedValue === null) { + return null; + } + + try { + $value = $this->crypto->decrypt($encryptedValue, $this->passphrase); + return json_decode($value); + } catch (\Exception $e) { + return null; + } + } + + /** + * Check if a named key exists in the session + * + * @param string $key + * @return bool + */ + public function exists($key) { + return $this->session->exists($key); + } + + /** + * Remove a $key/$value pair from the session + * + * @param string $key + */ + public function remove($key) { + $this->session->remove($key); + } + + /** + * Reset and recreate the session + */ + public function clear() { + $this->session->clear(); + } + + /** + * Close the session and release the lock + */ + public function close() { + $this->session->close(); + } + + /** + * @param mixed $offset + * @return bool + */ + public function offsetExists($offset) { + return $this->exists($offset); + } + + /** + * @param mixed $offset + * @return mixed + */ + public function offsetGet($offset) { + return $this->get($offset); + } + + /** + * @param mixed $offset + * @param mixed $value + */ + public function offsetSet($offset, $value) { + $this->set($offset, $value); + } + + /** + * @param mixed $offset + */ + public function offsetUnset($offset) { + $this->remove($offset); + } +} diff --git a/lib/private/session/cryptowrapper.php b/lib/private/session/cryptowrapper.php new file mode 100644 index 00000000000..62bdcbfb719 --- /dev/null +++ b/lib/private/session/cryptowrapper.php @@ -0,0 +1,96 @@ +<?php +/** + * @author Joas Schilling <nickvergessen@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\Session; + +use OCP\AppFramework\Utility\ITimeFactory; +use OCP\IConfig; +use OCP\IRequest; +use OCP\ISession; +use OCP\Security\ICrypto; +use OCP\Security\ISecureRandom; + +/** + * Class CryptoWrapper provides some rough basic level of additional security by + * storing the session data in an encrypted form. + * + * The content of the session is encrypted using another cookie sent by the browser. + * One should note that an adversary with access to the source code or the system + * memory is still able to read the original session ID from the users' request. + * This thus can not be considered a strong security measure one should consider + * it as an additional small security obfuscation layer to comply with compliance + * guidelines. + * + * TODO: Remove this in a future relase with an approach such as + * https://github.com/owncloud/core/pull/17866 + * + * @package OC\Session + */ +class CryptoWrapper { + const COOKIE_NAME = 'oc_sessionPassphrase'; + + /** @var ISession */ + protected $session; + + /** @var \OCP\Security\ICrypto */ + protected $crypto; + + /** @var ISecureRandom */ + protected $random; + + /** + * @param IConfig $config + * @param ICrypto $crypto + * @param ISecureRandom $random + * @param IRequest $request + */ + public function __construct(IConfig $config, + ICrypto $crypto, + ISecureRandom $random, + IRequest $request) { + $this->crypto = $crypto; + $this->config = $config; + $this->random = $random; + + if (!is_null($request->getCookie(self::COOKIE_NAME))) { + $this->passphrase = $request->getCookie(self::COOKIE_NAME); + } else { + $this->passphrase = $this->random->getMediumStrengthGenerator()->generate(128); + $secureCookie = $request->getServerProtocol() === 'https'; + // FIXME: Required for CI + if (!defined('PHPUNIT_RUN')) { + setcookie(self::COOKIE_NAME, $this->passphrase, 0, \OC::$WEBROOT, '', $secureCookie, true); + } + } + } + + /** + * @param ISession $session + * @return ISession + */ + public function wrapSession(ISession $session) { + if (!($session instanceof CryptoSessionData)) { + return new CryptoSessionData($session, $this->crypto, $this->passphrase); + } + + return $session; + } +} diff --git a/lib/private/template.php b/lib/private/template.php index e7acc778de3..920be71abbf 100644 --- a/lib/private/template.php +++ b/lib/private/template.php @@ -233,7 +233,7 @@ class OC_Template extends \OC\Template\Base { $content->assign('file', $exception->getFile()); $content->assign('line', $exception->getLine()); $content->assign('trace', $exception->getTraceAsString()); - $content->assign('debugMode', defined('DEBUG') && DEBUG === true); + $content->assign('debugMode', \OC::$server->getSystemConfig()->getValue('debug', false)); $content->assign('remoteAddr', $request->getRemoteAddress()); $content->assign('requestID', $request->getId()); $content->printPage(); diff --git a/lib/private/util.php b/lib/private/util.php index 501dbf5c4c5..edd375b5c36 100644 --- a/lib/private/util.php +++ b/lib/private/util.php @@ -1057,7 +1057,8 @@ class OC_Util { /** * Register an get/post call. Important to prevent CSRF attacks. * - * @return string Generated token. + * @return string The encrypted CSRF token, the shared secret is appended after the `:`. + * * @description * Creates a 'request token' (random) and stores it inside the session. * Ever subsequent (ajax) request must use such a valid token to succeed, @@ -1074,7 +1075,10 @@ class OC_Util { // Valid token already exists, send it $requestToken = \OC::$server->getSession()->get('requesttoken'); } - return ($requestToken); + + // Encrypt the token to mitigate breach-like attacks + $sharedSecret = \OC::$server->getSecureRandom()->getMediumStrengthGenerator()->generate(10); + return \OC::$server->getCrypto()->encrypt($requestToken, $sharedSecret) . ':' . $sharedSecret; } /** diff --git a/lib/public/activity/iconsumer.php b/lib/public/activity/iconsumer.php index a55110ababc..e74884d76c5 100644 --- a/lib/public/activity/iconsumer.php +++ b/lib/public/activity/iconsumer.php @@ -37,19 +37,11 @@ namespace OCP\Activity; */ interface IConsumer { /** - * @param $app - * @param $subject - * @param $subjectParams - * @param $message - * @param $messageParams - * @param $file - * @param $link - * @param $affectedUser - * @param $type - * @param $priority - * @return mixed + * @param IEvent $event + * @return null * @since 6.0.0 + * @since 8.2.0 Replaced the parameters with an IEvent object */ - function receive($app, $subject, $subjectParams, $message, $messageParams, $file, $link, $affectedUser, $type, $priority ); + public function receive(IEvent $event); } diff --git a/lib/public/activity/ievent.php b/lib/public/activity/ievent.php new file mode 100644 index 00000000000..184c7ae503f --- /dev/null +++ b/lib/public/activity/ievent.php @@ -0,0 +1,200 @@ +<?php +/** + * @author Joas Schilling <nickvergessen@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/> + * + */ + +/** + * Public interface of ownCloud for apps to use. + * Activity/IEvent interface + */ + +// use OCP namespace for all classes that are considered public. +// This means that they should be used by apps instead of the internal ownCloud classes +namespace OCP\Activity; + +/** + * Interface IEvent + * + * @package OCP\Activity + * @since 8.2.0 + */ +interface IEvent { + /** + * Set the app of the activity + * + * @param string $app + * @return IEvent + * @since 8.2.0 + */ + public function setApp($app); + + /** + * Set the type of the activity + * + * @param string $type + * @return IEvent + * @since 8.2.0 + */ + public function setType($type); + + /** + * Set the affected user of the activity + * + * @param string $user + * @return IEvent + * @since 8.2.0 + */ + public function setAffectedUser($user); + + /** + * Set the author of the activity + * + * @param string $author + * @return IEvent + * @since 8.2.0 + */ + public function setAuthor($author); + + /** + * Set the author of the activity + * + * @param int $timestamp + * @return IEvent + * @since 8.2.0 + */ + public function setTimestamp($timestamp); + + /** + * Set the subject of the activity + * + * @param string $subject + * @param array $parameters + * @return IEvent + * @since 8.2.0 + */ + public function setSubject($subject, array $parameters = []); + + /** + * Set the message of the activity + * + * @param string $message + * @param array $parameters + * @return IEvent + * @since 8.2.0 + */ + public function setMessage($message, array $parameters = []); + + /** + * Set the object of the activity + * + * @param string $objectType + * @param int $objectId + * @param string $objectName + * @return IEvent + * @since 8.2.0 + */ + public function setObject($objectType, $objectId, $objectName = ''); + + /** + * Set the link of the activity + * + * @param string $link + * @return IEvent + * @since 8.2.0 + */ + public function setLink($link); + + /** + * @return string + * @since 8.2.0 + */ + public function getApp(); + + /** + * @return string + * @since 8.2.0 + */ + public function getType(); + + /** + * @return string + * @since 8.2.0 + */ + public function getAffectedUser(); + + /** + * @return string + * @since 8.2.0 + */ + public function getAuthor(); + + /** + * @return int + * @since 8.2.0 + */ + public function getTimestamp(); + + /** + * @return string + * @since 8.2.0 + */ + public function getSubject(); + + /** + * @return array + * @since 8.2.0 + */ + public function getSubjectParameters(); + + /** + * @return string + * @since 8.2.0 + */ + public function getMessage(); + + /** + * @return array + * @since 8.2.0 + */ + public function getMessageParameters(); + + /** + * @return string + * @since 8.2.0 + */ + public function getObjectType(); + + /** + * @return string + * @since 8.2.0 + */ + public function getObjectId(); + + /** + * @return string + * @since 8.2.0 + */ + public function getObjectName(); + + /** + * @return string + * @since 8.2.0 + */ + public function getLink(); +} diff --git a/lib/public/activity/imanager.php b/lib/public/activity/imanager.php index 0f5dccd8ba1..b3a4969fb06 100644 --- a/lib/public/activity/imanager.php +++ b/lib/public/activity/imanager.php @@ -38,22 +38,52 @@ namespace OCP\Activity; * @since 6.0.0 */ interface IManager { + /** + * Generates a new IEvent object + * + * Make sure to call at least the following methods before sending it to the + * app with via the publish() method: + * - setApp() + * - setType() + * - setAffectedUser() + * - setSubject() + * + * @return IEvent + * @since 8.2.0 + */ + public function generateEvent(); + + /** + * Publish an event to the activity consumers + * + * Make sure to call at least the following methods before sending an Event: + * - setApp() + * - setType() + * - setAffectedUser() + * - setSubject() + * + * @param IEvent $event + * @return null + * @since 8.2.0 + */ + public function publish(IEvent $event); /** - * @param $app - * @param $subject - * @param $subjectParams - * @param $message - * @param $messageParams - * @param $file - * @param $link - * @param $affectedUser - * @param $type - * @param $priority - * @return mixed + * @param string $app The app where this event is associated with + * @param string $subject A short description of the event + * @param array $subjectParams Array with parameters that are filled in the subject + * @param string $message A longer description of the event + * @param array $messageParams Array with parameters that are filled in the message + * @param string $file The file including path where this event is associated with + * @param string $link A link where this event is associated with + * @param string $affectedUser Recipient of the activity + * @param string $type Type of the notification + * @param int $priority Priority of the notification + * @return null * @since 6.0.0 + * @deprecated 8.2.0 Grab an IEvent from generateEvent() instead and use the publish() method */ - function publishActivity($app, $subject, $subjectParams, $message, $messageParams, $file, $link, $affectedUser, $type, $priority); + public function publishActivity($app, $subject, $subjectParams, $message, $messageParams, $file, $link, $affectedUser, $type, $priority); /** * In order to improve lazy loading a closure can be registered which will be called in case @@ -65,7 +95,7 @@ interface IManager { * @return void * @since 6.0.0 */ - function registerConsumer(\Closure $callable); + public function registerConsumer(\Closure $callable); /** * In order to improve lazy loading a closure can be registered which will be called in case @@ -77,7 +107,7 @@ interface IManager { * @return void * @since 8.0.0 */ - function registerExtension(\Closure $callable); + public function registerExtension(\Closure $callable); /** * Will return additional notification types as specified by other apps @@ -91,21 +121,21 @@ interface IManager { * @since 8.0.0 * @changed 8.2.0 - Added support to allow limiting notifications to certain methods */ - function getNotificationTypes($languageCode); + public function getNotificationTypes($languageCode); /** * @param string $method * @return array * @since 8.0.0 */ - function getDefaultTypes($method); + public function getDefaultTypes($method); /** * @param string $type * @return string * @since 8.0.0 */ - function getTypeIcon($type); + public function getTypeIcon($type); /** * @param string $app @@ -117,7 +147,7 @@ interface IManager { * @return string|false * @since 8.0.0 */ - function translate($app, $text, $params, $stripPath, $highlightParams, $languageCode); + public function translate($app, $text, $params, $stripPath, $highlightParams, $languageCode); /** * @param string $app @@ -125,27 +155,27 @@ interface IManager { * @return array|false * @since 8.0.0 */ - function getSpecialParameterList($app, $text); + public function getSpecialParameterList($app, $text); /** * @param array $activity * @return integer|false * @since 8.0.0 */ - function getGroupParameter($activity); + public function getGroupParameter($activity); /** * @return array * @since 8.0.0 */ - function getNavigation(); + public function getNavigation(); /** * @param string $filterValue * @return boolean * @since 8.0.0 */ - function isFilterValid($filterValue); + public function isFilterValid($filterValue); /** * @param array $types @@ -153,14 +183,14 @@ interface IManager { * @return array * @since 8.0.0 */ - function filterNotificationTypes($types, $filter); + public function filterNotificationTypes($types, $filter); /** * @param string $filter * @return array * @since 8.0.0 */ - function getQueryForFilter($filter); + public function getQueryForFilter($filter); /** * Get the user we need to use diff --git a/lib/public/http/client/iclient.php b/lib/public/http/client/iclient.php index aadb9efd1bb..494ca7d419e 100644 --- a/lib/public/http/client/iclient.php +++ b/lib/public/http/client/iclient.php @@ -80,6 +80,7 @@ interface IClient { * 'verify' => true, // bool or string to CA file * 'debug' => true, * @return IResponse + * @throws \Exception If the request could not get completed * @since 8.1.0 */ public function head($uri, $options = []); @@ -109,6 +110,7 @@ interface IClient { * 'verify' => true, // bool or string to CA file * 'debug' => true, * @return IResponse + * @throws \Exception If the request could not get completed * @since 8.1.0 */ public function post($uri, array $options = []); @@ -138,6 +140,7 @@ interface IClient { * 'verify' => true, // bool or string to CA file * 'debug' => true, * @return IResponse + * @throws \Exception If the request could not get completed * @since 8.1.0 */ public function put($uri, array $options = []); @@ -167,6 +170,7 @@ interface IClient { * 'verify' => true, // bool or string to CA file * 'debug' => true, * @return IResponse + * @throws \Exception If the request could not get completed * @since 8.1.0 */ public function delete($uri, array $options = []); @@ -196,6 +200,7 @@ interface IClient { * 'verify' => true, // bool or string to CA file * 'debug' => true, * @return IResponse + * @throws \Exception If the request could not get completed * @since 8.1.0 */ public function options($uri, array $options = []); diff --git a/settings/ajax/togglegroups.php b/settings/ajax/togglegroups.php index 87b60e485bf..4d248408db0 100644 --- a/settings/ajax/togglegroups.php +++ b/settings/ajax/togglegroups.php @@ -60,9 +60,6 @@ if( OC_Group::inGroup( $username, $group )) { $error = $l->t("Unable to remove user from group %s", $group); $success = OC_Group::removeFromGroup( $username, $group ); $usersInGroup=OC_Group::usersInGroup($group); - if(count($usersInGroup) === 0) { - OC_Group::deleteGroup($group); - } } else{ $success = OC_Group::addToGroup( $username, $group ); diff --git a/settings/css/settings.css b/settings/css/settings.css index 9bd342ca6f2..0f1f432e4e2 100644 --- a/settings/css/settings.css +++ b/settings/css/settings.css @@ -361,6 +361,11 @@ table.grid td.date{ list-style: initial; margin: 10px 0; } +#security-warning-state span { + padding-left: 25px; + background-position: 5px center; + margin-left: -5px; +} #shareAPI p { padding-bottom: 0.8em; } #shareAPI input#shareapiExpireAfterNDays {width: 25px;} diff --git a/settings/js/admin.js b/settings/js/admin.js index aa228e76be7..7117c7b46cf 100644 --- a/settings/js/admin.js +++ b/settings/js/admin.js @@ -46,6 +46,8 @@ $(document).ready(function(){ var mode = $(this).val(); if (mode === 'ajax' || mode === 'webcron' || mode === 'cron') { OC.AppConfig.setValue('core', 'backgroundjobs_mode', mode); + // clear cron errors on background job mode change + OC.AppConfig.deleteKey('core', 'cronErrors'); } } }); @@ -177,6 +179,10 @@ $(document).ready(function(){ var $el = $('#postsetupchecks'); $el.find('.loading').addClass('hidden'); if (messages.length === 0) { + var securityWarning = $('#security-warning'); + if (securityWarning.children('ul').children().length === 0) { + $('#security-warning-state').find('span').removeClass('hidden'); + } } else { var $errorsEl = $el.find('.errors'); var $warningsEl = $el.find('.warnings'); diff --git a/settings/js/personal.js b/settings/js/personal.js index 9e4dd54090d..33746d22aca 100644 --- a/settings/js/personal.js +++ b/settings/js/personal.js @@ -321,7 +321,7 @@ $(document).ready(function () { var url = OC.generateUrl( '/avatar/{user}/{size}', {user: OC.currentUser, size: 1} - ) + '?requesttoken=' + encodeURIComponent(oc_requesttoken); + ); $.get(url, function (result) { if (typeof(result) === 'object') { $('#removeavatar').hide(); diff --git a/settings/js/users/users.js b/settings/js/users/users.js index 4e686a6db8f..5b12366ad40 100644 --- a/settings/js/users/users.js +++ b/settings/js/users/users.js @@ -470,17 +470,6 @@ var UserList = { UserList.availableGroups.push(groupName); } - // in case this was the last user in that group the group has to be removed - var groupElement = GroupList.getGroupLI(groupName); - var userCount = GroupList.getUserCount(groupElement); - if (response.data.action === 'remove' && userCount === 1) { - _.without(UserList.availableGroups, groupName); - GroupList.remove(groupName); - $('.groupsselect option').filterAttr('value', groupName).remove(); - $('.subadminsselect option').filterAttr('value', groupName).remove(); - } - - } if (response.data.message) { OC.Notification.show(response.data.message); diff --git a/settings/templates/admin.php b/settings/templates/admin.php index 888ed823793..9c161281846 100644 --- a/settings/templates/admin.php +++ b/settings/templates/admin.php @@ -177,6 +177,9 @@ if ($_['cronErrors']) { <?php print_unescaped($l->t('Please double check the <a target="_blank" href="%s">installation guides ↗</a>, and check for any errors or warnings in the <a href="#log-section">log</a>.', link_to_docs('admin-install'))); ?> </p> </div> +<div id="security-warning-state"> + <span class="hidden icon-checkmark"><?php p($l->t('All checks passed.'));?></span> +</div> </div> <div class="section" id="shareAPI"> diff --git a/settings/templates/users/part.grouplist.php b/settings/templates/users/part.grouplist.php index 51638c7bcce..cd6ac4a1e89 100644 --- a/settings/templates/users/part.grouplist.php +++ b/settings/templates/users/part.grouplist.php @@ -43,9 +43,11 @@ </a> <span class="utils"> <span class="usercount"><?php if($group['usercount'] > 0) { p($group['usercount']); } ?></span> + <?php if($_['isAdmin']): ?> <a href="#" class="action delete" original-title="<?php p($l->t('Delete'))?>"> <img src="<?php print_unescaped(image_path('core', 'actions/delete.svg')) ?>" class="svg" /> </a> + <?php endif; ?> </span> </li> <?php endforeach; ?> diff --git a/status.php b/status.php index 6e7bcea5266..90250ff2615 100644 --- a/status.php +++ b/status.php @@ -41,6 +41,7 @@ try { if (OC::$CLI) { print_r($values); } else { + header('Access-Control-Allow-Origin: *'); header('Content-Type: application/json'); echo json_encode($values); } diff --git a/tests/core/command/log/managetest.php b/tests/core/command/log/managetest.php new file mode 100644 index 00000000000..6fb83347f23 --- /dev/null +++ b/tests/core/command/log/managetest.php @@ -0,0 +1,181 @@ +<?php +/** + * @author Robin McCorkell <rmccorkell@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 Tests\Core\Command\Log; + + +use OC\Core\Command\Log\Manage; +use Test\TestCase; + +class ManageTest extends TestCase { + /** @var \PHPUnit_Framework_MockObject_MockObject */ + protected $config; + /** @var \PHPUnit_Framework_MockObject_MockObject */ + protected $consoleInput; + /** @var \PHPUnit_Framework_MockObject_MockObject */ + protected $consoleOutput; + + /** @var \Symfony\Component\Console\Command\Command */ + protected $command; + + protected function setUp() { + parent::setUp(); + + $config = $this->config = $this->getMockBuilder('OCP\IConfig') + ->disableOriginalConstructor() + ->getMock(); + $this->consoleInput = $this->getMock('Symfony\Component\Console\Input\InputInterface'); + $this->consoleOutput = $this->getMock('Symfony\Component\Console\Output\OutputInterface'); + + $this->command = new Manage($config); + } + + public function testChangeBackend() { + $this->consoleInput->method('getOption') + ->will($this->returnValueMap([ + ['backend', 'syslog'] + ])); + $this->config->expects($this->once()) + ->method('setSystemValue') + ->with('log_type', 'syslog'); + + self::invokePrivate($this->command, 'execute', [$this->consoleInput, $this->consoleOutput]); + } + + public function testChangeLevel() { + $this->consoleInput->method('getOption') + ->will($this->returnValueMap([ + ['level', 'debug'] + ])); + $this->config->expects($this->once()) + ->method('setSystemValue') + ->with('loglevel', 0); + + self::invokePrivate($this->command, 'execute', [$this->consoleInput, $this->consoleOutput]); + } + + public function testChangeTimezone() { + $this->consoleInput->method('getOption') + ->will($this->returnValueMap([ + ['timezone', 'UTC'] + ])); + $this->config->expects($this->once()) + ->method('setSystemValue') + ->with('logtimezone', 'UTC'); + + self::invokePrivate($this->command, 'execute', [$this->consoleInput, $this->consoleOutput]); + } + + /** + * @expectedException \InvalidArgumentException + */ + public function testValidateBackend() { + self::invokePrivate($this->command, 'validateBackend', ['notabackend']); + } + + /** + * @expectedException \Exception + */ + public function testValidateTimezone() { + // this might need to be changed when humanity colonises Mars + self::invokePrivate($this->command, 'validateTimezone', ['Mars/OlympusMons']); + } + + public function convertLevelStringProvider() { + return [ + ['dEbug', 0], + ['inFO', 1], + ['Warning', 2], + ['wArn', 2], + ['error', 3], + ['eRr', 3], + ]; + } + + /** + * @dataProvider convertLevelStringProvider + */ + public function testConvertLevelString($levelString, $expectedInt) { + $this->assertEquals($expectedInt, + self::invokePrivate($this->command, 'convertLevelString', [$levelString]) + ); + } + + /** + * @expectedException \InvalidArgumentException + */ + public function testConvertLevelStringInvalid() { + self::invokePrivate($this->command, 'convertLevelString', ['abc']); + } + + public function convertLevelNumberProvider() { + return [ + [0, 'Debug'], + [1, 'Info'], + [2, 'Warning'], + [3, 'Error'], + ]; + } + + /** + * @dataProvider convertLevelNumberProvider + */ + public function testConvertLevelNumber($levelNum, $expectedString) { + $this->assertEquals($expectedString, + self::invokePrivate($this->command, 'convertLevelNumber', [$levelNum]) + ); + } + + /** + * @expectedException \InvalidArgumentException + */ + public function testConvertLevelNumberInvalid() { + self::invokePrivate($this->command, 'convertLevelNumber', [11]); + } + + public function testGetConfiguration() { + $this->config->expects($this->at(0)) + ->method('getSystemValue') + ->with('log_type', 'owncloud') + ->willReturn('log_type_value'); + $this->config->expects($this->at(1)) + ->method('getSystemValue') + ->with('loglevel', 2) + ->willReturn(0); + $this->config->expects($this->at(2)) + ->method('getSystemValue') + ->with('logtimezone', 'UTC') + ->willReturn('logtimezone_value'); + + $this->consoleOutput->expects($this->at(0)) + ->method('writeln') + ->with('Enabled logging backend: log_type_value'); + $this->consoleOutput->expects($this->at(1)) + ->method('writeln') + ->with('Log level: Debug (0)'); + $this->consoleOutput->expects($this->at(2)) + ->method('writeln') + ->with('Log timezone: logtimezone_value'); + + self::invokePrivate($this->command, 'execute', [$this->consoleInput, $this->consoleOutput]); + } + +} diff --git a/tests/core/command/log/owncloudtest.php b/tests/core/command/log/owncloudtest.php new file mode 100644 index 00000000000..3cb05221c37 --- /dev/null +++ b/tests/core/command/log/owncloudtest.php @@ -0,0 +1,121 @@ +<?php +/** + * @author Robin McCorkell <rmccorkell@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 Tests\Core\Command\Log; + + +use OC\Core\Command\Log\OwnCloud; +use Test\TestCase; + +class OwnCloudTest extends TestCase { + /** @var \PHPUnit_Framework_MockObject_MockObject */ + protected $config; + /** @var \PHPUnit_Framework_MockObject_MockObject */ + protected $consoleInput; + /** @var \PHPUnit_Framework_MockObject_MockObject */ + protected $consoleOutput; + + /** @var \Symfony\Component\Console\Command\Command */ + protected $command; + + protected function setUp() { + parent::setUp(); + + $config = $this->config = $this->getMockBuilder('OCP\IConfig') + ->disableOriginalConstructor() + ->getMock(); + $this->consoleInput = $this->getMock('Symfony\Component\Console\Input\InputInterface'); + $this->consoleOutput = $this->getMock('Symfony\Component\Console\Output\OutputInterface'); + + $this->command = new OwnCloud($config); + } + + public function testEnable() { + $this->consoleInput->method('getOption') + ->will($this->returnValueMap([ + ['enable', 'true'] + ])); + $this->config->expects($this->once()) + ->method('setSystemValue') + ->with('log_type', 'owncloud'); + + self::invokePrivate($this->command, 'execute', [$this->consoleInput, $this->consoleOutput]); + } + + public function testChangeFile() { + $this->consoleInput->method('getOption') + ->will($this->returnValueMap([ + ['file', '/foo/bar/file.log'] + ])); + $this->config->expects($this->once()) + ->method('setSystemValue') + ->with('logfile', '/foo/bar/file.log'); + + self::invokePrivate($this->command, 'execute', [$this->consoleInput, $this->consoleOutput]); + } + + public function changeRotateSizeProvider() { + return [ + ['42', 42], + ['0', 0], + ['1 kB', 1024], + ['5MB', 5 * 1024 * 1024], + ]; + } + + /** + * @dataProvider changeRotateSizeProvider + */ + public function testChangeRotateSize($optionValue, $configValue) { + $this->consoleInput->method('getOption') + ->will($this->returnValueMap([ + ['rotate-size', $optionValue] + ])); + $this->config->expects($this->once()) + ->method('setSystemValue') + ->with('log_rotate_size', $configValue); + + self::invokePrivate($this->command, 'execute', [$this->consoleInput, $this->consoleOutput]); + } + + public function testGetConfiguration() { + $this->config->method('getSystemValue') + ->will($this->returnValueMap([ + ['log_type', 'owncloud', 'log_type_value'], + ['datadirectory', \OC::$SERVERROOT.'/data', '/data/directory/'], + ['logfile', '/data/directory/owncloud.log', '/var/log/owncloud.log'], + ['log_rotate_size', 0, 5 * 1024 * 1024], + ])); + + $this->consoleOutput->expects($this->at(0)) + ->method('writeln') + ->with('Log backend ownCloud: disabled'); + $this->consoleOutput->expects($this->at(1)) + ->method('writeln') + ->with('Log file: /var/log/owncloud.log'); + $this->consoleOutput->expects($this->at(2)) + ->method('writeln') + ->with('Rotate at: 5 MB'); + + self::invokePrivate($this->command, 'execute', [$this->consoleInput, $this->consoleOutput]); + } + +} diff --git a/tests/core/lostpassword/controller/lostcontrollertest.php b/tests/core/lostpassword/controller/lostcontrollertest.php index f82fc1ba3ff..0f8cb4fc5c8 100644 --- a/tests/core/lostpassword/controller/lostcontrollertest.php +++ b/tests/core/lostpassword/controller/lostcontrollertest.php @@ -1,9 +1,22 @@ <?php /** - * Copyright (c) 2014-2015 Lukas Reschke <lukas@owncloud.com> - * This file is licensed under the Affero General Public License version 3 or - * later. - * See the COPYING-README file. + * @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\Core\LostPassword\Controller; @@ -47,6 +60,8 @@ class LostControllerTest extends \PHPUnit_Framework_TestCase { ->disableOriginalConstructor()->getMock(); $this->container['SecureRandom'] = $this->getMockBuilder('\OCP\Security\ISecureRandom') ->disableOriginalConstructor()->getMock(); + $this->container['TimeFactory'] = $this->getMockBuilder('\OCP\AppFramework\Utility\ITimeFactory') + ->disableOriginalConstructor()->getMock(); $this->container['IsEncryptionEnabled'] = true; $this->lostController = $this->container['LostController']; } @@ -116,6 +131,10 @@ class LostControllerTest extends \PHPUnit_Framework_TestCase { ->method('userExists') ->with('ExistingUser') ->will($this->returnValue(true)); + $this->container['TimeFactory'] + ->expects($this->once()) + ->method('getTime') + ->will($this->returnValue(12348)); $this->container['Config'] ->expects($this->once()) ->method('getUserValue') @@ -128,7 +147,7 @@ class LostControllerTest extends \PHPUnit_Framework_TestCase { $this->container['Config'] ->expects($this->once()) ->method('setUserValue') - ->with('ExistingUser', 'owncloud', 'lostpassword', 'ThisIsMaybeANotSoSecretToken!'); + ->with('ExistingUser', 'owncloud', 'lostpassword', '12348:ThisIsMaybeANotSoSecretToken!'); $this->container['URLGenerator'] ->expects($this->once()) ->method('linkToRouteAbsolute') @@ -190,7 +209,11 @@ class LostControllerTest extends \PHPUnit_Framework_TestCase { $this->container['Config'] ->expects($this->once()) ->method('setUserValue') - ->with('ExistingUser', 'owncloud', 'lostpassword', 'ThisIsMaybeANotSoSecretToken!'); + ->with('ExistingUser', 'owncloud', 'lostpassword', '12348:ThisIsMaybeANotSoSecretToken!'); + $this->container['TimeFactory'] + ->expects($this->once()) + ->method('getTime') + ->will($this->returnValue(12348)); $this->container['URLGenerator'] ->expects($this->once()) ->method('linkToRouteAbsolute') @@ -256,9 +279,13 @@ class LostControllerTest extends \PHPUnit_Framework_TestCase { ->expects($this->once()) ->method('getUserValue') ->with('ValidTokenUser', 'owncloud', 'lostpassword', null) - ->will($this->returnValue('TheOnlyAndOnlyOneTokenToResetThePassword')); + ->will($this->returnValue('12345:TheOnlyAndOnlyOneTokenToResetThePassword')); $user = $this->getMockBuilder('\OCP\IUser') ->disableOriginalConstructor()->getMock(); + $user + ->expects($this->once()) + ->method('getLastLogin') + ->will($this->returnValue(12344)); $user->expects($this->once()) ->method('setPassword') ->with('NewPassword') @@ -272,12 +299,94 @@ class LostControllerTest extends \PHPUnit_Framework_TestCase { ->expects($this->once()) ->method('deleteUserValue') ->with('ValidTokenUser', 'owncloud', 'lostpassword'); + $this->container['TimeFactory'] + ->expects($this->once()) + ->method('getTime') + ->will($this->returnValue(12348)); $response = $this->lostController->setPassword('TheOnlyAndOnlyOneTokenToResetThePassword', 'ValidTokenUser', 'NewPassword', true); $expectedResponse = array('status' => 'success'); $this->assertSame($expectedResponse, $response); } + public function testSetPasswordExpiredToken() { + $this->container['Config'] + ->expects($this->once()) + ->method('getUserValue') + ->with('ValidTokenUser', 'owncloud', 'lostpassword', null) + ->will($this->returnValue('12345:TheOnlyAndOnlyOneTokenToResetThePassword')); + $user = $this->getMockBuilder('\OCP\IUser') + ->disableOriginalConstructor()->getMock(); + $this->container['UserManager'] + ->expects($this->once()) + ->method('get') + ->with('ValidTokenUser') + ->will($this->returnValue($user)); + $this->container['TimeFactory'] + ->expects($this->once()) + ->method('getTime') + ->will($this->returnValue(55546)); + + $response = $this->lostController->setPassword('TheOnlyAndOnlyOneTokenToResetThePassword', 'ValidTokenUser', 'NewPassword', true); + $expectedResponse = [ + 'status' => 'error', + 'msg' => 'Couldn\'t reset password because the token is expired', + ]; + $this->assertSame($expectedResponse, $response); + } + + public function testSetPasswordInvalidDataInDb() { + $this->container['Config'] + ->expects($this->once()) + ->method('getUserValue') + ->with('ValidTokenUser', 'owncloud', 'lostpassword', null) + ->will($this->returnValue('TheOnlyAndOnlyOneTokenToResetThePassword')); + $user = $this->getMockBuilder('\OCP\IUser') + ->disableOriginalConstructor()->getMock(); + $this->container['UserManager'] + ->expects($this->once()) + ->method('get') + ->with('ValidTokenUser') + ->will($this->returnValue($user)); + + $response = $this->lostController->setPassword('TheOnlyAndOnlyOneTokenToResetThePassword', 'ValidTokenUser', 'NewPassword', true); + $expectedResponse = [ + 'status' => 'error', + 'msg' => 'Couldn\'t reset password because the token is invalid', + ]; + $this->assertSame($expectedResponse, $response); + } + + public function testSetPasswordExpiredTokenDueToLogin() { + $this->container['Config'] + ->expects($this->once()) + ->method('getUserValue') + ->with('ValidTokenUser', 'owncloud', 'lostpassword', null) + ->will($this->returnValue('12345:TheOnlyAndOnlyOneTokenToResetThePassword')); + $user = $this->getMockBuilder('\OCP\IUser') + ->disableOriginalConstructor()->getMock(); + $user + ->expects($this->once()) + ->method('getLastLogin') + ->will($this->returnValue(12346)); + $this->container['UserManager'] + ->expects($this->once()) + ->method('get') + ->with('ValidTokenUser') + ->will($this->returnValue($user)); + $this->container['TimeFactory'] + ->expects($this->once()) + ->method('getTime') + ->will($this->returnValue(12345)); + + $response = $this->lostController->setPassword('TheOnlyAndOnlyOneTokenToResetThePassword', 'ValidTokenUser', 'NewPassword', true); + $expectedResponse = [ + 'status' => 'error', + 'msg' => 'Couldn\'t reset password because the token is expired', + ]; + $this->assertSame($expectedResponse, $response); + } + public function testIsSetPasswordWithoutTokenFailing() { $this->container['Config'] ->expects($this->once()) diff --git a/tests/lib/activitymanager.php b/tests/lib/activitymanager.php index 28caf575948..26759d46270 100644 --- a/tests/lib/activitymanager.php +++ b/tests/lib/activitymanager.php @@ -41,6 +41,9 @@ class Test_ActivityManager extends \Test\TestCase { $this->config ); + $this->assertSame([], $this->invokePrivate($this->activityManager, 'getConsumers')); + $this->assertSame([], $this->invokePrivate($this->activityManager, 'getExtensions')); + $this->activityManager->registerConsumer(function() { return new NoOpConsumer(); }); @@ -50,6 +53,11 @@ class Test_ActivityManager extends \Test\TestCase { $this->activityManager->registerExtension(function() { return new SimpleExtension(); }); + + $this->assertNotEmpty($this->invokePrivate($this->activityManager, 'getConsumers')); + $this->assertNotEmpty($this->invokePrivate($this->activityManager, 'getConsumers')); + $this->assertNotEmpty($this->invokePrivate($this->activityManager, 'getExtensions')); + $this->assertNotEmpty($this->invokePrivate($this->activityManager, 'getExtensions')); } public function testGetConsumers() { @@ -249,6 +257,192 @@ class Test_ActivityManager extends \Test\TestCase { ->method('getUser') ->willReturn($mockUser); } + + /** + * @expectedException BadMethodCallException + * @expectedExceptionMessage App not set + * @expectedExceptionCode 10 + */ + public function testPublishExceptionNoApp() { + $event = new \OC\Activity\Event(); + $this->activityManager->publish($event); + } + + /** + * @expectedException BadMethodCallException + * @expectedExceptionMessage Type not set + * @expectedExceptionCode 11 + */ + public function testPublishExceptionNoType() { + $event = new \OC\Activity\Event(); + $event->setApp('test'); + $this->activityManager->publish($event); + } + + /** + * @expectedException BadMethodCallException + * @expectedExceptionMessage Affected user not set + * @expectedExceptionCode 12 + */ + public function testPublishExceptionNoAffectedUser() { + $event = new \OC\Activity\Event(); + $event->setApp('test') + ->setType('test_type'); + $this->activityManager->publish($event); + } + + /** + * @expectedException BadMethodCallException + * @expectedExceptionMessage Subject not set + * @expectedExceptionCode 13 + */ + public function testPublishExceptionNoSubject() { + $event = new \OC\Activity\Event(); + $event->setApp('test') + ->setType('test_type') + ->setAffectedUser('test_affected'); + $this->activityManager->publish($event); + } + + public function dataPublish() { + return [ + [null], + ['test_author'], + ]; + } + + /** + * @dataProvider dataPublish + * @param string $author + */ + public function testPublish($author) { + if ($author !== null) { + $authorObject = $this->getMockBuilder('OCP\IUser') + ->disableOriginalConstructor() + ->getMock(); + $authorObject->expects($this->once()) + ->method('getUID') + ->willReturn($author); + $this->session->expects($this->atLeastOnce()) + ->method('getUser') + ->willReturn($authorObject); + } + + $event = new \OC\Activity\Event(); + $event->setApp('test') + ->setType('test_type') + ->setSubject('test_subject', []) + ->setAffectedUser('test_affected'); + + $consumer = $this->getMockBuilder('OCP\Activity\IConsumer') + ->disableOriginalConstructor() + ->getMock(); + $consumer->expects($this->once()) + ->method('receive') + ->with($event) + ->willReturnCallback(function(\OCP\Activity\IEvent $event) use ($author) { + $this->assertLessThanOrEqual(time() + 2, $event->getTimestamp(), 'Timestamp not set correctly'); + $this->assertGreaterThanOrEqual(time() - 2, $event->getTimestamp(), 'Timestamp not set correctly'); + $this->assertSame($author, $event->getAuthor(), 'Author name not set correctly'); + }); + $this->activityManager->registerConsumer(function () use ($consumer) { + return $consumer; + }); + + $this->activityManager->publish($event); + } + + public function testPublishAllManually() { + $event = new \OC\Activity\Event(); + $event->setApp('test_app') + ->setType('test_type') + ->setAffectedUser('test_affected') + ->setAuthor('test_author') + ->setTimestamp(1337) + ->setSubject('test_subject', ['test_subject_param']) + ->setMessage('test_message', ['test_message_param']) + ->setObject('test_object_type', 42, 'test_object_name') + ->setLink('test_link') + ; + + $consumer = $this->getMockBuilder('OCP\Activity\IConsumer') + ->disableOriginalConstructor() + ->getMock(); + $consumer->expects($this->once()) + ->method('receive') + ->willReturnCallback(function(\OCP\Activity\IEvent $event) { + $this->assertSame('test_app', $event->getApp(), 'App not set correctly'); + $this->assertSame('test_type', $event->getType(), 'Type not set correctly'); + $this->assertSame('test_affected', $event->getAffectedUser(), 'Affected user not set correctly'); + $this->assertSame('test_author', $event->getAuthor(), 'Author not set correctly'); + $this->assertSame(1337, $event->getTimestamp(), 'Timestamp not set correctly'); + $this->assertSame('test_subject', $event->getSubject(), 'Subject not set correctly'); + $this->assertSame(['test_subject_param'], $event->getSubjectParameters(), 'Subject parameter not set correctly'); + $this->assertSame('test_message', $event->getMessage(), 'Message not set correctly'); + $this->assertSame(['test_message_param'], $event->getMessageParameters(), 'Message parameter not set correctly'); + $this->assertSame('test_object_type', $event->getObjectType(), 'Object type not set correctly'); + $this->assertSame(42, $event->getObjectId(), 'Object ID not set correctly'); + $this->assertSame('test_object_name', $event->getObjectName(), 'Object name not set correctly'); + $this->assertSame('test_link', $event->getLink(), 'Link not set correctly'); + }); + $this->activityManager->registerConsumer(function () use ($consumer) { + return $consumer; + }); + + $this->activityManager->publish($event); + } + + public function testDeprecatedPublishActivity() { + $event = new \OC\Activity\Event(); + $event->setApp('test_app') + ->setType('test_type') + ->setAffectedUser('test_affected') + ->setAuthor('test_author') + ->setTimestamp(1337) + ->setSubject('test_subject', ['test_subject_param']) + ->setMessage('test_message', ['test_message_param']) + ->setObject('test_object_type', 42, 'test_object_name') + ->setLink('test_link') + ; + + $consumer = $this->getMockBuilder('OCP\Activity\IConsumer') + ->disableOriginalConstructor() + ->getMock(); + $consumer->expects($this->once()) + ->method('receive') + ->willReturnCallback(function(\OCP\Activity\IEvent $event) { + $this->assertSame('test_app', $event->getApp(), 'App not set correctly'); + $this->assertSame('test_type', $event->getType(), 'Type not set correctly'); + $this->assertSame('test_affected', $event->getAffectedUser(), 'Affected user not set correctly'); + $this->assertSame('test_subject', $event->getSubject(), 'Subject not set correctly'); + $this->assertSame(['test_subject_param'], $event->getSubjectParameters(), 'Subject parameter not set correctly'); + $this->assertSame('test_message', $event->getMessage(), 'Message not set correctly'); + $this->assertSame(['test_message_param'], $event->getMessageParameters(), 'Message parameter not set correctly'); + $this->assertSame('test_object_name', $event->getObjectName(), 'Object name not set correctly'); + $this->assertSame('test_link', $event->getLink(), 'Link not set correctly'); + + // The following values can not be used via publishActivity() + $this->assertLessThanOrEqual(time() + 2, $event->getTimestamp(), 'Timestamp not set correctly'); + $this->assertGreaterThanOrEqual(time() - 2, $event->getTimestamp(), 'Timestamp not set correctly'); + $this->assertSame(null, $event->getAuthor(), 'Author not set correctly'); + $this->assertSame('', $event->getObjectType(), 'Object type should not be set'); + $this->assertSame(0, $event->getObjectId(), 'Object ID should not be set'); + }); + $this->activityManager->registerConsumer(function () use ($consumer) { + return $consumer; + }); + + $this->activityManager->publishActivity( + $event->getApp(), + $event->getSubject(), $event->getSubjectParameters(), + $event->getMessage(), $event->getMessageParameters(), + $event->getObjectName(), + $event->getLink(), + $event->getAffectedUser(), + $event->getType(), + \OCP\Activity\IExtension::PRIORITY_MEDIUM + ); + } } class SimpleExtension implements \OCP\Activity\IExtension { @@ -368,6 +562,7 @@ class NoOpExtension implements \OCP\Activity\IExtension { class NoOpConsumer implements \OCP\Activity\IConsumer { - public function receive($app, $subject, $subjectParams, $message, $messageParams, $file, $link, $affectedUser, $type, $priority) { + public function receive(\OCP\Activity\IEvent $event) { + } } diff --git a/tests/lib/app/manager.php b/tests/lib/app/manager.php index 6cf7eb3bb6c..7333d7601b1 100644 --- a/tests/lib/app/manager.php +++ b/tests/lib/app/manager.php @@ -204,4 +204,74 @@ class Manager extends \PHPUnit_Framework_TestCase { $this->appConfig->setValue('test4', 'enabled', '["asd"]'); $this->assertEquals(['test1', 'test3'], $this->manager->getEnabledAppsForUser($user)); } + + public function testGetAppsNeedingUpgrade() { + $this->manager = $this->getMockBuilder('\OC\App\AppManager') + ->setConstructorArgs([$this->userSession, $this->appConfig, $this->groupManager, $this->cacheFactory]) + ->setMethods(['getAppInfo']) + ->getMock(); + + $appInfos = [ + 'test1' => ['id' => 'test1', 'version' => '1.0.1', 'requiremax' => '9.0.0'], + 'test2' => ['id' => 'test2', 'version' => '1.0.0', 'requiremin' => '8.2.0'], + 'test3' => ['id' => 'test3', 'version' => '1.2.4', 'requiremin' => '9.0.0'], + 'test4' => ['id' => 'test4', 'version' => '3.0.0', 'requiremin' => '8.1.0'], + 'testnoversion' => ['id' => 'testnoversion', 'requiremin' => '8.2.0'], + ]; + + $this->manager->expects($this->any()) + ->method('getAppInfo') + ->will($this->returnCallback( + function($appId) use ($appInfos) { + return $appInfos[$appId]; + } + )); + + $this->appConfig->setValue('test1', 'enabled', 'yes'); + $this->appConfig->setValue('test1', 'installed_version', '1.0.0'); + $this->appConfig->setValue('test2', 'enabled', 'yes'); + $this->appConfig->setValue('test2', 'installed_version', '1.0.0'); + $this->appConfig->setValue('test3', 'enabled', 'yes'); + $this->appConfig->setValue('test3', 'installed_version', '1.0.0'); + $this->appConfig->setValue('test4', 'enabled', 'yes'); + $this->appConfig->setValue('test4', 'installed_version', '2.4.0'); + + $apps = $this->manager->getAppsNeedingUpgrade('8.2.0'); + + $this->assertCount(2, $apps); + $this->assertEquals('test1', $apps[0]['id']); + $this->assertEquals('test4', $apps[1]['id']); + } + + public function testGetIncompatibleApps() { + $this->manager = $this->getMockBuilder('\OC\App\AppManager') + ->setConstructorArgs([$this->userSession, $this->appConfig, $this->groupManager, $this->cacheFactory]) + ->setMethods(['getAppInfo']) + ->getMock(); + + $appInfos = [ + 'test1' => ['id' => 'test1', 'version' => '1.0.1', 'requiremax' => '8.0.0'], + 'test2' => ['id' => 'test2', 'version' => '1.0.0', 'requiremin' => '8.2.0'], + 'test3' => ['id' => 'test3', 'version' => '1.2.4', 'requiremin' => '9.0.0'], + 'testnoversion' => ['id' => 'testnoversion', 'requiremin' => '8.2.0'], + ]; + + $this->manager->expects($this->any()) + ->method('getAppInfo') + ->will($this->returnCallback( + function($appId) use ($appInfos) { + return $appInfos[$appId]; + } + )); + + $this->appConfig->setValue('test1', 'enabled', 'yes'); + $this->appConfig->setValue('test2', 'enabled', 'yes'); + $this->appConfig->setValue('test3', 'enabled', 'yes'); + + $apps = $this->manager->getIncompatibleApps('8.2.0'); + + $this->assertCount(2, $apps); + $this->assertEquals('test1', $apps[0]['id']); + $this->assertEquals('test3', $apps[1]['id']); + } } diff --git a/tests/lib/appframework/controller/ApiControllerTest.php b/tests/lib/appframework/controller/ApiControllerTest.php index 137e5950f67..573fe7f3bad 100644 --- a/tests/lib/appframework/controller/ApiControllerTest.php +++ b/tests/lib/appframework/controller/ApiControllerTest.php @@ -38,6 +38,7 @@ class ApiControllerTest extends \Test\TestCase { $request = new Request( ['server' => ['HTTP_ORIGIN' => 'test']], $this->getMock('\OCP\Security\ISecureRandom'), + $this->getMock('\OCP\Security\ICrypto'), $this->getMock('\OCP\IConfig') ); $this->controller = new ChildApiController('app', $request, 'verbs', diff --git a/tests/lib/appframework/controller/ControllerTest.php b/tests/lib/appframework/controller/ControllerTest.php index 0d7716da411..243014a91a7 100644 --- a/tests/lib/appframework/controller/ControllerTest.php +++ b/tests/lib/appframework/controller/ControllerTest.php @@ -76,6 +76,7 @@ class ControllerTest extends \Test\TestCase { 'method' => 'hi', ], $this->getMock('\OCP\Security\ISecureRandom'), + $this->getMock('\OCP\Security\ICrypto'), $this->getMock('\OCP\IConfig') ); diff --git a/tests/lib/appframework/controller/OCSControllerTest.php b/tests/lib/appframework/controller/OCSControllerTest.php index 92b092cf0e9..292a56e3caa 100644 --- a/tests/lib/appframework/controller/OCSControllerTest.php +++ b/tests/lib/appframework/controller/OCSControllerTest.php @@ -43,6 +43,7 @@ class OCSControllerTest extends \Test\TestCase { ], ], $this->getMock('\OCP\Security\ISecureRandom'), + $this->getMock('\OCP\Security\ICrypto'), $this->getMock('\OCP\IConfig') ); $controller = new ChildOCSController('app', $request, 'verbs', @@ -64,6 +65,7 @@ class OCSControllerTest extends \Test\TestCase { $controller = new ChildOCSController('app', new Request( [], $this->getMock('\OCP\Security\ISecureRandom'), + $this->getMock('\OCP\Security\ICrypto'), $this->getMock('\OCP\IConfig') )); $expected = "<?xml version=\"1.0\"?>\n" . @@ -96,6 +98,7 @@ class OCSControllerTest extends \Test\TestCase { $controller = new ChildOCSController('app', new Request( [], $this->getMock('\OCP\Security\ISecureRandom'), + $this->getMock('\OCP\Security\ICrypto'), $this->getMock('\OCP\IConfig') )); $expected = "<?xml version=\"1.0\"?>\n" . @@ -128,6 +131,7 @@ class OCSControllerTest extends \Test\TestCase { $controller = new ChildOCSController('app', new Request( [], $this->getMock('\OCP\Security\ISecureRandom'), + $this->getMock('\OCP\Security\ICrypto'), $this->getMock('\OCP\IConfig') )); $expected = '{"ocs":{"meta":{"status":"failure","statuscode":400,"message":"OK",' . diff --git a/tests/lib/appframework/dependencyinjection/DIContainerTest.php b/tests/lib/appframework/dependencyinjection/DIContainerTest.php index 0cbdddbb205..598e70beffc 100644 --- a/tests/lib/appframework/dependencyinjection/DIContainerTest.php +++ b/tests/lib/appframework/dependencyinjection/DIContainerTest.php @@ -74,6 +74,7 @@ class DIContainerTest extends \Test\TestCase { $this->container['Request'] = new Request( ['method' => 'GET'], $this->getMock('\OCP\Security\ISecureRandom'), + $this->getMock('\OCP\Security\ICrypto'), $this->getMock('\OCP\IConfig') ); $security = $this->container['SecurityMiddleware']; diff --git a/tests/lib/appframework/http/DispatcherTest.php b/tests/lib/appframework/http/DispatcherTest.php index 02c86df8e72..c25fd7b6f85 100644 --- a/tests/lib/appframework/http/DispatcherTest.php +++ b/tests/lib/appframework/http/DispatcherTest.php @@ -295,6 +295,7 @@ class DispatcherTest extends \Test\TestCase { 'method' => 'POST' ], $this->getMock('\OCP\Security\ISecureRandom'), + $this->getMock('\OCP\Security\ICrypto'), $this->getMock('\OCP\IConfig') ); $this->dispatcher = new Dispatcher( @@ -322,6 +323,7 @@ class DispatcherTest extends \Test\TestCase { 'method' => 'POST', ], $this->getMock('\OCP\Security\ISecureRandom'), + $this->getMock('\OCP\Security\ICrypto'), $this->getMock('\OCP\IConfig') ); $this->dispatcher = new Dispatcher( @@ -352,6 +354,7 @@ class DispatcherTest extends \Test\TestCase { 'method' => 'GET' ], $this->getMock('\OCP\Security\ISecureRandom'), + $this->getMock('\OCP\Security\ICrypto'), $this->getMock('\OCP\IConfig') ); $this->dispatcher = new Dispatcher( @@ -381,6 +384,7 @@ class DispatcherTest extends \Test\TestCase { 'method' => 'GET' ], $this->getMock('\OCP\Security\ISecureRandom'), + $this->getMock('\OCP\Security\ICrypto'), $this->getMock('\OCP\IConfig') ); $this->dispatcher = new Dispatcher( @@ -411,6 +415,7 @@ class DispatcherTest extends \Test\TestCase { 'method' => 'PUT' ], $this->getMock('\OCP\Security\ISecureRandom'), + $this->getMock('\OCP\Security\ICrypto'), $this->getMock('\OCP\IConfig') ); $this->dispatcher = new Dispatcher( @@ -443,6 +448,7 @@ class DispatcherTest extends \Test\TestCase { 'method' => 'POST' ], $this->getMock('\OCP\Security\ISecureRandom'), + $this->getMock('\OCP\Security\ICrypto'), $this->getMock('\OCP\IConfig') ); $this->dispatcher = new Dispatcher( diff --git a/tests/lib/appframework/http/RequestTest.php b/tests/lib/appframework/http/RequestTest.php index 10a9e486c97..deb28909869 100644 --- a/tests/lib/appframework/http/RequestTest.php +++ b/tests/lib/appframework/http/RequestTest.php @@ -10,6 +10,7 @@ namespace OC\AppFramework\Http; +use OC\Security\Crypto; use OCP\Security\ISecureRandom; use OCP\IConfig; @@ -53,6 +54,7 @@ class RequestTest extends \Test\TestCase { $request = new Request( $vars, $this->secureRandom, + $this->getMock('\OCP\Security\ICrypto'), $this->config, $this->stream ); @@ -85,6 +87,7 @@ class RequestTest extends \Test\TestCase { $request = new Request( $vars, $this->secureRandom, + $this->getMock('\OCP\Security\ICrypto'), $this->config, $this->stream ); @@ -96,8 +99,8 @@ class RequestTest extends \Test\TestCase { /** - * @expectedException \RuntimeException - */ + * @expectedException \RuntimeException + */ public function testImmutableArrayAccess() { $vars = array( 'get' => array('name' => 'John Q. Public', 'nickname' => 'Joey'), @@ -107,6 +110,7 @@ class RequestTest extends \Test\TestCase { $request = new Request( $vars, $this->secureRandom, + $this->getMock('\OCP\Security\ICrypto'), $this->config, $this->stream ); @@ -115,8 +119,8 @@ class RequestTest extends \Test\TestCase { } /** - * @expectedException \RuntimeException - */ + * @expectedException \RuntimeException + */ public function testImmutableMagicAccess() { $vars = array( 'get' => array('name' => 'John Q. Public', 'nickname' => 'Joey'), @@ -126,6 +130,7 @@ class RequestTest extends \Test\TestCase { $request = new Request( $vars, $this->secureRandom, + $this->getMock('\OCP\Security\ICrypto'), $this->config, $this->stream ); @@ -134,8 +139,8 @@ class RequestTest extends \Test\TestCase { } /** - * @expectedException \LogicException - */ + * @expectedException \LogicException + */ public function testGetTheMethodRight() { $vars = array( 'get' => array('name' => 'John Q. Public', 'nickname' => 'Joey'), @@ -145,6 +150,7 @@ class RequestTest extends \Test\TestCase { $request = new Request( $vars, $this->secureRandom, + $this->getMock('\OCP\Security\ICrypto'), $this->config, $this->stream ); @@ -161,6 +167,7 @@ class RequestTest extends \Test\TestCase { $request = new Request( $vars, $this->secureRandom, + $this->getMock('\OCP\Security\ICrypto'), $this->config, $this->stream ); @@ -182,6 +189,7 @@ class RequestTest extends \Test\TestCase { $request = new Request( $vars, $this->secureRandom, + $this->getMock('\OCP\Security\ICrypto'), $this->config, $this->stream ); @@ -206,6 +214,7 @@ class RequestTest extends \Test\TestCase { $request = new Request( $vars, $this->secureRandom, + $this->getMock('\OCP\Security\ICrypto'), $this->config, $this->stream ); @@ -230,6 +239,7 @@ class RequestTest extends \Test\TestCase { $request = new Request( $vars, $this->secureRandom, + $this->getMock('\OCP\Security\ICrypto'), $this->config, $this->stream ); @@ -250,6 +260,7 @@ class RequestTest extends \Test\TestCase { $request = new Request( $vars, $this->secureRandom, + $this->getMock('\OCP\Security\ICrypto'), $this->config, $this->stream ); @@ -274,6 +285,7 @@ class RequestTest extends \Test\TestCase { $request = new Request( $vars, $this->secureRandom, + $this->getMock('\OCP\Security\ICrypto'), $this->config, $this->stream ); @@ -303,6 +315,7 @@ class RequestTest extends \Test\TestCase { $request = new Request( $vars, $this->secureRandom, + $this->getMock('\OCP\Security\ICrypto'), $this->config, $this->stream ); @@ -324,6 +337,7 @@ class RequestTest extends \Test\TestCase { $request = new Request( $vars, $this->secureRandom, + $this->getMock('\OCP\Security\ICrypto'), $this->config, $this->stream ); @@ -347,6 +361,7 @@ class RequestTest extends \Test\TestCase { $request = new Request( [], $this->secureRandom, + $this->getMock('\OCP\Security\ICrypto'), $this->config, $this->stream ); @@ -358,6 +373,7 @@ class RequestTest extends \Test\TestCase { $request = new Request( [], \OC::$server->getSecureRandom(), + $this->getMock('\OCP\Security\ICrypto'), $this->config, $this->stream ); @@ -382,6 +398,7 @@ class RequestTest extends \Test\TestCase { ], ], $this->secureRandom, + $this->getMock('\OCP\Security\ICrypto'), $this->config, $this->stream ); @@ -410,6 +427,7 @@ class RequestTest extends \Test\TestCase { ], ], $this->secureRandom, + $this->getMock('\OCP\Security\ICrypto'), $this->config, $this->stream ); @@ -438,6 +456,7 @@ class RequestTest extends \Test\TestCase { ], ], $this->secureRandom, + $this->getMock('\OCP\Security\ICrypto'), $this->config, $this->stream ); @@ -470,6 +489,7 @@ class RequestTest extends \Test\TestCase { ], ], $this->secureRandom, + $this->getMock('\OCP\Security\ICrypto'), $this->config, $this->stream ); @@ -497,6 +517,7 @@ class RequestTest extends \Test\TestCase { $request = new Request( [], $this->secureRandom, + $this->getMock('\OCP\Security\ICrypto'), $this->config, $this->stream ); @@ -506,10 +527,10 @@ class RequestTest extends \Test\TestCase { public function testGetServerProtocolWithProtoValid() { $this->config - ->expects($this->exactly(2)) - ->method('getSystemValue') - ->with('overwriteprotocol') - ->will($this->returnValue('')); + ->expects($this->exactly(2)) + ->method('getSystemValue') + ->with('overwriteprotocol') + ->will($this->returnValue('')); $requestHttps = new Request( [ @@ -518,6 +539,7 @@ class RequestTest extends \Test\TestCase { ], ], $this->secureRandom, + $this->getMock('\OCP\Security\ICrypto'), $this->config, $this->stream ); @@ -528,6 +550,7 @@ class RequestTest extends \Test\TestCase { ], ], $this->secureRandom, + $this->getMock('\OCP\Security\ICrypto'), $this->config, $this->stream ); @@ -551,6 +574,7 @@ class RequestTest extends \Test\TestCase { ], ], $this->secureRandom, + $this->getMock('\OCP\Security\ICrypto'), $this->config, $this->stream ); @@ -571,6 +595,7 @@ class RequestTest extends \Test\TestCase { ], ], $this->secureRandom, + $this->getMock('\OCP\Security\ICrypto'), $this->config, $this->stream ); @@ -587,6 +612,7 @@ class RequestTest extends \Test\TestCase { $request = new Request( [], $this->secureRandom, + $this->getMock('\OCP\Security\ICrypto'), $this->config, $this->stream ); @@ -607,6 +633,7 @@ class RequestTest extends \Test\TestCase { ], ], $this->secureRandom, + $this->getMock('\OCP\Security\ICrypto'), $this->config, $this->stream ); @@ -628,6 +655,7 @@ class RequestTest extends \Test\TestCase { ] ], $this->secureRandom, + $this->getMock('\OCP\Security\ICrypto'), $this->config, $this->stream ); @@ -716,6 +744,7 @@ class RequestTest extends \Test\TestCase { ] ], $this->secureRandom, + $this->getMock('\OCP\Security\ICrypto'), $this->config, $this->stream ); @@ -732,6 +761,7 @@ class RequestTest extends \Test\TestCase { ] ], $this->secureRandom, + $this->getMock('\OCP\Security\ICrypto'), $this->config, $this->stream ); @@ -749,6 +779,7 @@ class RequestTest extends \Test\TestCase { ] ], $this->secureRandom, + $this->getMock('\OCP\Security\ICrypto'), $this->config, $this->stream ); @@ -766,6 +797,7 @@ class RequestTest extends \Test\TestCase { ] ], $this->secureRandom, + $this->getMock('\OCP\Security\ICrypto'), $this->config, $this->stream ); @@ -793,6 +825,7 @@ class RequestTest extends \Test\TestCase { $request = new Request( [], $this->secureRandom, + $this->getMock('\OCP\Security\ICrypto'), $this->config, $this->stream ); @@ -814,6 +847,7 @@ class RequestTest extends \Test\TestCase { ], ], $this->secureRandom, + $this->getMock('\OCP\Security\ICrypto'), $this->config, $this->stream ); @@ -840,6 +874,7 @@ class RequestTest extends \Test\TestCase { ], ], $this->secureRandom, + $this->getMock('\OCP\Security\ICrypto'), $this->config, $this->stream ); @@ -866,6 +901,7 @@ class RequestTest extends \Test\TestCase { ], ], $this->secureRandom, + $this->getMock('\OCP\Security\ICrypto'), $this->config, $this->stream ); @@ -882,6 +918,7 @@ class RequestTest extends \Test\TestCase { $request = new Request( [], $this->secureRandom, + $this->getMock('\OCP\Security\ICrypto'), $this->config, $this->stream ); @@ -909,6 +946,7 @@ class RequestTest extends \Test\TestCase { $request = new Request( [], $this->secureRandom, + $this->getMock('\OCP\Security\ICrypto'), $this->config, $this->stream ); @@ -924,6 +962,7 @@ class RequestTest extends \Test\TestCase { ] ], $this->secureRandom, + $this->getMock('\OCP\Security\ICrypto'), $this->config, $this->stream ); @@ -944,6 +983,7 @@ class RequestTest extends \Test\TestCase { ] ], $this->secureRandom, + $this->getMock('\OCP\Security\ICrypto'), $this->config, $this->stream ); @@ -964,6 +1004,7 @@ class RequestTest extends \Test\TestCase { ] ], $this->secureRandom, + $this->getMock('\OCP\Security\ICrypto'), $this->config, $this->stream ); @@ -986,6 +1027,7 @@ class RequestTest extends \Test\TestCase { ] ], $this->secureRandom, + $this->getMock('\OCP\Security\ICrypto'), $this->config, $this->stream ); @@ -1008,6 +1050,7 @@ class RequestTest extends \Test\TestCase { ] ], $this->secureRandom, + $this->getMock('\OCP\Security\ICrypto'), $this->config, $this->stream ); @@ -1030,6 +1073,7 @@ class RequestTest extends \Test\TestCase { ] ], $this->secureRandom, + $this->getMock('\OCP\Security\ICrypto'), $this->config, $this->stream ); @@ -1052,6 +1096,7 @@ class RequestTest extends \Test\TestCase { ] ], $this->secureRandom, + $this->getMock('\OCP\Security\ICrypto'), $this->config, $this->stream ); @@ -1105,6 +1150,7 @@ class RequestTest extends \Test\TestCase { ] ], $this->secureRandom, + $this->getMock('\OCP\Security\ICrypto'), $this->config, $this->stream ); @@ -1144,6 +1190,7 @@ class RequestTest extends \Test\TestCase { ] ], $this->secureRandom, + $this->getMock('\OCP\Security\ICrypto'), $this->config, $this->stream ]) @@ -1157,17 +1204,25 @@ class RequestTest extends \Test\TestCase { } public function testPassesCSRFCheckWithGet() { + $crypto = $this->getMock('\OCP\Security\ICrypto'); + $crypto + ->expects($this->once()) + ->method('decrypt') + ->with('1c637c4147e40a8a8f09428ec2059cebea3480c27b402b4e793c69710a731513|wlXxNUaFqHuQnZr5|e6ab49c9e0e20c8d3607e02f1d8e6ec17ad6020ae10b7d64ab4b0a6318c0875940943a6aa303dc090fea0b4cd5b9fb8bcbecac4308a2bd15d9f369cdc22121a4', 'secret') + ->will($this->returnValue('MyStoredRequestToken')); + /** @var Request $request */ $request = $this->getMockBuilder('\OC\AppFramework\Http\Request') ->setMethods(['getScriptName']) ->setConstructorArgs([ [ 'get' => [ - 'requesttoken' => 'MyStoredRequestToken', + 'requesttoken' => '1c637c4147e40a8a8f09428ec2059cebea3480c27b402b4e793c69710a731513|wlXxNUaFqHuQnZr5|e6ab49c9e0e20c8d3607e02f1d8e6ec17ad6020ae10b7d64ab4b0a6318c0875940943a6aa303dc090fea0b4cd5b9fb8bcbecac4308a2bd15d9f369cdc22121a4:secret', ], 'requesttoken' => 'MyStoredRequestToken', ], $this->secureRandom, + $crypto, $this->config, $this->stream ]) @@ -1177,17 +1232,25 @@ class RequestTest extends \Test\TestCase { } public function testPassesCSRFCheckWithPost() { + $crypto = $this->getMock('\OCP\Security\ICrypto'); + $crypto + ->expects($this->once()) + ->method('decrypt') + ->with('1c637c4147e40a8a8f09428ec2059cebea3480c27b402b4e793c69710a731513|wlXxNUaFqHuQnZr5|e6ab49c9e0e20c8d3607e02f1d8e6ec17ad6020ae10b7d64ab4b0a6318c0875940943a6aa303dc090fea0b4cd5b9fb8bcbecac4308a2bd15d9f369cdc22121a4', 'secret') + ->will($this->returnValue('MyStoredRequestToken')); + /** @var Request $request */ $request = $this->getMockBuilder('\OC\AppFramework\Http\Request') ->setMethods(['getScriptName']) ->setConstructorArgs([ [ 'post' => [ - 'requesttoken' => 'MyStoredRequestToken', + 'requesttoken' => '1c637c4147e40a8a8f09428ec2059cebea3480c27b402b4e793c69710a731513|wlXxNUaFqHuQnZr5|e6ab49c9e0e20c8d3607e02f1d8e6ec17ad6020ae10b7d64ab4b0a6318c0875940943a6aa303dc090fea0b4cd5b9fb8bcbecac4308a2bd15d9f369cdc22121a4:secret', ], 'requesttoken' => 'MyStoredRequestToken', ], $this->secureRandom, + $crypto, $this->config, $this->stream ]) @@ -1197,17 +1260,24 @@ class RequestTest extends \Test\TestCase { } public function testPassesCSRFCheckWithHeader() { + $crypto = $this->getMock('\OCP\Security\ICrypto'); + $crypto + ->expects($this->once()) + ->method('decrypt') + ->with('1c637c4147e40a8a8f09428ec2059cebea3480c27b402b4e793c69710a731513|wlXxNUaFqHuQnZr5|e6ab49c9e0e20c8d3607e02f1d8e6ec17ad6020ae10b7d64ab4b0a6318c0875940943a6aa303dc090fea0b4cd5b9fb8bcbecac4308a2bd15d9f369cdc22121a4', 'secret') + ->will($this->returnValue('MyStoredRequestToken')); /** @var Request $request */ $request = $this->getMockBuilder('\OC\AppFramework\Http\Request') ->setMethods(['getScriptName']) ->setConstructorArgs([ [ 'server' => [ - 'HTTP_REQUESTTOKEN' => 'MyStoredRequestToken', + 'HTTP_REQUESTTOKEN' => '1c637c4147e40a8a8f09428ec2059cebea3480c27b402b4e793c69710a731513|wlXxNUaFqHuQnZr5|e6ab49c9e0e20c8d3607e02f1d8e6ec17ad6020ae10b7d64ab4b0a6318c0875940943a6aa303dc090fea0b4cd5b9fb8bcbecac4308a2bd15d9f369cdc22121a4:secret', ], 'requesttoken' => 'MyStoredRequestToken', ], $this->secureRandom, + $crypto, $this->config, $this->stream ]) @@ -1216,18 +1286,34 @@ class RequestTest extends \Test\TestCase { $this->assertTrue($request->passesCSRFCheck()); } - public function testPassesCSRFCheckWithInvalidToken() { + public function invalidTokenDataProvider() { + return [ + ['InvalidSentToken'], + ['InvalidSentToken:InvalidSecret'], + [null], + [''], + ]; + } + + /** + * @dataProvider invalidTokenDataProvider + * @param string $invalidToken + */ + public function testPassesCSRFCheckWithInvalidToken($invalidToken) { + $crypto = new Crypto($this->config, $this->secureRandom); + /** @var Request $request */ $request = $this->getMockBuilder('\OC\AppFramework\Http\Request') ->setMethods(['getScriptName']) ->setConstructorArgs([ [ 'server' => [ - 'HTTP_REQUESTTOKEN' => 'MyInvalidSentToken', + 'HTTP_REQUESTTOKEN' => $invalidToken, ], 'requesttoken' => 'MyStoredRequestToken', ], $this->secureRandom, + $crypto, $this->config, $this->stream ]) @@ -1243,6 +1329,7 @@ class RequestTest extends \Test\TestCase { ->setConstructorArgs([ [], $this->secureRandom, + $this->getMock('\OCP\Security\ICrypto'), $this->config, $this->stream ]) diff --git a/tests/lib/appframework/middleware/MiddlewareDispatcherTest.php b/tests/lib/appframework/middleware/MiddlewareDispatcherTest.php index a8731525798..eab45b03c98 100644 --- a/tests/lib/appframework/middleware/MiddlewareDispatcherTest.php +++ b/tests/lib/appframework/middleware/MiddlewareDispatcherTest.php @@ -133,6 +133,7 @@ class MiddlewareDispatcherTest extends \Test\TestCase { new Request( ['method' => 'GET'], $this->getMock('\OCP\Security\ISecureRandom'), + $this->getMock('\OCP\Security\ICrypto'), $this->getMock('\OCP\IConfig') ) ] diff --git a/tests/lib/appframework/middleware/MiddlewareTest.php b/tests/lib/appframework/middleware/MiddlewareTest.php index 33f04e1383d..8e077b37e2f 100644 --- a/tests/lib/appframework/middleware/MiddlewareTest.php +++ b/tests/lib/appframework/middleware/MiddlewareTest.php @@ -61,6 +61,7 @@ class MiddlewareTest extends \Test\TestCase { new Request( [], $this->getMock('\OCP\Security\ISecureRandom'), + $this->getMock('\OCP\Security\ICrypto'), $this->getMock('\OCP\IConfig') ) ] diff --git a/tests/lib/appframework/middleware/security/CORSMiddlewareTest.php b/tests/lib/appframework/middleware/security/CORSMiddlewareTest.php index ca526fb859c..f5e6106dc3a 100644 --- a/tests/lib/appframework/middleware/security/CORSMiddlewareTest.php +++ b/tests/lib/appframework/middleware/security/CORSMiddlewareTest.php @@ -42,6 +42,7 @@ class CORSMiddlewareTest extends \Test\TestCase { ] ], $this->getMock('\OCP\Security\ISecureRandom'), + $this->getMock('\OCP\Security\ICrypto'), $this->getMock('\OCP\IConfig') ); $this->reflector->reflect($this, __FUNCTION__); @@ -61,6 +62,7 @@ class CORSMiddlewareTest extends \Test\TestCase { ] ], $this->getMock('\OCP\Security\ISecureRandom'), + $this->getMock('\OCP\Security\ICrypto'), $this->getMock('\OCP\IConfig') ); $middleware = new CORSMiddleware($request, $this->reflector, $this->session); @@ -78,6 +80,7 @@ class CORSMiddlewareTest extends \Test\TestCase { $request = new Request( [], $this->getMock('\OCP\Security\ISecureRandom'), + $this->getMock('\OCP\Security\ICrypto'), $this->getMock('\OCP\IConfig') ); $this->reflector->reflect($this, __FUNCTION__); @@ -101,6 +104,7 @@ class CORSMiddlewareTest extends \Test\TestCase { ] ], $this->getMock('\OCP\Security\ISecureRandom'), + $this->getMock('\OCP\Security\ICrypto'), $this->getMock('\OCP\IConfig') ); $this->reflector->reflect($this, __FUNCTION__); @@ -119,6 +123,7 @@ class CORSMiddlewareTest extends \Test\TestCase { $request = new Request( [], $this->getMock('\OCP\Security\ISecureRandom'), + $this->getMock('\OCP\Security\ICrypto'), $this->getMock('\OCP\IConfig') ); $this->reflector->reflect($this, __FUNCTION__); @@ -144,6 +149,7 @@ class CORSMiddlewareTest extends \Test\TestCase { 'PHP_AUTH_PW' => 'pass' ]], $this->getMock('\OCP\Security\ISecureRandom'), + $this->getMock('\OCP\Security\ICrypto'), $this->getMock('\OCP\IConfig') ); $this->session->expects($this->once()) @@ -169,6 +175,7 @@ class CORSMiddlewareTest extends \Test\TestCase { 'PHP_AUTH_PW' => 'pass' ]], $this->getMock('\OCP\Security\ISecureRandom'), + $this->getMock('\OCP\Security\ICrypto'), $this->getMock('\OCP\IConfig') ); $this->session->expects($this->once()) @@ -190,6 +197,7 @@ class CORSMiddlewareTest extends \Test\TestCase { 'PHP_AUTH_PW' => 'pass' ]], $this->getMock('\OCP\Security\ISecureRandom'), + $this->getMock('\OCP\Security\ICrypto'), $this->getMock('\OCP\IConfig') ); $middleware = new CORSMiddleware($request, $this->reflector, $this->session); @@ -206,6 +214,7 @@ class CORSMiddlewareTest extends \Test\TestCase { 'PHP_AUTH_PW' => 'pass' ]], $this->getMock('\OCP\Security\ISecureRandom'), + $this->getMock('\OCP\Security\ICrypto'), $this->getMock('\OCP\IConfig') ); $middleware = new CORSMiddleware($request, $this->reflector, $this->session); @@ -226,6 +235,7 @@ class CORSMiddlewareTest extends \Test\TestCase { 'PHP_AUTH_PW' => 'pass' ]], $this->getMock('\OCP\Security\ISecureRandom'), + $this->getMock('\OCP\Security\ICrypto'), $this->getMock('\OCP\IConfig') ); $middleware = new CORSMiddleware($request, $this->reflector, $this->session); diff --git a/tests/lib/appframework/middleware/security/SecurityMiddlewareTest.php b/tests/lib/appframework/middleware/security/SecurityMiddlewareTest.php index 347a0423ea6..3b4d7987e94 100644 --- a/tests/lib/appframework/middleware/security/SecurityMiddlewareTest.php +++ b/tests/lib/appframework/middleware/security/SecurityMiddlewareTest.php @@ -322,6 +322,7 @@ class SecurityMiddlewareTest extends \Test\TestCase { ] ], $this->getMock('\OCP\Security\ISecureRandom'), + $this->getMock('\OCP\Security\ICrypto'), $this->getMock('\OCP\IConfig') ); $this->middleware = $this->getMiddleware(true, true); diff --git a/tests/lib/appframework/middleware/sessionmiddlewaretest.php b/tests/lib/appframework/middleware/sessionmiddlewaretest.php index 11c1600f515..06390e96d4c 100644 --- a/tests/lib/appframework/middleware/sessionmiddlewaretest.php +++ b/tests/lib/appframework/middleware/sessionmiddlewaretest.php @@ -36,6 +36,7 @@ class SessionMiddlewareTest extends \Test\TestCase { $this->request = new Request( [], $this->getMockBuilder('\OCP\Security\ISecureRandom')->getMock(), + $this->getMock('\OCP\Security\ICrypto'), $this->getMock('\OCP\IConfig') ); $this->reflector = new ControllerMethodReflector(); diff --git a/tests/lib/backgroundjob/joblist.php b/tests/lib/backgroundjob/joblist.php index 13bee12479e..d5136676a47 100644 --- a/tests/lib/backgroundjob/joblist.php +++ b/tests/lib/backgroundjob/joblist.php @@ -202,4 +202,30 @@ class JobList extends \Test\TestCase { $this->instance->remove($job); } + + public function testGetNextNonExisting() { + $job = new TestJob(); + $this->instance->add($job, 1); + $this->instance->add('\OC\Non\Existing\Class'); + $this->instance->add($job, 2); + + $jobs = $this->instance->getAll(); + + $savedJob1 = $jobs[count($jobs) - 2]; + $savedJob2 = $jobs[count($jobs) - 1]; + + $this->config->expects($this->any()) + ->method('getAppValue') + ->with('backgroundjob', 'lastjob', 0) + ->will($this->returnValue($savedJob1->getId())); + + $this->instance->getNext(); + + $nextJob = $this->instance->getNext(); + + $this->assertEquals($savedJob2, $nextJob); + + $this->instance->remove($job, 1); + $this->instance->remove($job, 2); + } } diff --git a/tests/lib/connector/sabre/requesttest/requesttest.php b/tests/lib/connector/sabre/requesttest/requesttest.php index c7739aefcd7..7f33dcf817b 100644 --- a/tests/lib/connector/sabre/requesttest/requesttest.php +++ b/tests/lib/connector/sabre/requesttest/requesttest.php @@ -11,22 +11,18 @@ namespace Test\Connector\Sabre\RequestTest; use OC\Connector\Sabre\Server; use OC\Connector\Sabre\ServerFactory; use OC\Files\Mount\MountPoint; +use OC\Files\Storage\StorageFactory; use OC\Files\Storage\Temporary; use OC\Files\View; use OCP\IUser; use Sabre\HTTP\Request; use Test\TestCase; +use Test\Traits\MountProviderTrait; +use Test\Traits\UserTrait; abstract class RequestTest extends TestCase { - /** - * @var \OC_User_Dummy - */ - protected $userBackend; - - /** - * @var \OCP\Files\Config\IMountProvider[] - */ - protected $mountProviders; + use UserTrait; + use MountProviderTrait; /** * @var \OC\Connector\Sabre\ServerFactory @@ -40,33 +36,8 @@ abstract class RequestTest extends TestCase { return $stream; } - /** - * @param $userId - * @param $storages - * @return \OCP\Files\Config\IMountProvider - */ - protected function getMountProvider($userId, $storages) { - $mounts = []; - foreach ($storages as $mountPoint => $storage) { - $mounts[] = new MountPoint($storage, $mountPoint); - } - $provider = $this->getMock('\OCP\Files\Config\IMountProvider'); - $provider->expects($this->any()) - ->method('getMountsForUser') - ->will($this->returnCallback(function (IUser $user) use ($userId, $mounts) { - if ($user->getUID() === $userId) { - return $mounts; - } else { - return []; - } - })); - return $provider; - } - protected function setUp() { parent::setUp(); - $this->userBackend = new \OC_User_Dummy(); - \OC::$server->getUserManager()->registerBackend($this->userBackend); $this->serverFactory = new ServerFactory( \OC::$server->getConfig(), @@ -78,16 +49,9 @@ abstract class RequestTest extends TestCase { ); } - protected function tearDown() { - parent::tearDown(); - \OC::$server->getUserManager()->removeBackend($this->userBackend); - } - protected function setupUser($name, $password) { - $this->userBackend->createUser($name, $password); - \OC::$server->getMountProviderCollection()->registerProvider($this->getMountProvider($name, [ - '/' . $name => new Temporary() - ])); + $this->createUser($name, $password); + $this->registerMount($name, '\OC\Files\Storage\Temporary', '/' . $name); $this->loginAsUser($name); return new View('/' . $name . '/files'); } diff --git a/tests/lib/server.php b/tests/lib/server.php index 9c5c83ceb5c..bc44c50a22a 100644 --- a/tests/lib/server.php +++ b/tests/lib/server.php @@ -56,6 +56,7 @@ class Server extends \Test\TestCase { ['ContactsManager', '\OCP\Contacts\IManager'], ['Crypto', '\OC\Security\Crypto'], ['Crypto', '\OCP\Security\ICrypto'], + ['CryptoWrapper', '\OC\Session\CryptoWrapper'], ['DatabaseConnection', '\OC\DB\Connection'], ['DatabaseConnection', '\OCP\IDBConnection'], diff --git a/tests/lib/session/cryptosessiondatatest.php b/tests/lib/session/cryptosessiondatatest.php new file mode 100644 index 00000000000..ee6bcbf11c1 --- /dev/null +++ b/tests/lib/session/cryptosessiondatatest.php @@ -0,0 +1,53 @@ +<?php +/** + * @author Joas Schilling <nickvergessen@owncloud.com> + * + * @copyright Copyright (c) 2015, ownCloud, Inc. + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see <http://www.gnu.org/licenses/> + * + */ + +namespace Test\Session; + +use OC\Session\CryptoSessionData; + +class CryptoSessionDataTest extends Session { + /** @var \PHPUnit_Framework_MockObject_MockObject|\OCP\Security\ICrypto */ + protected $crypto; + + /** @var \OCP\ISession */ + protected $wrappedSession; + + protected function setUp() { + parent::setUp(); + + $this->wrappedSession = new \OC\Session\Memory($this->getUniqueID()); + $this->crypto = $this->getMockBuilder('OCP\Security\ICrypto') + ->disableOriginalConstructor() + ->getMock(); + $this->crypto->expects($this->any()) + ->method('encrypt') + ->willReturnCallback(function ($input) { + return '#' . $input . '#'; + }); + $this->crypto->expects($this->any()) + ->method('decrypt') + ->willReturnCallback(function ($input) { + return substr($input, 1, -1); + }); + + $this->instance = new CryptoSessionData($this->wrappedSession, $this->crypto, 'PASS'); + } +} diff --git a/tests/lib/session/cryptowrappingtest.php b/tests/lib/session/cryptowrappingtest.php new file mode 100644 index 00000000000..12b3c905b7f --- /dev/null +++ b/tests/lib/session/cryptowrappingtest.php @@ -0,0 +1,82 @@ +<?php +/** + * @author Joas Schilling <nickvergessen@owncloud.com> + * + * @copyright Copyright (c) 2015, ownCloud, Inc. + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see <http://www.gnu.org/licenses/> + * + */ + +namespace Test\Session; + +use OC\Session\CryptoSessionData; +use Test\TestCase; + +class CryptoWrappingTest extends TestCase { + /** @var \PHPUnit_Framework_MockObject_MockObject|\OCP\Security\ICrypto */ + protected $crypto; + + /** @var \PHPUnit_Framework_MockObject_MockObject|\OCP\ISession */ + protected $wrappedSession; + + /** @var \OC\Session\CryptoSessionData */ + protected $instance; + + protected function setUp() { + parent::setUp(); + + $this->wrappedSession = $this->getMockBuilder('OCP\ISession') + ->disableOriginalConstructor() + ->getMock(); + $this->crypto = $this->getMockBuilder('OCP\Security\ICrypto') + ->disableOriginalConstructor() + ->getMock(); + $this->crypto->expects($this->any()) + ->method('encrypt') + ->willReturnCallback(function ($input) { + return $input; + }); + $this->crypto->expects($this->any()) + ->method('decrypt') + ->willReturnCallback(function ($input) { + return substr($input, 1, -1); + }); + + $this->instance = new CryptoSessionData($this->wrappedSession, $this->crypto, 'PASS'); + } + + public function testWrappingSet() { + $unencryptedValue = 'foobar'; + + $this->wrappedSession->expects($this->once()) + ->method('set') + ->with('key', $this->crypto->encrypt(json_encode($unencryptedValue))); + $this->instance->set('key', $unencryptedValue); + } + + public function testUnwrappingGet() { + $unencryptedValue = 'foobar'; + $encryptedValue = $this->crypto->encrypt($unencryptedValue); + + $this->wrappedSession->expects($this->once()) + ->method('get') + ->with('key') + ->willReturnCallback(function () use ($encryptedValue) { + return $encryptedValue; + }); + + $this->assertSame($unencryptedValue, $this->wrappedSession->get('key')); + } +} diff --git a/tests/lib/testcase.php b/tests/lib/testcase.php index fd0b8d5f2de..854d5f4f65b 100644 --- a/tests/lib/testcase.php +++ b/tests/lib/testcase.php @@ -32,21 +32,52 @@ abstract class TestCase extends \PHPUnit_Framework_TestCase { */ private $commandBus; + protected function getTestTraits() { + $traits = []; + $class = $this; + do { + $traits = array_merge(class_uses($class), $traits); + } while ($class = get_parent_class($class)); + foreach ($traits as $trait => $same) { + $traits = array_merge(class_uses($trait), $traits); + } + $traits = array_unique($traits); + return array_filter($traits, function ($trait) { + return substr($trait, 0, 5) === 'Test\\'; + }); + } + protected function setUp() { // overwrite the command bus with one we can run ourselves $this->commandBus = new QueueBus(); \OC::$server->registerService('AsyncCommandBus', function () { return $this->commandBus; }); + + $traits = $this->getTestTraits(); + foreach ($traits as $trait) { + $methodName = 'setUp' . basename(str_replace('\\', '/', $trait)); + if (method_exists($this, $methodName)) { + call_user_func([$this, $methodName]); + } + } } protected function tearDown() { $hookExceptions = \OC_Hook::$thrownExceptions; \OC_Hook::$thrownExceptions = []; \OC::$server->getLockingProvider()->releaseAll(); - if(!empty($hookExceptions)) { + if (!empty($hookExceptions)) { throw $hookExceptions[0]; } + + $traits = $this->getTestTraits(); + foreach ($traits as $trait) { + $methodName = 'tearDown' . basename(str_replace('\\', '/', $trait)); + if (method_exists($this, $methodName)) { + call_user_func([$this, $methodName]); + } + } } /** diff --git a/tests/lib/traits/mountprovidertrait.php b/tests/lib/traits/mountprovidertrait.php new file mode 100644 index 00000000000..66eca1597c8 --- /dev/null +++ b/tests/lib/traits/mountprovidertrait.php @@ -0,0 +1,56 @@ +<?php +/** + * Copyright (c) 2015 Robin Appelman <icewind@owncloud.com> + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. + */ + +namespace Test\Traits; + +use OC\Files\Mount\MountPoint; +use OC\Files\Storage\StorageFactory; +use OCP\IUser; + +/** + * Allow setting mounts for users + */ +trait MountProviderTrait { + /** + * @var \OCP\Files\Config\IMountProvider + */ + protected $mountProvider; + + /** + * @var \OC\Files\Storage\StorageFactory + */ + protected $storageFactory; + + protected $mounts = []; + + protected function registerMount($userId, $storage, $mountPoint, $arguments = null) { + if (!isset($this->mounts[$userId])) { + $this->mounts[$userId] = []; + } + $this->mounts[$userId][] = new MountPoint($storage, $mountPoint, $arguments, $this->storageFactory); + } + + protected function registerStorageWrapper($name, $wrapper) { + $this->storageFactory->addStorageWrapper($name, $wrapper); + } + + protected function setUpMountProviderTrait() { + $this->storageFactory = new StorageFactory(); + $this->mountProvider = $this->getMock('\OCP\Files\Config\IMountProvider'); + $this->mountProvider->expects($this->any()) + ->method('getMountsForUser') + ->will($this->returnCallback(function (IUser $user) { + if (isset($this->mounts[$user->getUID()])) { + return $this->mounts[$user->getUID()]; + } else { + return []; + } + })); + \OC::$server->getMountProviderCollection()->registerProvider($this->mountProvider); + } +} diff --git a/tests/lib/traits/usertrait.php b/tests/lib/traits/usertrait.php new file mode 100644 index 00000000000..401d8b8a832 --- /dev/null +++ b/tests/lib/traits/usertrait.php @@ -0,0 +1,32 @@ +<?php +/** + * Copyright (c) 2015 Robin Appelman <icewind@owncloud.com> + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. + */ + +namespace Test\Traits; + +/** + * Allow creating users in a temporary backend + */ +trait UserTrait { + /** + * @var \OC_User_Dummy|\OCP\UserInterface + */ + protected $userBackend; + + protected function createUser($name, $password) { + $this->userBackend->createUser($name, $password); + } + + protected function setUpUserTrait() { + $this->userBackend = new \OC_User_Dummy(); + \OC::$server->getUserManager()->registerBackend($this->userBackend); + } + + protected function tearDownUserTrait() { + \OC::$server->getUserManager()->removeBackend($this->userBackend); + } +} diff --git a/tests/lib/util.php b/tests/lib/util.php index e52a9fcc618..b9b8062653e 100644 --- a/tests/lib/util.php +++ b/tests/lib/util.php @@ -91,7 +91,7 @@ class Test_Util extends \Test\TestCase { function testCallRegister() { $result = strlen(OC_Util::callRegister()); - $this->assertEquals(30, $result); + $this->assertEquals(221, $result); } function testSanitizeHTML() { |