Skip to content

Commit

Permalink
Merge pull request #8 from IlicMiljan/improve-exception-handling
Browse files Browse the repository at this point in the history
[Breaking Change] Services - Improve Exception Handling
  • Loading branch information
IlicMiljan committed Mar 10, 2024
2 parents b210580 + 61a7882 commit 70ead66
Show file tree
Hide file tree
Showing 13 changed files with 269 additions and 27 deletions.
14 changes: 10 additions & 4 deletions src/Cipher/AdvancedEncryptionStandardCipher.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,21 +6,20 @@
use IlicMiljan\SecureProps\Cipher\Exception\FailedDecryptingValue;
use IlicMiljan\SecureProps\Cipher\Exception\FailedEncryptingValue;
use IlicMiljan\SecureProps\Cipher\Exception\FailedGeneratingInitializationVector;
use InvalidArgumentException;
use IlicMiljan\SecureProps\Cipher\Exception\InvalidKeyLength;
use SensitiveParameter;

class AdvancedEncryptionStandardCipher implements Cipher
{
private const CIPHER = 'AES-256-GCM';
private const TAG_LENGTH = 16;
private const KEY_LENGTH = 32;

public function __construct(
#[SensitiveParameter]
private string $key
) {
if (strlen($key) !== 32) {
throw new InvalidArgumentException('Key must be 32 bytes (256 bits) long.');
}
$this->validateKey($key);
}

/**
Expand Down Expand Up @@ -101,4 +100,11 @@ public function calculateInitializationVectorLength(string $cipher): int

return $ivLength;
}

public function validateKey(string $key): void
{
if (strlen($key) !== self::KEY_LENGTH) {
throw new InvalidKeyLength(self::KEY_LENGTH);
}
}
}
25 changes: 25 additions & 0 deletions src/Cipher/Exception/InvalidKeyLength.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<?php

namespace IlicMiljan\SecureProps\Cipher\Exception;

use RuntimeException;
use Throwable;

class InvalidKeyLength extends RuntimeException implements CipherException
{
public function __construct(
private int $expectedLength,
?Throwable $previous = null
) {
parent::__construct(
'The provided key is too short.',
0,
$previous
);
}

public function getExpectedLength(): int
{
return $this->expectedLength;
}
}
9 changes: 9 additions & 0 deletions src/Exception/EncryptionServiceException.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<?php

namespace IlicMiljan\SecureProps\Exception;

use Throwable;

interface EncryptionServiceException extends Throwable
{
}
25 changes: 25 additions & 0 deletions src/Exception/ValueMustBeObject.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<?php

namespace IlicMiljan\SecureProps\Exception;

use RuntimeException;
use Throwable;

class ValueMustBeObject extends RuntimeException implements EncryptionServiceException
{
public function __construct(
private string $type,
?Throwable $previous = null
) {
parent::__construct(
'The value must be an object.',
0,
$previous
);
}

public function getType(): string
{
return $this->type;
}
}
25 changes: 25 additions & 0 deletions src/Exception/ValueMustBeString.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<?php

namespace IlicMiljan\SecureProps\Exception;

use RuntimeException;
use Throwable;

class ValueMustBeString extends RuntimeException implements EncryptionServiceException
{
public function __construct(
private string $type,
?Throwable $previous = null
) {
parent::__construct(
'The value must be a string.',
0,
$previous
);
}

public function getType(): string
{
return $this->type;
}
}
12 changes: 8 additions & 4 deletions src/ObjectEncryptionService.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,11 @@
use IlicMiljan\SecureProps\Attribute\Encrypted;
use IlicMiljan\SecureProps\Cipher\Cipher;
use IlicMiljan\SecureProps\Cipher\Exception\CipherException;
use IlicMiljan\SecureProps\Exception\EncryptionServiceException;
use IlicMiljan\SecureProps\Exception\ValueMustBeObject;
use IlicMiljan\SecureProps\Exception\ValueMustBeString;
use IlicMiljan\SecureProps\Reader\Exception\ReaderException;
use IlicMiljan\SecureProps\Reader\ObjectPropertiesReader;
use InvalidArgumentException;
use ReflectionProperty;
use SensitiveParameter;

Expand All @@ -23,13 +25,14 @@ public function __construct(
* @param mixed $value
* @return object
*
* @throws EncryptionServiceException
* @throws CipherException
* @throws ReaderException
*/
public function encrypt(#[SensitiveParameter] mixed $value): object
{
if (!is_object($value)) {
throw new InvalidArgumentException('Value must be object.');
throw new ValueMustBeObject(gettype($value));
}

$encryptedProperties = $this->objectPropertiesReader->getPropertiesWithAttribute($value, Encrypted::class);
Expand All @@ -50,13 +53,14 @@ public function encrypt(#[SensitiveParameter] mixed $value): object
*
* @return object
*
* @throws EncryptionServiceException
* @throws CipherException
* @throws ReaderException
*/
public function decrypt(#[SensitiveParameter] mixed $value): object
{
if (!is_object($value)) {
throw new InvalidArgumentException('Value must be object.');
throw new ValueMustBeObject(gettype($value));
}

$encryptedProperties = $this->objectPropertiesReader->getPropertiesWithAttribute($value, Encrypted::class);
Expand All @@ -83,7 +87,7 @@ private function updatePropertyValue(ReflectionProperty $property, object $objec
}

if (!is_string($currentValue)) {
throw new InvalidArgumentException('Value must be string.');
throw new ValueMustBeString(gettype($currentValue));
}

$property->setValue($object, $callback($currentValue));
Expand Down
12 changes: 9 additions & 3 deletions src/StringEncryptionService.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@

use IlicMiljan\SecureProps\Cipher\Cipher;
use IlicMiljan\SecureProps\Cipher\Exception\CipherException;
use InvalidArgumentException;
use IlicMiljan\SecureProps\Exception\EncryptionServiceException;
use IlicMiljan\SecureProps\Exception\ValueMustBeString;
use SensitiveParameter;

class StringEncryptionService implements EncryptionService
Expand All @@ -16,13 +17,16 @@ public function __construct(

/**
* @param mixed $value
*
* @return string
*
* @throws EncryptionServiceException
* @throws CipherException
*/
public function encrypt(#[SensitiveParameter] mixed $value): string
{
if (!is_string($value)) {
throw new InvalidArgumentException('Value must be string.');
throw new ValueMustBeString(gettype($value));
}

return $this->cipher->encrypt($value);
Expand All @@ -31,12 +35,14 @@ public function encrypt(#[SensitiveParameter] mixed $value): string
/**
* @param mixed $value
* @return string
*
* @throws EncryptionServiceException
* @throws CipherException
*/
public function decrypt(#[SensitiveParameter] mixed $value): string
{
if (!is_string($value)) {
throw new InvalidArgumentException('Value must be string.');
throw new ValueMustBeString(gettype($value));
}

return $this->cipher->decrypt($value);
Expand Down
4 changes: 2 additions & 2 deletions tests/Cipher/AdvancedEncryptionStandardCipherTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
namespace IlicMiljan\SecureProps\Tests\Cipher;

use IlicMiljan\SecureProps\Cipher\AdvancedEncryptionStandardCipher;
use InvalidArgumentException;
use IlicMiljan\SecureProps\Cipher\Exception\InvalidKeyLength;
use PHPUnit\Framework\TestCase;

class AdvancedEncryptionStandardCipherTest extends TestCase
Expand All @@ -17,7 +17,7 @@ protected function setUp(): void

public function testConstructWithInvalidKeyLengthThrowsException(): void
{
$this->expectException(InvalidArgumentException::class);
$this->expectException(InvalidKeyLength::class);

new AdvancedEncryptionStandardCipher(openssl_random_pseudo_bytes(16));
}
Expand Down
47 changes: 47 additions & 0 deletions tests/Cipher/Exception/InvalidKeyLengthTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
<?php

namespace IlicMiljan\SecureProps\Tests\Cipher\Exception;

use IlicMiljan\SecureProps\Cipher\Exception\CipherException;
use IlicMiljan\SecureProps\Cipher\Exception\InvalidKeyLength;
use PHPUnit\Framework\TestCase;
use RuntimeException;

class InvalidKeyLengthTest extends TestCase
{
private int $expectedLength;

protected function setUp(): void
{
$this->expectedLength = 32;
}

public function testCanBeCreated(): void
{
$exception = new InvalidKeyLength($this->expectedLength);

$this->assertInstanceOf(InvalidKeyLength::class, $exception);
}

public function testReturnsExpectedLength(): void
{
$exception = new InvalidKeyLength($this->expectedLength);

$this->assertEquals($this->expectedLength, $exception->getExpectedLength());
}

public function testPreviousExceptionIsStored(): void
{
$previous = new RuntimeException('Previous exception');
$exception = new InvalidKeyLength($this->expectedLength, $previous);

$this->assertSame($previous, $exception->getPrevious());
}

public function testImplementsCipherExceptionInterface(): void
{
$exception = new InvalidKeyLength($this->expectedLength);

$this->assertInstanceOf(CipherException::class, $exception);
}
}
47 changes: 47 additions & 0 deletions tests/Exception/ValueMustBeObjectTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
<?php

namespace IlicMiljan\SecureProps\Tests\Exception;

use IlicMiljan\SecureProps\Exception\EncryptionServiceException;
use IlicMiljan\SecureProps\Exception\ValueMustBeObject;
use PHPUnit\Framework\TestCase;
use RuntimeException;

class ValueMustBeObjectTest extends TestCase
{
private string $type;

protected function setUp(): void
{
$this->type = 'string';
}

public function testCanBeCreated(): void
{
$exception = new ValueMustBeObject($this->type);

$this->assertInstanceOf(ValueMustBeObject::class, $exception);
}

public function testReturnsType(): void
{
$exception = new ValueMustBeObject($this->type);

$this->assertEquals($this->type, $exception->getType());
}

public function testPreviousExceptionIsStored(): void
{
$previous = new RuntimeException('Previous exception');
$exception = new ValueMustBeObject($this->type, $previous);

$this->assertSame($previous, $exception->getPrevious());
}

public function testImplementsEncryptionServiceExceptionInterface(): void
{
$exception = new ValueMustBeObject($this->type);

$this->assertInstanceOf(EncryptionServiceException::class, $exception);
}
}
47 changes: 47 additions & 0 deletions tests/Exception/ValueMustBeStringTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
<?php

namespace IlicMiljan\SecureProps\Tests\Exception;

use IlicMiljan\SecureProps\Exception\EncryptionServiceException;
use IlicMiljan\SecureProps\Exception\ValueMustBeString;
use PHPUnit\Framework\TestCase;
use RuntimeException;

class ValueMustBeStringTest extends TestCase
{
private string $type;

protected function setUp(): void
{
$this->type = 'object';
}

public function testCanBeCreated(): void
{
$exception = new ValueMustBeString($this->type);

$this->assertInstanceOf(ValueMustBeString::class, $exception);
}

public function testReturnsType(): void
{
$exception = new ValueMustBeString($this->type);

$this->assertEquals($this->type, $exception->getType());
}

public function testPreviousExceptionIsStored(): void
{
$previous = new RuntimeException('Previous exception');
$exception = new ValueMustBeString($this->type, $previous);

$this->assertSame($previous, $exception->getPrevious());
}

public function testImplementsEncryptionServiceExceptionInterface(): void
{
$exception = new ValueMustBeString($this->type);

$this->assertInstanceOf(EncryptionServiceException::class, $exception);
}
}

0 comments on commit 70ead66

Please sign in to comment.