Twig, Tests, AES,

This commit is contained in:
2021-12-08 16:53:43 +01:00
parent 25d47f7528
commit 918244125c
74 changed files with 5350 additions and 1515 deletions

143
test/AesStream.test.php Normal file
View File

@@ -0,0 +1,143 @@
<?php
use Objects\AesStream;
class AesStreamTest extends PHPUnit\Framework\TestCase {
static string $TEMP_FILE;
public static function setUpBeforeClass(): void {
AesStreamTest::$TEMP_FILE = tempnam(sys_get_temp_dir(), 'aesTest');
}
public static function tearDownAfterClass(): void {
unlink(AesStreamTest::$TEMP_FILE);
}
public function testConstructorInvalidKey1() {
$this->expectExceptionMessage("Invalid Key Size");
$this->expectException(\Exception::class);
new AesStream("", "");
}
public function testConstructorInvalidKey2() {
$this->expectExceptionMessage("Invalid Key Size");
$this->expectException(\Exception::class);
new AesStream(str_repeat("A",15), "");
}
public function testConstructorInvalidKey3() {
$this->expectExceptionMessage("Invalid Key Size");
$this->expectException(\Exception::class);
new AesStream(str_repeat("A",33), "");
}
public function testConstructorInvalidIV1() {
$this->expectExceptionMessage("Invalid IV Size");
$this->expectException(\Exception::class);
new AesStream(str_repeat("A",32), str_repeat("B", 17));
}
public function testConstructorValid() {
$key = str_repeat("A",32);
$iv = str_repeat("B", 16);
$aesStream = new AesStream($key, $iv);
$this->assertInstanceOf(AesStream::class, $aesStream);
$this->assertEquals($key, $aesStream->getKey());
$this->assertEquals($iv, $aesStream->getIV());
$this->assertEquals("aes-256-ctr", $aesStream->getCipherMode());
}
private function getOutput(string $chunk, string &$data) {
$data .= $chunk;
}
public function testEncrypt() {
$key = str_repeat("A", 32);
$iv = str_repeat("B", 16);
$aesStream = new AesStream($key, $iv);
$data = [
"43" => "8c", # small block test 1 (1 byte)
"abcd" => "6424", # small block test 2 (2 byte)
"a37c599429cfdefde6546ad6d7082a" => "6c9539264abc8cae39308dbc86e768", # small block test 3 (15 byte)
"43b3504077482bd9bf8c3c08ad3c937f" => "8c5a30f2143b798a60e8db62fcd3d1f7", # one block (16 byte)
"9b241a3d7e9f03f6e66a8fa0cba3221008eda86f465e3fbfb0f3a4d3527cffb7"
=> "54cd7a8f1dec51a5390e68ca9a4c60986aaafadd42b6960a09deedfa7f2cf1c3" # two blocks (16 byte)
];
foreach ($data as $pt => $ct) {
$output = "";
file_put_contents(AesStreamTest::$TEMP_FILE, hex2bin($pt));
$aesStream->setInputFile(AesStreamTest::$TEMP_FILE);
$aesStream->setOutput(function($chunk) use (&$output) { $this->getOutput($chunk, $output); });
$aesStream->start();
$this->assertEquals($ct, bin2hex($output), $ct . " != " . bin2hex($output));
}
}
private function openssl(AesStream $aesStream) {
// check if openssl util produce the same output
$cmd = ["/usr/bin/openssl", $aesStream->getCipherMode(), "-K", bin2hex($aesStream->getKey()), "-iv", bin2hex($aesStream->getIV()), "-in", AesStreamTest::$TEMP_FILE];
$proc = proc_open($cmd, [1 => ["pipe", "w"]], $pipes);
$this->assertTrue(is_resource($proc));
$this->assertTrue(is_resource($pipes[1]));
$output = stream_get_contents($pipes[1]);
proc_close($proc);
return $output;
}
private function testEncryptDecrypt($key, $iv, $inputData) {
$aesStream = new AesStream($key, $iv);
$inputSize = strlen($inputData);
file_put_contents(AesStreamTest::$TEMP_FILE, $inputData);
$output = "";
$aesStream->setInputFile(AesStreamTest::$TEMP_FILE);
$aesStream->setOutput(function($chunk) use (&$output) { $this->getOutput($chunk, $output); });
$aesStream->start();
$this->assertEquals($inputSize, strlen($output));
$this->assertNotEquals($inputData, $output);
// check if openssl util produce the same output
$this->assertEquals($this->openssl($aesStream), $output);
file_put_contents(AesStreamTest::$TEMP_FILE, $output);
$output = "";
$aesStream->setInputFile(AesStreamTest::$TEMP_FILE);
$aesStream->setOutput(function($chunk) use (&$output) { $this->getOutput($chunk, $output); });
$aesStream->start();
$this->assertEquals($inputData, $output);
// check if openssl util produce the same output
$this->assertEquals($this->openssl($aesStream), $output);
}
public function testEncryptDecryptRandom() {
$chunkSize = 65536;
$key = random_bytes(32);
$iv = random_bytes(16);
$inputSize = 10 * $chunkSize;
$inputData = random_bytes($inputSize);
$this->testEncryptDecrypt($key, $iv, $inputData);
}
public function testEncryptDecryptLargeIV() {
$chunkSize = 65536;
$key = random_bytes(32);
$iv = hex2bin(str_repeat("FF", 16));
$inputSize = 10 * $chunkSize;
$inputData = random_bytes($inputSize);
$this->testEncryptDecrypt($key, $iv, $inputData);
}
public function testEncryptDecryptZeroIV() {
$chunkSize = 65536;
$key = random_bytes(32);
$iv = hex2bin(str_repeat("00", 16));
$inputSize = 10 * $chunkSize;
$inputData = random_bytes($inputSize);
$this->testEncryptDecrypt($key, $iv, $inputData);
}
}

108
test/Parameter.test.php Normal file
View File

@@ -0,0 +1,108 @@
<?php
use Api\Parameter\ArrayType;
use Api\Parameter\StringType;
use Api\Parameter\Parameter;
class ParameterTest extends \PHPUnit\Framework\TestCase {
public function testStringType() {
// test various string sizes
$unlimited = new StringType("test_unlimited");
$this->assertTrue($unlimited->parseParam(str_repeat("A", 1024)));
$empty = new StringType("test_empty", 0);
$this->assertTrue($empty->parseParam(""));
$this->assertTrue($empty->parseParam("A"));
$one = new StringType("test_one", 1);
$this->assertTrue($one->parseParam(""));
$this->assertTrue($one->parseParam("A"));
$this->assertFalse($one->parseParam("AB"));
$randomSize = rand(1, 64);
$random = new StringType("test_empty", $randomSize);
$data = str_repeat("A", $randomSize);
$this->assertTrue($random->parseParam(""));
$this->assertTrue($random->parseParam("A"));
$this->assertTrue($random->parseParam($data));
$this->assertEquals($data, $random->value);
// test data types
$this->assertFalse($random->parseParam(null));
$this->assertFalse($random->parseParam(1));
$this->assertFalse($random->parseParam(2.5));
$this->assertFalse($random->parseParam(true));
$this->assertFalse($random->parseParam(false));
$this->assertFalse($random->parseParam(["key" => 1]));
}
public function testArrayType() {
// int array type
$arrayType = new ArrayType("int_array", Parameter::TYPE_INT);
$this->assertTrue($arrayType->parseParam([1,2,3]));
$this->assertTrue($arrayType->parseParam([1]));
$this->assertTrue($arrayType->parseParam(["1"]));
$this->assertTrue($arrayType->parseParam([1.0]));
$this->assertTrue($arrayType->parseParam([]));
$this->assertTrue($arrayType->parseParam(["1.0"]));
$this->assertFalse($arrayType->parseParam([1.2]));
$this->assertFalse($arrayType->parseParam(["1.5"]));
$this->assertFalse($arrayType->parseParam([true]));
$this->assertFalse($arrayType->parseParam(1));
// optional single value
$arrayType = new ArrayType("int_array_single", Parameter::TYPE_INT, true);
$this->assertTrue($arrayType->parseParam(1));
// mixed values
$arrayType = new ArrayType("mixed_array", Parameter::TYPE_MIXED);
$this->assertTrue($arrayType->parseParam([1, 2.5, "test", false]));
}
public function testParseType() {
// int
$this->assertEquals(Parameter::TYPE_INT, Parameter::parseType(1));
$this->assertEquals(Parameter::TYPE_INT, Parameter::parseType(1.0));
$this->assertEquals(Parameter::TYPE_INT, Parameter::parseType("1"));
$this->assertEquals(Parameter::TYPE_INT, Parameter::parseType("1.0"));
// array
$this->assertEquals(Parameter::TYPE_ARRAY, Parameter::parseType([1, true]));
// float
$this->assertEquals(Parameter::TYPE_FLOAT, Parameter::parseType(1.5));
$this->assertEquals(Parameter::TYPE_FLOAT, Parameter::parseType(1.234e2));
$this->assertEquals(Parameter::TYPE_FLOAT, Parameter::parseType("1.75"));
// boolean
$this->assertEquals(Parameter::TYPE_BOOLEAN, Parameter::parseType(true));
$this->assertEquals(Parameter::TYPE_BOOLEAN, Parameter::parseType(false));
$this->assertEquals(Parameter::TYPE_BOOLEAN, Parameter::parseType("true"));
$this->assertEquals(Parameter::TYPE_BOOLEAN, Parameter::parseType("false"));
// date
$this->assertEquals(Parameter::TYPE_DATE, Parameter::parseType("2021-11-13"));
$this->assertEquals(Parameter::TYPE_STRING, Parameter::parseType("2021-13-11")); # invalid date
// time
$this->assertEquals(Parameter::TYPE_TIME, Parameter::parseType("10:11:12"));
$this->assertEquals(Parameter::TYPE_STRING, Parameter::parseType("25:11:12")); # invalid time
// datetime
$this->assertEquals(Parameter::TYPE_DATE_TIME, Parameter::parseType("2021-11-13 10:11:12"));
$this->assertEquals(Parameter::TYPE_STRING, Parameter::parseType("2021-13-13 10:11:12")); # invalid date
$this->assertEquals(Parameter::TYPE_STRING, Parameter::parseType("2021-13-11 10:61:12")); # invalid time
// email
$this->assertEquals(Parameter::TYPE_EMAIL, Parameter::parseType("a@b.com"));
$this->assertEquals(Parameter::TYPE_EMAIL, Parameter::parseType("test.123@example.com"));
$this->assertEquals(Parameter::TYPE_STRING, Parameter::parseType("@example.com")); # invalid email
$this->assertEquals(Parameter::TYPE_STRING, Parameter::parseType("test@")); # invalid email
// string, everything else
$this->assertEquals(Parameter::TYPE_STRING, Parameter::parseType("test"));
}
}

192
test/Request.test.php Normal file
View File

@@ -0,0 +1,192 @@
<?php
use Api\Request;
use Configuration\Configuration;
use Objects\User;
function __new_header_impl(string $line) {
if (preg_match("/^HTTP\/([0-9.]+) (\d+) (.*)$/", $line, $m)) {
RequestTest::$SENT_STATUS_CODE = intval($m[2]);
return;
}
$key = $line;
$value = "";
$index = strpos($key, ": ");
if ($index !== false) {
$key = substr($line, 0, $index);
$value = substr($line, $index + 2);
}
RequestTest::$SENT_HEADERS[$key] = $value;
}
function __new_http_response_code_impl(int $code) {
RequestTest::$SENT_STATUS_CODE = $code;
}
function __new_die_impl($content) {
RequestTest::$SENT_CONTENT = $content;
}
class RequestTest extends \PHPUnit\Framework\TestCase {
const FUNCTION_OVERRIDES = ["header", "http_response_code"];
static User $USER;
static User $USER_LOGGED_IN;
static ?string $SENT_CONTENT;
static array $SENT_HEADERS;
static ?int $SENT_STATUS_CODE;
public static function setUpBeforeClass(): void {
$config = new Configuration();
RequestTest::$USER = new User($config);
RequestTest::$USER_LOGGED_IN = new User($config);
if (!RequestTest::$USER->getSQL() || !RequestTest::$USER->getSQL()->isConnected()) {
throw new Exception("Could not establish database connection");
} else {
RequestTest::$USER->setLanguage(\Objects\Language::DEFAULT_LANGUAGE());
}
if (!function_exists("runkit7_function_rename") || !function_exists("runkit7_function_remove")) {
throw new Exception("Request Unit Test requires runkit7 extension");
}
if (ini_get("runkit.internal_override") !== "1") {
throw new Exception("Request Unit Test requires runkit7 with internal_override enabled to function properly");
}
foreach (self::FUNCTION_OVERRIDES as $functionName) {
runkit7_function_rename($functionName, "__orig_${functionName}_impl");
runkit7_function_rename("__new_${functionName}_impl", $functionName);
}
}
public static function tearDownAfterClass(): void {
RequestTest::$USER->getSQL()->close();
foreach (self::FUNCTION_OVERRIDES as $functionName) {
runkit7_function_remove($functionName);
runkit7_function_rename("__orig_${functionName}_impl", $functionName);
}
}
private function simulateRequest(Request $request, string $method, array $get = [], array $post = [], array $headers = []): bool {
if (!is_cli()) {
self::throwException(new \Exception("Cannot simulate request outside cli"));
}
$_SERVER = [];
$_SERVER["REQUEST_METHOD"] = $method;
self::$SENT_HEADERS = [];
self::$SENT_STATUS_CODE = null;
self::$SENT_CONTENT = null;
foreach ($headers as $key => $value) {
$key = "HTTP_" . preg_replace("/\s/", "_", strtoupper($key));
$_SERVER[$key] = $value;
}
$_GET = $get;
$_POST = $post;
return $request->execute();
}
public function testAllMethods() {
// all methods allowed
$allMethodsAllowed = new RequestAllMethods(RequestTest::$USER, true);
$this->assertTrue($this->simulateRequest($allMethodsAllowed, "GET"), $allMethodsAllowed->getLastError());
$this->assertTrue($this->simulateRequest($allMethodsAllowed, "POST"), $allMethodsAllowed->getLastError());
$this->assertFalse($this->simulateRequest($allMethodsAllowed, "PUT"), $allMethodsAllowed->getLastError());
$this->assertFalse($this->simulateRequest($allMethodsAllowed, "DELETE"), $allMethodsAllowed->getLastError());
$this->assertTrue($this->simulateRequest($allMethodsAllowed, "OPTIONS"), $allMethodsAllowed->getLastError());
$this->assertEquals(204, self::$SENT_STATUS_CODE);
$this->assertEquals(["Allow" => "OPTIONS, GET, POST"], self::$SENT_HEADERS);
}
public function testOnlyPost() {
// only post allowed
$onlyPostAllowed = new RequestOnlyPost(RequestTest::$USER, true);
$this->assertFalse($this->simulateRequest($onlyPostAllowed, "GET"));
$this->assertEquals("This method is not allowed", $onlyPostAllowed->getLastError(), $onlyPostAllowed->getLastError());
$this->assertEquals(405, self::$SENT_STATUS_CODE);
$this->assertTrue($this->simulateRequest($onlyPostAllowed, "POST"), $onlyPostAllowed->getLastError());
$this->assertTrue($this->simulateRequest($onlyPostAllowed, "OPTIONS"), $onlyPostAllowed->getLastError());
$this->assertEquals(204, self::$SENT_STATUS_CODE);
$this->assertEquals(["Allow" => "OPTIONS, POST"], self::$SENT_HEADERS);
}
public function testPrivate() {
// private method
$privateExternal = new RequestPrivate(RequestTest::$USER, true);
$this->assertFalse($this->simulateRequest($privateExternal, "GET"));
$this->assertEquals("This function is private.", $privateExternal->getLastError());
$this->assertEquals(403, self::$SENT_STATUS_CODE);
$privateInternal = new RequestPrivate(RequestTest::$USER, false);
$this->assertTrue($privateInternal->execute());
}
public function testDisabled() {
// disabled method
$disabledMethod = new RequestDisabled(RequestTest::$USER, true);
$this->assertFalse($this->simulateRequest($disabledMethod, "GET"));
$this->assertEquals("This function is currently disabled.", $disabledMethod->getLastError(), $disabledMethod->getLastError());
$this->assertEquals(503, self::$SENT_STATUS_CODE);
}
public function testLoginRequired() {
$loginRequired = new RequestLoginRequired(RequestTest::$USER, true);
$this->assertFalse($this->simulateRequest($loginRequired, "GET"));
$this->assertEquals("You are not logged in.", $loginRequired->getLastError(), $loginRequired->getLastError());
$this->assertEquals(401, self::$SENT_STATUS_CODE);
}
}
abstract class TestRequest extends Request {
public function __construct(User $user, bool $externalCall = false, $params = []) {
parent::__construct($user, $externalCall, $params);
}
protected function _die(string $data = ""): bool {
__new_die_impl($data);
return true;
}
}
class RequestAllMethods extends TestRequest {
public function __construct(User $user, bool $externalCall = false) {
parent::__construct($user, $externalCall, []);
}
}
class RequestOnlyPost extends TestRequest {
public function __construct(User $user, bool $externalCall = false) {
parent::__construct($user, $externalCall, []);
$this->forbidMethod("GET");
}
}
class RequestPrivate extends TestRequest {
public function __construct(User $user, bool $externalCall = false) {
parent::__construct($user, $externalCall, []);
$this->isPublic = false;
}
}
class RequestDisabled extends TestRequest {
public function __construct(User $user, bool $externalCall = false) {
parent::__construct($user, $externalCall, []);
$this->isDisabled = true;
}
}
class RequestLoginRequired extends TestRequest {
public function __construct(User $user, bool $externalCall = false) {
parent::__construct($user, $externalCall, []);
$this->loginRequired = true;
}
}