adding a first unit test to EGroupware, plus a test runner running all test-classes in either:

$app/src/.*/test/$classTest.php or $app/test/class.$classTest.inc.php
adding test runner doc/test-cli.php to Travis
This commit is contained in:
Ralf Becker 2016-07-11 21:38:36 +02:00
parent d96ad49bc8
commit a96ebb7513
8 changed files with 1488 additions and 10 deletions

View File

@ -22,6 +22,8 @@ matrix:
sudo: required sudo: required
dist: trusty dist: trusty
# this myy fix hhvm builds according to https://docs.travis-ci.com/user/languages/php#HHVM-versions-on-Trusty
group: edge
before_script: before_script:
# - mysql -e 'create database egroupware' # - mysql -e 'create database egroupware'
@ -32,9 +34,8 @@ before_script:
- mr --trust-all --stats up - mr --trust-all --stats up
script: script:
#- find . -name "*.php" | xargs -n1 php -l
./doc/php_syntax_check.sh ./doc/php_syntax_check.sh
#- ./vendor/bin/sabre-cs-fixer fix . --dry-run --diff ./doc/test-cli.php
cache: cache:
directories: directories:

View File

@ -18,13 +18,13 @@ use EGroupware\Api;
/** /**
* Mail account credentials are stored in egw_ea_credentials for given * Mail account credentials are stored in egw_ea_credentials for given
* acocunt-id, users and types (imap, smtp and optional admin connection). * account_id, users and types (imap, smtp and optional admin connection).
* *
* Passwords in credentials are encrypted with either user password from session * Passwords in credentials are encrypted with either user password from session
* or the database password. * or the database password.
* *
* If OpenSSL extension is available it is used to store credentials with AES-128-CBC, * If OpenSSL extension is available it is used to store credentials with AES-128-CBC,
* with key generated via hash_pbkdf2 sha256 hash and 12 byte binary salt (=16 char base64). * with key generated via hash_pbkdf2 sha256 hash and 16 byte binary salt (=24 char base64).
* OpenSSL can be also used to read old MCrypt credentials (OpenSSL 'des-ede3'). * OpenSSL can be also used to read old MCrypt credentials (OpenSSL 'des-ede3').
* *
* If only MCrypt is available (or EGroupware versions 14.x) credentials are are stored * If only MCrypt is available (or EGroupware versions 14.x) credentials are are stored
@ -404,9 +404,10 @@ class Credentials
* @param int $account_id user-account password is for * @param int $account_id user-account password is for
* @param int &$pw_enc on return encryption used * @param int &$pw_enc on return encryption used
* @param string $key =null key/password to use, default password according to account_id * @param string $key =null key/password to use, default password according to account_id
* @param string $salt =null (binary) salt to use, default generate new random salt
* @return string encrypted password * @return string encrypted password
*/ */
protected static function encrypt_openssl_aes($password, $account_id, &$pw_enc, $key=null) protected static function encrypt_openssl_aes($password, $account_id, &$pw_enc, $key=null, $salt=null)
{ {
if (empty($key)) if (empty($key))
{ {
@ -423,7 +424,6 @@ class Credentials
} }
} }
// using a pbkdf2 password derivation with a (stored) salt // using a pbkdf2 password derivation with a (stored) salt
$salt = null;
$aes_key = self::aes_key($key, $salt); $aes_key = self::aes_key($key, $salt);
return base64_encode($salt).base64_encode(openssl_encrypt($password, self::AES_METHOD, $aes_key, OPENSSL_RAW_DATA, $salt)); return base64_encode($salt).base64_encode(openssl_encrypt($password, self::AES_METHOD, $aes_key, OPENSSL_RAW_DATA, $salt));

View File

@ -0,0 +1,105 @@
<?php
/**
* EGroupware Api: Mail account credentials tests
*
* @link http://www.stylite.de
* @package api
* @subpackage mail
* @author Ralf Becker <rb-AT-stylite.de>
* @copyright (c) 2016 by Ralf Becker <rb-AT-stylite.de>
* @author Stylite AG <info@stylite.de>
* @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
* @version $Id$
*/
namespace EGroupware\Api\Mail;
require_once realpath(__DIR__.'/../../loader/common.php'); // autoloader & check_load_extension
use EGroupware\Api;
use PHPUnit\Framework\TestCase;
use ReflectionClass;
use EGroupware\Api\Mail\Credentials;
/**
* Mail account credentials tests
*
* Only testing en&decryption of mail passwords so far.
* Further tests would need database.
*/
class CredentialsTest extends TestCase
{
/**
* Test new 16.1 AES password encryption with OpenSSL
*/
public function testAes()
{
$mail_password = 'RälfÜber12345678sdddfd';
$account_id = $GLOBALS['egw_info']['user']['account_id'] = 1;
Api\Cache::setSession('phpgwapi', 'password', base64_encode('HMqUHxzMBjjvXppV'));
// test encryption with fixed salt
$pw_encrypted = 'IaaBeu6LiIa+iFBnHYroXA==4lp30Z4B20OdUYnFrxM3lo4b+bsf5wQITdyM1eMP6PM=';
$pw_enc = null;
$this->assertEquals($pw_encrypted, self::callProtectedMethod('encrypt_openssl_aes', __NAMESPACE__.'\\Credentials',
array($mail_password, $account_id, &$pw_enc, null, base64_decode(substr($pw_encrypted, 0, Credentials::SALT_LEN64)))),
'AES encrypt with fixed salt');
// test encryption&descryption with random salt
$pw_encrypted_rs = self::callProtectedMethod('encrypt', __NAMESPACE__.'\\Credentials',
array($mail_password, $account_id, &$pw_enc));
$row = array(
'account_id' => $account_id,
'cred_password' => $pw_encrypted_rs,
'cred_pw_enc' => $pw_enc,
);
$this->assertEquals($mail_password, self::callProtectedMethod('decrypt', __NAMESPACE__.'\\Credentials',
array($row)), 'AES decrypt with random salt');
}
/**
* Test old 14.x tripledes password encryption with mcrypt (if available) and openssl
*/
public function testTripledes()
{
$mail_password = 'RälfÜber12345678sdddfd';
$account_id = $GLOBALS['egw_info']['user']['account_id'] = 1;
Api\Cache::setSession('phpgwapi', 'password', base64_encode('HMqUHxzMBjjvXppV'));
$pw_encrypted = 'Y7QwLIqS6MP61hS8/e4i0wCdtpQP6kZ2';
// if mycrypt is available check encrypting too
if (check_load_extension('mcrypt'))
{
$pw_enc = null;
$this->assertEquals($pw_encrypted, self::callProtectedMethod('encrypt_mcrypt_3des', __NAMESPACE__.'\\Credentials',
array($mail_password, $account_id, &$pw_enc)), 'tripledes encryption with mcrypt');
}
else
{
$pw_enc = Credentials::USER;
}
// otherwise only check decrypting with openssl
$row = array(
'account_id' => $account_id,
'cred_password' => $pw_encrypted,
'cred_pw_enc' => $pw_enc,
);
$this->assertEquals($mail_password, self::callProtectedMethod('decrypt', __NAMESPACE__.'\\Credentials',
array($row)), 'tripledes decryption with openssl');
if (check_load_extension('mcrypt'))
{
$this->assertEquals($mail_password, self::callProtectedMethod('decrypt_mcrypt_3des', __NAMESPACE__.'\\Credentials',
array($row)), 'tripledes decryption with mcrypt');
}
}
protected static function callProtectedMethod($name, $classname, $params)
{
$class = new ReflectionClass($classname);
$method = $class->getMethod($name);
$method->setAccessible(true);
$obj = new $classname();
return $method->invokeArgs($obj, $params);
}
}

View File

@ -20,6 +20,7 @@ if (!defined('EGW_SERVER_ROOT'))
define('EGW_SERVER_ROOT', dirname(dirname(__DIR__))); define('EGW_SERVER_ROOT', dirname(dirname(__DIR__)));
define('EGW_INCLUDE_ROOT', EGW_SERVER_ROOT); define('EGW_INCLUDE_ROOT', EGW_SERVER_ROOT);
define('EGW_API_INC', __DIR__); define('EGW_API_INC', __DIR__);
error_reporting(E_ALL & ~E_NOTICE & ~E_STRICT);
} }
/** /**

View File

@ -33,5 +33,8 @@
"bower-asset/jquery-ui":"=1.11.2", "bower-asset/jquery-ui":"=1.11.2",
"npm-asset/as-jqplot" : "1.0.*", "npm-asset/as-jqplot" : "1.0.*",
"npm-asset/gridster":"0.5.*" "npm-asset/gridster":"0.5.*"
},
"require-dev": {
"phpunit/phpunit": "5.4.*"
} }
} }

1303
composer.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -55,7 +55,7 @@ $config = array(
'gpg' => trim(`which gpg`), 'gpg' => trim(`which gpg`),
'editor' => trim(`which vi`), 'editor' => trim(`which vi`),
'rsync' => trim(`which rsync`).' --progress -e ssh --exclude "*-stylite-*" --exclude "*-esyncpro-*"', 'rsync' => trim(`which rsync`).' --progress -e ssh --exclude "*-stylite-*" --exclude "*-esyncpro-*"',
'composer' => ($composer=trim(`which composer.phar`)) ? $composer.' install --ignore-platform-reqs' : '', 'composer' => ($composer=trim(`which composer.phar`)) ? $composer.' install --ignore-platform-reqs --no-dev' : '',
'after-checkout' => 'rm -rf */source */templates/*/source', 'after-checkout' => 'rm -rf */source */templates/*/source',
'packager' => 'build@stylite.de', 'packager' => 'build@stylite.de',
'obs' => '/home/stylite/obs/stylite-epl-trunk', 'obs' => '/home/stylite/obs/stylite-epl-trunk',

71
doc/test-cli.php Executable file
View File

@ -0,0 +1,71 @@
#!/usr/bin/env php
<?php
/**
* EGroupware Test Runner
*
* @author Ralf Becker <RalfBecker@outdoor-training.de>
* @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
*/
if (php_sapi_name() !== 'cli') // security precaution: forbit calling as web-page
{
die('<h1>test-cli.php must NOT be called as web-page --> exiting !!!</h1>');
}
require_once './api/src/loader/common.php';
$_SERVER['argv'][] = '--verbose';
$_SERVER['argv'][] = 'EgroupwareTestRunner';
$_SERVER['argv'][] = __FILE__;
PHPUnit_TextUI_Command::main();
/**
* Run all AllTests.php files
*/
class EgroupwareTestRunner
{
public static function suite()
{
$suite = new PHPUnit_Framework_TestSuite('EGroupware Test Runner');
$basedir = dirname(__DIR__);
// Find all /test/*Test.php files/classes
foreach (new RecursiveIteratorIterator(new RecursiveDirectoryIterator($basedir)) as $file)
{
if ($file->isFile() && preg_match('|/test/[^/]+Test\.php$|', $path=$file->getPathname()))
{
// Include the test suite, as it is NOT autoloadable in test directory!
require_once($path);
$matches = null;
// tests of namespaced classes in $app/src/.*/test/$classTest.php
if (preg_match('|/([^/]+)/src/((.*)/)?test/[^/]+Test\.php$|', $path, $matches))
{
$class = 'EGroupware\\'.ucfirst($matches[1]);
if (!empty($matches[2]))
{
foreach(explode('/', $matches[3]) as $name)
{
$class .= '\\'.ucfirst($name);
}
}
$class .= '\\'.$file->getBasename('.php');
}
// non-namespaced class in $app/test/class.$classTest.inc.php
elseif (preg_match('|/test/class\.([^./]+)\.inc\.php$|', $path, $matches))
{
$class = $matches[1];
}
else
{
continue;
}
echo "$path: $class\n";
$suite->addTestSuite($class);
}
}
return $suite;
}
}