file_encrypt-8.x-1.x-dev/tests/src/Unit/StreamFilterTest.php
tests/src/Unit/StreamFilterTest.php
<?php
namespace Drupal\Tests\file_encrypt\Unit;
use Drupal\encrypt\EncryptServiceInterface;
use Drupal\encrypt\Entity\EncryptionProfile;
use Drupal\file_encrypt\EncryptStreamWrapper;
use Drupal\file_encrypt\StreamFilter\DecryptStreamFilter;
use Drupal\file_encrypt\StreamFilter\EncryptStreamFilter;
use Drupal\file_encrypt\StreamFilter\StreamFilterBase;
use Drupal\Tests\UnitTestCase;
use Drupal\encrypt\Exception\EncryptException;
/**
* Tests stream filtering.
*
* @group file_encrypt
*/
class StreamFilterTest extends UnitTestCase {
/**
* The encryption service.
*
* @var \Drupal\encrypt\EncryptServiceInterface|\PHPUnit\Framework\MockObject\MockObject
*/
protected $encryptionService;
/**
* The encryption method.
*
* @var \Drupal\encrypt\Entity\EncryptionProfile|\PHPUnit\Framework\MockObject\MockObject
*/
protected $encryptionProfile;
/**
* {@inheritdoc}
*/
protected function setUp(): void {
parent::setUp();
$this->encryptionService = $this->createMock(EncryptServiceInterface::class);
// @todo The current alpha release of encrypt module incorrectly depends on
// a concretion rather than an interface, so we must mock accordingly.
// Use \Drupal\encrypt\EncryptionProfileInterface once the next release
// comes out.
$this->encryptionProfile = $this->createMock(EncryptionProfile::class);
}
/**
* Tests the OpenSslUnsafe helper class.
*/
public function testOpenSslUnsafe() {
$original_data = 'Lorem ipsum dolor sit amet';
$encrypted_data = OpenSslUnsafe::encrypt($original_data);
$decrypted_data = OpenSslUnsafe::decrypt($encrypted_data);
$this->assertEquals($original_data, $decrypted_data, 'Data preserved through encryption and decryption.');
}
/**
* Tests the stream filters end-to-end.
*
* Note: This does NOT exercise the stream wrapper, which appends the filters
* in production.
*
* @param string $raw_data
* The raw data to filter.
* @param int $chunk_size
* The number of bytes on which to break buckets.
* @param string $message
* The assertion message.
*
* @dataProvider providerTestEndToEnd
*/
public function testEndToEnd($raw_data, $chunk_size, $message) {
$this->encryptionService->method('encrypt')
->willReturnCallback([OpenSslUnsafe::class, 'encrypt']);
$this->encryptionService->method('decrypt')
->willReturnCallback([OpenSslUnsafe::class, 'decrypt']);
$stream = $this->createFilteredStream($chunk_size);
fwrite($stream, $raw_data);
fseek($stream, 0);
$filtered_data = stream_get_contents($stream);
fclose($stream);
$this->assertEquals($raw_data, $filtered_data, $message);
}
/**
* Data provider.
*/
public function providerTestEndToEnd() {
yield ['', 1000, 'Zero-length string with large chunk size.'];
yield ['test', 100, 'Shorter string than chunk size.'];
yield ['Lorem ipsum dolor sit amet', 4, 'Larger string than chunk size.'];
$two_mb = pow(1024, 2) * 2;
$default_chunk_size = 1024 * 8;
yield [
openssl_random_pseudo_bytes($two_mb),
$default_chunk_size,
'Two megabytes of random data with the default chunk size.',
];
}
/**
* Tests creating a stream filter without required parameters.
*
* @param string $name
* The name of the required parameter to omit.
*
* @covers \Drupal\file_encrypt\StreamFilter\StreamFilterBase::onCreate
*
* @dataProvider providerTestCreatingStreamFilterWithoutRequiredParam
*/
public function testCreatingStreamFilterWithoutRequiredParam($name) {
$stream = fopen('php://temp', 'r+');
$params = $this->createDefaultParams();
unset($params[$name]);
stream_filter_register(EncryptStreamFilter::NAME, EncryptStreamFilter::class);
try {
stream_filter_append($stream, EncryptStreamFilter::NAME, NULL, $params);
$this->fail(sprintf("Did not throw exception for missing required parameter '%s'.", $name));
}
catch (\AssertionError $e) {
$this->assertEquals(sprintf("Missing or invalid '%s' parameter.", $name), $e->getMessage(), sprintf("Threw correct exception for missing required parameter '%s'.", $name));
}
finally {
fclose($stream);
}
}
/**
* Data provider.
*/
public function providerTestCreatingStreamFilterWithoutRequiredParam() {
return [
['encryption_service'],
['encryption_profile'],
];
}
/**
* Tests StreamFilterBase::maxPayloadLength().
*/
public function testMaxPayloadLength() {
$this->assertSame(9999999, StreamFilterBase::maxPayloadLength(), 'Maximum payload length calculated correctly.');
}
/**
* Tests EncryptStreamFilter::filterData().
*
* @param string $data
* The data to filter.
* @param string $message
* The assertion message.
*
* @dataProvider providerTestEncryptFilterData
*/
public function testEncryptFilterData($data, $message) {
$this->encryptionService
->method('encrypt')
->willReturn($data);
$filter = $this->createEncryptStreamFilter();
$actual_filtered_data = $filter->filterData($data);
$header = str_pad(strlen($data), StreamFilterBase::HEADER_LENGTH, StreamFilterBase::HEADER_PADDING_CHARACTER);
$expected_filtered_data = $header . $data;
$this->assertEquals($expected_filtered_data, $actual_filtered_data, $message);
}
/**
* Data provider.
*/
public function providerTestEncryptFilterData() {
return [
['', 'Zero-length string.'],
['test', 'Arbitrary string.'],
["\0\t\r\n", 'Special characters.'],
[
str_pad('', StreamFilterBase::maxPayloadLength(), 'abcdefghijklmnopqrstuvwxyz1234567890'),
'Maximum length string.',
],
];
}
/**
* Tests DecryptStreamFilter::shiftDatumFromBuffer().
*
* @param string $buffer
* The buffer variable.
* @param int $next_datum_size
* The size of the next datum in bytes.
* @param string $expected_datum
* The expected datum.
* @param string $expected_buffer
* The expected buffer value after operation.
*
* @dataProvider providerTestShiftDatumFromBuffer
*/
public function testShiftDatumFromBuffer($buffer, $next_datum_size, $expected_datum, $expected_buffer) {
$datum = DecryptStreamFilter::shiftDatumFromBuffer($buffer, $next_datum_size);
$this->assertEquals($expected_datum, $datum, 'Returned expected datum.');
$this->assertEquals($expected_buffer, $buffer, 'Updated buffer value correctly.');
}
/**
* Data provider.
*/
public function providerTestShiftDatumFromBuffer() {
return [
['1234567890', 5, '12345', '67890'],
["4\0\0\0\0\0\0test", 7, "4\0\0\0\0\0\0", 'test'],
];
}
/**
* Tests trying to decrypt bucket data that is too long.
*/
public function testEncryptingTooLongData() {
$this->expectException(EncryptException::class);
$this->expectExceptionMessage('Payload is too large');
$payload = str_repeat('x', StreamFilterBase::maxPayloadLength() + 1);
$this->encryptionService
->method('encrypt')
->willReturn($payload);
$filter = $this->createEncryptStreamFilter();
$filter->filterData('test');
}
/**
* Creates a stream resource with encrypt/decrypt filters attached.
*
* @param int $chunk_size
* The number of bytes on which to break buckets.
*
* @return resource
* A stream resource with filters attached.
*/
protected function createFilteredStream($chunk_size = 100) {
$stream = fopen('php://temp', 'r+');
stream_set_chunk_size($stream, $chunk_size);
stream_set_read_buffer($stream, $chunk_size);
stream_set_write_buffer($stream, $chunk_size);
$params = $this->createDefaultParams();
EncryptStreamWrapper::appendStreamFilter($stream, EncryptStreamFilter::NAME, EncryptStreamFilter::class, STREAM_FILTER_WRITE, $params);
EncryptStreamWrapper::appendStreamFilter($stream, DecryptStreamFilter::NAME, DecryptStreamFilter::class, STREAM_FILTER_READ, $params);
return $stream;
}
/**
* Creates default parameters for stream filters.
*
* @return array
* An array of stream filter parameters.
*/
protected function createDefaultParams() {
return [
'encryption_service' => $this->encryptionService,
'encryption_profile' => $this->encryptionProfile,
];
}
/**
* Creates an encrypt stream filter instance.
*
* @return \Drupal\file_encrypt\StreamFilter\EncryptStreamFilter
* An encrypt stream filter instance.
*/
protected function createEncryptStreamFilter() {
$filter = new EncryptStreamFilter();
$filter->params = $this->createDefaultParams();
$filter->onCreate();
return $filter;
}
/**
* Creates a decrypt stream filter instance.
*
* @return \Drupal\file_encrypt\StreamFilter\DecryptStreamFilter
* A decrypt stream filter instance.
*/
protected function createDecryptStreamFilter() {
$filter = new DecryptStreamFilter();
$filter->params = $this->createDefaultParams();
$filter->onCreate();
return $filter;
}
}
