Merge pull request #10642 from owncloud/securityutils

Add some security utilities
This commit is contained in:
Lukas Reschke 2014-09-03 15:28:42 +02:00
commit 373d1c5e9f
20 changed files with 639 additions and 56 deletions

@ -1 +1 @@
Subproject commit 57245d2a64c99aab8a438f909988e7a4ffef5b23
Subproject commit 82d02dd48ad11312bd740c57720dc84b4d66fa8a

View File

@ -35,6 +35,9 @@ $CONFIG = array(
/* Define the salt used to hash the user passwords. All your user passwords are lost if you lose this string. */
"passwordsalt" => "",
/* Secret used by ownCloud for various purposes, e.g. to encrypt data. If you lose this string there will be data corruption. */
"secret" => "",
/* Force use of HTTPS connection (true = use HTTPS) */
"forcessl" => false,

View File

@ -153,7 +153,6 @@ class Controller {
'hasMSSQL' => $hasMSSQL,
'databases' => $databases,
'directory' => $datadir,
'secureRNG' => \OC_Util::secureRNGAvailable(),
'htaccessWorking' => $htaccessWorking,
'vulnerableToNullByte' => $vulnerableToNullByte,
'errors' => $errors,

View File

@ -27,13 +27,6 @@
<?php p($l->t('Please update your PHP installation to use %s securely.', $theme->getName() )); ?></p>
</fieldset>
<?php endif; ?>
<?php if(!$_['secureRNG']): ?>
<fieldset class="warning">
<legend><strong><?php p($l->t('Security Warning'));?></strong></legend>
<p><?php p($l->t('No secure random number generator is available, please enable the PHP OpenSSL extension.'));?><br/>
<?php p($l->t('Without a secure random number generator an attacker may be able to predict password reset tokens and take over your account.'));?></p>
</fieldset>
<?php endif; ?>
<?php if(!$_['htaccessWorking']): ?>
<fieldset class="warning">
<legend><strong><?php p($l->t('Security Warning'));?></strong></legend>

View File

@ -71,6 +71,7 @@ class Repair extends BasicEmitter {
return array(
new \OC\Repair\RepairMimeTypes(),
new \OC\Repair\RepairLegacyStorages(\OC::$server->getConfig(), \OC_DB::getConnection()),
new \OC\Repair\RepairConfig(),
);
}

View File

@ -0,0 +1,115 @@
<?php
/**
* Copyright (c) 2014 Lukas Reschke <lukas@owncloud.com>
* This file is licensed under the Affero General Public License version 3 or
* later.
* See the COPYING-README file.
*/
namespace OC\Security;
use Crypt_AES;
use Crypt_Hash;
use OCP\Security\ICrypto;
use OCP\Security\ISecureRandom;
use OCP\Security\StringUtils;
use OCP\IConfig;
/**
* Class Crypto provides a high-level encryption layer using AES-CBC. If no key has been provided
* it will use the secret defined in config.php as key. Additionally the message will be HMAC'd.
*
* Usage:
* $encryptWithDefaultPassword = \OC::$server->getCrypto()->encrypt('EncryptedText');
* $encryptWithCustompassword = \OC::$server->getCrypto()->encrypt('EncryptedText', 'password');
*
* @package OC\Security
*/
class Crypto implements ICrypto {
/** @var Crypt_AES $cipher */
private $cipher;
/** @var int */
private $ivLength = 16;
/** @var IConfig */
private $config;
/** @var ISecureRandom */
private $random;
function __construct(IConfig $config, ISecureRandom $random) {
$this->cipher = new Crypt_AES();
$this->config = $config;
$this->random = $random;
}
/**
* @param string $message The message to authenticate
* @param string $password Password to use (defaults to `secret` in config.php)
* @return string Calculated HMAC
*/
public function calculateHMAC($message, $password = '') {
if($password === '') {
$password = $this->config->getSystemValue('secret');
}
// Append an "a" behind the password and hash it to prevent reusing the same password as for encryption
$password = hash('sha512', $password . 'a');
$hash = new Crypt_Hash('sha512');
$hash->setKey($password);
return $hash->hash($message);
}
/**
* Encrypts a value and adds an HMAC (Encrypt-Then-MAC)
* @param string $plaintext
* @param string $password Password to encrypt, if not specified the secret from config.php will be taken
* @return string Authenticated ciphertext
*/
public function encrypt($plaintext, $password = '') {
if($password === '') {
$password = $this->config->getSystemValue('secret');
}
$this->cipher->setPassword($password);
$iv = $this->random->getLowStrengthGenerator()->generate($this->ivLength);
$this->cipher->setIV($iv);
$ciphertext = bin2hex($this->cipher->encrypt($plaintext));
$hmac = bin2hex($this->calculateHMAC($ciphertext.$iv, $password));
return $ciphertext.'|'.$iv.'|'.$hmac;
}
/**
* Decrypts a value and verifies the HMAC (Encrypt-Then-Mac)
* @param string $authenticatedCiphertext
* @param string $password Password to encrypt, if not specified the secret from config.php will be taken
* @return string plaintext
* @throws \Exception If the HMAC does not match
*/
public function decrypt($authenticatedCiphertext, $password = '') {
if($password === '') {
$password = $this->config->getSystemValue('secret');
}
$this->cipher->setPassword($password);
$parts = explode('|', $authenticatedCiphertext);
if(sizeof($parts) !== 3) {
throw new \Exception('Authenticated ciphertext could not be decoded.');
}
$ciphertext = hex2bin($parts[0]);
$iv = $parts[1];
$hmac = hex2bin($parts[2]);
$this->cipher->setIV($iv);
if(!StringUtils::equals($this->calculateHMAC($parts[0].$parts[1], $password), $hmac)) {
throw new \Exception('HMAC does not match.');
}
return $this->cipher->decrypt($ciphertext);
}
}

View File

@ -0,0 +1,79 @@
<?php
/**
* Copyright (c) 2014 Lukas Reschke <lukas@owncloud.com>
* This file is licensed under the Affero General Public License version 3 or
* later.
* See the COPYING-README file.
*/
namespace OC\Security;
use RandomLib;
use Sabre\DAV\Exception;
use OCP\Security\ISecureRandom;
/**
* Class SecureRandom provides a layer around RandomLib to generate
* secure random strings.
*
* Usage:
* \OC::$server->getSecureRandom()->getMediumStrengthGenerator()->generate(10);
*
* @package OC\Security
*/
class SecureRandom implements ISecureRandom {
/** @var \RandomLib\Factory */
var $factory;
/** @var \RandomLib\Generator */
var $generator;
function __construct() {
$this->factory = new RandomLib\Factory;
}
/**
* Convenience method to get a low strength random number generator.
*
* Low Strength should be used anywhere that random strings are needed
* in a non-cryptographical setting. They are not strong enough to be
* used as keys or salts. They are however useful for one-time use tokens.
*
* @return $this
*/
public function getLowStrengthGenerator() {
$this->generator = $this->factory->getLowStrengthGenerator();
return $this;
}
/**
* Convenience method to get a medium strength random number generator.
*
* Medium Strength should be used for most needs of a cryptographic nature.
* They are strong enough to be used as keys and salts. However, they do
* take some time and resources to generate, so they should not be over-used
*
* @return $this
*/
public function getMediumStrengthGenerator() {
$this->generator = $this->factory->getMediumStrengthGenerator();
return $this;
}
/**
* Generate a random string of specified length.
* @param string $length The length of the generated string
* @param string $characters An optional list of characters to use if no characterlist is
* specified 0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ./
* is used.
* @return string
* @throws \Exception If the generator is not initialized.
*/
public function generate($length, $characters = '') {
if(is_null($this->generator)) {
throw new \Exception('Generator is not initialized.');
}
return $this->generator->generateString($length, $characters);
}
}

View File

@ -0,0 +1,42 @@
<?php
/**
* Copyright (c) 2014 Lukas Reschke <lukas@owncloud.com>
* This file is licensed under the Affero General Public License version 3 or
* later.
* See the COPYING-README file.
*/
namespace OC\Security;
class StringUtils {
/**
* Compares whether two strings are equal. To prevent guessing of the string
* length this is done by comparing two hashes against each other and afterwards
* a comparison of the real string to prevent against the unlikely chance of
* collisions.
*
* Be aware that this function may leak whether the string to compare have a different
* length.
*
* @param string $expected The expected value
* @param string $input The input to compare against
* @return bool True if the two strings are equal, otherwise false.
*/
public static function equals($expected, $input) {
if(function_exists('hash_equals')) {
return hash_equals($expected, $input);
}
$randomString = \OC::$server->getSecureRandom()->getLowStrengthGenerator()->generate(10);
if(hash('sha512', $expected.$randomString) === hash('sha512', $input.$randomString)) {
if($expected === $input) {
return true;
}
}
return false;
}
}

View File

@ -10,6 +10,8 @@ use OC\Security\CertificateManager;
use OC\DB\ConnectionWrapper;
use OC\Files\Node\Root;
use OC\Files\View;
use OC\Security\Crypto;
use OC\Security\SecureRandom;
use OCP\IServerContainer;
use OCP\ISession;
@ -201,6 +203,12 @@ class Server extends SimpleContainer implements IServerContainer {
$this->registerService('Search', function ($c) {
return new Search();
});
$this->registerService('SecureRandom', function($c) {
return new SecureRandom();
});
$this->registerService('Crypto', function($c) {
return new Crypto(\OC::$server->getConfig(), \OC::$server->getSecureRandom());
});
$this->registerService('Db', function ($c) {
return new Db();
});
@ -467,6 +475,24 @@ class Server extends SimpleContainer implements IServerContainer {
return $this->query('Search');
}
/**
* Returns a SecureRandom instance
*
* @return \OCP\Security\ISecureRandom
*/
function getSecureRandom() {
return $this->query('SecureRandom');
}
/**
* Returns a Crypto instance
*
* @return \OCP\Security\ICrypto
*/
function getCrypto() {
return $this->query('Crypto');
}
/**
* Returns an instance of the db facade
*

View File

@ -67,14 +67,19 @@ class OC_Setup {
}
//generate a random salt that is used to salt the local user passwords
$salt = OC_Util::generateRandomBytes(30);
OC_Config::setValue('passwordsalt', $salt);
$salt = \OC::$server->getSecureRandom()->getLowStrengthGenerator()->generate(30);
\OC::$server->getConfig()->setSystemValue('passwordsalt', $salt);
// generate a secret
$secret = \OC::$server->getSecureRandom()->getMediumStrengthGenerator()->generate(48);
\OC::$server->getConfig()->setSystemValue('secret', $secret);
//write the config file
OC_Config::setValue('trusted_domains', $trustedDomains);
OC_Config::setValue('datadirectory', $datadir);
OC_Config::setValue('dbtype', $dbtype);
OC_Config::setValue('version', implode('.', OC_Util::getVersion()));
\OC::$server->getConfig()->setSystemValue('trusted_domains', $trustedDomains);
\OC::$server->getConfig()->setSystemValue('datadirectory', $datadir);
\OC::$server->getConfig()->setSystemValue('dbtype', $dbtype);
\OC::$server->getConfig()->setSystemValue('version', implode('.', OC_Util::getVersion()));
try {
$dbSetup->initialize($options);
$dbSetup->setupDatabase($username);

View File

@ -905,7 +905,7 @@ class OC_Util {
$id = OC_Config::getValue('instanceid', null);
if (is_null($id)) {
// We need to guarantee at least one letter in instanceid so it can be used as the session_name
$id = 'oc' . self::generateRandomBytes(10);
$id = 'oc' . \OC::$server->getSecureRandom()->getLowStrengthGenerator()->generate(10);
OC_Config::$object->setValue('instanceid', $id);
}
return $id;
@ -1208,54 +1208,20 @@ class OC_Util {
*
* @param int $length of the random string
* @return string
* @throws Exception when no secure RNG source is available
* Please also update secureRNGAvailable if you change something here
* @deprecated Use \OC::$server->getSecureRandom()->getMediumStrengthGenerator()->generate($length); instead
*/
public static function generateRandomBytes($length = 30) {
// Try to use openssl_random_pseudo_bytes
if (function_exists('openssl_random_pseudo_bytes')) {
$pseudoByte = bin2hex(openssl_random_pseudo_bytes($length, $strong));
if ($strong == true) {
return substr($pseudoByte, 0, $length); // Truncate it to match the length
}
}
// Try to use /dev/urandom
if (!self::runningOnWindows()) {
$fp = @file_get_contents('/dev/urandom', false, null, 0, $length);
if ($fp !== false) {
$string = substr(bin2hex($fp), 0, $length);
return $string;
}
}
// No random numbers are better then bad random numbers
throw new \Exception('No secure random number generator available, please install the php-openssl extension');
return \OC::$server->getSecureRandom()->getMediumStrengthGenerator()->generate($length);
}
/**
* Checks if a secure random number generator is available
*
* @return bool
* @return true
* @deprecated Function will be removed in the future and does only return true.
*/
public static function secureRNGAvailable() {
// Check openssl_random_pseudo_bytes
if (function_exists('openssl_random_pseudo_bytes')) {
openssl_random_pseudo_bytes(1, $strong);
if ($strong == true) {
return true;
}
}
// Check /dev/urandom
if (!self::runningOnWindows()) {
$fp = @file_get_contents('/dev/urandom', false, null, 0, 1);
if ($fp !== false) {
return true;
}
}
return false;
return true;
}
/**

View File

@ -0,0 +1,46 @@
<?php
/**
* Copyright (c) 2014 Lukas Reschke <lukas@owncloud.com>
* This file is licensed under the Affero General Public License version 3 or
* later.
* See the COPYING-README file.
*/
namespace OCP\Security;
/**
* Class Crypto provides a high-level encryption layer using AES-CBC. If no key has been provided
* it will use the secret defined in config.php as key. Additionally the message will be HMAC'd.
*
* Usage:
* $encryptWithDefaultPassword = \OC::$server->getCrypto()->encrypt('EncryptedText');
* $encryptWithCustomPassword = \OC::$server->getCrypto()->encrypt('EncryptedText', 'password');
*
* @package OCP\Security
*/
interface ICrypto {
/**
* @param string $message The message to authenticate
* @param string $password Password to use (defaults to `secret` in config.php)
* @return string Calculated HMAC
*/
public function calculateHMAC($message, $password = '');
/**
* Encrypts a value and adds an HMAC (Encrypt-Then-MAC)
* @param string $plaintext
* @param string $password Password to encrypt, if not specified the secret from config.php will be taken
* @return string Authenticated ciphertext
*/
public function encrypt($plaintext, $password = '');
/**
* Decrypts a value and verifies the HMAC (Encrypt-Then-Mac)
* @param string $authenticatedCiphertext
* @param string $password Password to encrypt, if not specified the secret from config.php will be taken
* @return string plaintext
* @throws \Exception If the HMAC does not match
*/
public function decrypt($authenticatedCiphertext, $password = '');
}

View File

@ -0,0 +1,61 @@
<?php
/**
* Copyright (c) 2014 Lukas Reschke <lukas@owncloud.com>
* This file is licensed under the Affero General Public License version 3 or
* later.
* See the COPYING-README file.
*/
namespace OCP\Security;
/**
* Class SecureRandom provides a layer around RandomLib to generate
* secure random numbers.
*
* Usage:
* $rng = new \OC\Security\SecureRandom();
* $randomString = $rng->getMediumStrengthGenerator()->generateString(30);
*
* @package OCP\Security
*/
interface ISecureRandom {
/**
* Flags for characters that can be used for <code>generate($length, $characters)</code>
*/
const CHAR_UPPER = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
const CHAR_LOWER = 'abcdefghijklmnopqrstuvwxyz';
const CHAR_DIGITS = '0123456789';
const CHAR_SYMBOLS = '!\"#$%&\\\'()* +,-./:;<=>?@[\]^_`{|}~';
/**
* Convenience method to get a low strength random number generator.
*
* Low Strength should be used anywhere that random strings are needed
* in a non-cryptographical setting. They are not strong enough to be
* used as keys or salts. They are however useful for one-time use tokens.
*
* @return $this
*/
public function getLowStrengthGenerator();
/**
* Convenience method to get a medium strength random number generator.
*
* Medium Strength should be used for most needs of a cryptographic nature.
* They are strong enough to be used as keys and salts. However, they do
* take some time and resources to generate, so they should not be over-used
*
* @return $this
*/
public function getMediumStrengthGenerator();
/**
* Generate a random string of specified length.
* @param string $length The length of the generated string
* @param string $characters An optional list of characters to use
* @return string
* @throws \Exception
*/
public function generate($length, $characters = '');
}

View File

@ -0,0 +1,25 @@
<?php
/**
* Copyright (c) 2014 Lukas Reschke <lukas@owncloud.com>
* This file is licensed under the Affero General Public License version 3 or
* later.
* See the COPYING-README file.
*/
namespace OCP\Security;
class StringUtils {
/**
* Compares whether two strings are equal. To prevent guessing of the string
* length this is done by comparing two hashes against each other and afterwards
* a comparison of the real string to prevent against the unlikely chance of
* collisions.
* @param string $expected The expected value
* @param string $input The input to compare against
* @return bool True if the two strings are equal, otherwise false.
*/
public static function equals($expected, $input) {
return \OC\Security\StringUtils::equals($expected, $input);
}
}

View File

@ -505,6 +505,7 @@ class Util {
* Generates a cryptographic secure pseudo-random string
* @param int $length of the random string
* @return string
* @deprecated Use \OC::$server->getSecureRandom()->getMediumStrengthGenerator()->generate($length); instead
*/
public static function generateRandomBytes($length = 30) {
return \OC_Util::generateRandomBytes($length);

View File

@ -0,0 +1,37 @@
<?php
/**
* Copyright (c) 2014 Lukas Reschke <lukas@owncloud.com>
* This file is licensed under the Affero General Public License version 3 or
* later.
* See the COPYING-README file.
*/
namespace OC\Repair;
use OC\Hooks\BasicEmitter;
use OC\RepairStep;
use Sabre\DAV\Exception;
class RepairConfig extends BasicEmitter implements RepairStep {
public function getName() {
return 'Repair config';
}
/**
* Updates the configuration after running an update
*/
public function run() {
$this->addSecret();
}
/**
* Adds a secret to config.php
*/
private function addSecret() {
if(\OC::$server->getConfig()->getSystemValue('secret', null) === null) {
$secret = \OC::$server->getSecureRandom()->getMediumStrengthGenerator()->generate(48);
\OC::$server->getConfig()->setSystemValue('secret', $secret);
}
}
}

View File

@ -0,0 +1,70 @@
<?php
/**
* Copyright (c) 2014 Lukas Reschke <lukas@owncloud.com>
* This file is licensed under the Affero General Public License version 3 or
* later.
* See the COPYING-README file.
*/
use \OC\Security\Crypto;
class CryptoTest extends \PHPUnit_Framework_TestCase {
public function defaultEncryptionProvider()
{
return array(
array('Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt.'),
array(''),
array('我看这本书。 我看這本書')
);
}
/** @var Crypto */
protected $crypto;
protected function setUp() {
$this->crypto = new Crypto(\OC::$server->getConfig(), \OC::$server->getSecureRandom());
}
/**
* @dataProvider defaultEncryptionProvider
*/
function testDefaultEncrypt($stringToEncrypt) {
$ciphertext = $this->crypto->encrypt($stringToEncrypt);
$this->assertEquals($stringToEncrypt, $this->crypto->decrypt($ciphertext));
}
/**
* @expectedException \Exception
* @expectedExceptionMessage HMAC does not match.
*/
function testWrongPassword() {
$stringToEncrypt = 'Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt.';
$ciphertext = $this->crypto->encrypt($stringToEncrypt);
$this->crypto->decrypt($ciphertext, 'A wrong password!');
}
function testLaterDecryption() {
$stringToEncrypt = 'Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt.';
$encryptedString = '44a35023cca2e7a6125e06c29fc4b2ad9d8a33d0873a8b45b0de4ef9284f260c6c46bf25dc62120644c59b8bafe4281ddc47a70c35ae6c29ef7a63d79eefacc297e60b13042ac582733598d0a6b4de37311556bb5c480fd2633de4e6ebafa868c2d1e2d80a5d24f9660360dba4d6e0c8|lhrFgK0zd9U160Wo|a75e57ab701f9124e1113543fd1dc596f21e20d456a0d1e813d5a8aaec9adcb11213788e96598b67fe9486a9f0b99642c18296d0175db44b1ae426e4e91080ee';
$this->assertEquals($stringToEncrypt, $this->crypto->decrypt($encryptedString, 'ThisIsAVeryS3cur3P4ssw0rd'));
}
/**
* @expectedException \Exception
* @expectedExceptionMessage HMAC does not match.
*/
function testWrongIV() {
$encryptedString = '560f5436ba864b9f12f7f7ca6d41c327554a6f2c0a160a03316b202af07c65163274993f3a46e7547c07ba89304f00594a2f3bd99f83859097c58049c39d0d4ade10e0de914ff0604961e7c849d0271ed6c0b23f984ba16e7d033e3305fb0910e7b6a2a65c988d17dbee71d8f953684d|d2kdFUspVjC0o0sr|1a5feacf87eaa6869a6abdfba9a296e7bbad45b6ad89f7dce67cdc98e2da5dc4379cc672cc655e52bbf19599bf59482fbea13a73937697fa656bf10f3fc4f1aa';
$this->crypto->decrypt($encryptedString, 'ThisIsAVeryS3cur3P4ssw0rd');
}
/**
* @expectedException \Exception
* @expectedExceptionMessage Authenticated ciphertext could not be decoded.
*/
function testWrongParameters() {
$encryptedString = '1|2';
$this->crypto->decrypt($encryptedString, 'ThisIsAVeryS3cur3P4ssw0rd');
}
}

View File

@ -0,0 +1,76 @@
<?php
/**
* Copyright (c) 2014 Lukas Reschke <lukas@owncloud.com>
* This file is licensed under the Affero General Public License version 3 or
* later.
* See the COPYING-README file.
*/
use \OC\Security\SecureRandom;
class SecureRandomTest extends \PHPUnit_Framework_TestCase {
public function stringGenerationProvider() {
return array(
array(0, 0),
array(1, 1),
array(128, 128),
array(256, 256),
array(1024, 1024),
array(2048, 2048),
array(64000, 64000),
);
}
public static function charCombinations() {
return array(
array('CHAR_LOWER', '[a-z]'),
array('CHAR_UPPER', '[A-Z]'),
array('CHAR_DIGITS', '[0-9]'),
);
}
/** @var SecureRandom */
protected $rng;
protected function setUp() {
$this->rng = new \OC\Security\SecureRandom();
}
/**
* @dataProvider stringGenerationProvider
*/
function testGetLowStrengthGeneratorLength($length, $expectedLength) {
$generator = $this->rng->getLowStrengthGenerator();
$this->assertEquals($expectedLength, strlen($generator->generate($length)));
}
/**
* @dataProvider stringGenerationProvider
*/
function testMediumLowStrengthGeneratorLength($length, $expectedLength) {
$generator = $this->rng->getMediumStrengthGenerator();
$this->assertEquals($expectedLength, strlen($generator->generate($length)));
}
/**
* @expectedException \Exception
* @expectedExceptionMessage Generator is not initialized
*/
function testUninitializedGenerate() {
$this->rng->generate(30);
}
/**
* @dataProvider charCombinations
*/
public function testScheme($charName, $chars) {
$generator = $this->rng->getMediumStrengthGenerator();
$scheme = constant('OCP\Security\ISecureRandom::' . $charName);
$randomString = $generator->generate(100, $scheme);
$matchesRegex = preg_match('/^'.$chars.'+$/', $randomString);
$this->assertSame(1, $matchesRegex);
}
}

View File

@ -0,0 +1,38 @@
<?php
/**
* Copyright (c) 2014 Lukas Reschke <lukas@owncloud.com>
* This file is licensed under the Affero General Public License version 3 or
* later.
* See the COPYING-README file.
*/
use \OC\Security\StringUtils;
class StringUtilsTest extends \PHPUnit_Framework_TestCase {
public function dataProvider()
{
return array(
array('Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt.', 'Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt.'),
array('', ''),
array('我看这本书。 我看這本書', '我看这本书。 我看這本書'),
array('GpKY9fSnWNJbES99zVGvA', 'GpKY9fSnWNJbES99zVGvA')
);
}
/**
* @dataProvider dataProvider
*/
function testWrongEquals($string) {
$this->assertFalse(StringUtils::equals($string, 'A Completely Wrong String'));
$this->assertFalse(StringUtils::equals($string, null));
}
/**
* @dataProvider dataProvider
*/
function testTrueEquals($string, $expected) {
$this->assertTrue(StringUtils::equals($string, $expected));
}
}

View File

@ -3,7 +3,7 @@
// We only can count up. The 4. digit is only for the internal patchlevel to trigger DB upgrades
// between betas, final and RCs. This is _not_ the public version number. Reset minor/patchlevel
// when updating major/minor version number.
$OC_Version=array(7, 8, 0, 0);
$OC_Version=array(7, 8, 1, 0);
// The human readable string
$OC_VersionString='8.0 pre alpha';