diff --git a/CHANGELOG.md b/CHANGELOG.md index a8cf81a..7e53954 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,6 +21,15 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. ### Security +## [4.0.1] - 2020-03-29 + +### Fixed + +* Fix collection deserialization errors due to upstream `allowed_classes` being + set to `false`. For details, see [ramsey/uuid#303](https://github.com/ramsey/uuid/issues/303) + and [ramsey/collection#47](https://github.com/ramsey/collection/issues/47). + + ## [4.0.0] - 2020-03-22 ### Added @@ -1173,7 +1182,8 @@ versions leading up to this release.* [ramsey/uuid-doctrine]: https://github.com/ramsey/uuid-doctrine [ramsey/uuid-console]: https://github.com/ramsey/uuid-console -[unreleased]: https://github.com/ramsey/uuid/compare/4.0.0...HEAD +[unreleased]: https://github.com/ramsey/uuid/compare/4.0.1...HEAD +[4.0.1]: https://github.com/ramsey/uuid/compare/4.0.0...4.0.1 [4.0.0]: https://github.com/ramsey/uuid/compare/4.0.0-beta2...4.0.0 [4.0.0-beta2]: https://github.com/ramsey/uuid/compare/4.0.0-beta1...4.0.0-beta2 [4.0.0-beta1]: https://github.com/ramsey/uuid/compare/4.0.0-alpha5...4.0.0-beta1 diff --git a/src/Builder/BuilderCollection.php b/src/Builder/BuilderCollection.php index 67b5a50..b3e5f1d 100644 --- a/src/Builder/BuilderCollection.php +++ b/src/Builder/BuilderCollection.php @@ -16,6 +16,13 @@ namespace Ramsey\Uuid\Builder; use Ramsey\Collection\AbstractCollection; use Ramsey\Collection\CollectionInterface; +use Ramsey\Uuid\Converter\Number\GenericNumberConverter; +use Ramsey\Uuid\Converter\Time\GenericTimeConverter; +use Ramsey\Uuid\Converter\Time\PhpTimeConverter; +use Ramsey\Uuid\Guid\GuidBuilder; +use Ramsey\Uuid\Math\BrickMathCalculator; +use Ramsey\Uuid\Nonstandard\UuidBuilder as NonstandardUuidBuilder; +use Ramsey\Uuid\Rfc4122\UuidBuilder as Rfc4122UuidBuilder; use Traversable; /** @@ -37,4 +44,30 @@ class BuilderCollection extends AbstractCollection implements CollectionInterfac { return parent::getIterator(); } + + /** + * Re-constructs the object from its serialized form + * + * @param string $serialized The serialized PHP string to unserialize into + * a UuidInterface instance + * + * @phpcsSuppress SlevomatCodingStandard.TypeHints.ParameterTypeHint.MissingNativeTypeHint + */ + public function unserialize($serialized): void + { + /** @var mixed[] $data */ + $data = unserialize($serialized, [ + 'allowed_classes' => [ + BrickMathCalculator::class, + GenericNumberConverter::class, + GenericTimeConverter::class, + GuidBuilder::class, + NonstandardUuidBuilder::class, + PhpTimeConverter::class, + Rfc4122UuidBuilder::class, + ], + ]); + + $this->data = $data; + } } diff --git a/src/Provider/Node/NodeProviderCollection.php b/src/Provider/Node/NodeProviderCollection.php index 0037153..89d0917 100644 --- a/src/Provider/Node/NodeProviderCollection.php +++ b/src/Provider/Node/NodeProviderCollection.php @@ -17,6 +17,7 @@ namespace Ramsey\Uuid\Provider\Node; use Ramsey\Collection\AbstractCollection; use Ramsey\Collection\CollectionInterface; use Ramsey\Uuid\Provider\NodeProviderInterface; +use Ramsey\Uuid\Type\Hexadecimal; /** * A collection of NodeProviderInterface objects @@ -27,4 +28,27 @@ class NodeProviderCollection extends AbstractCollection implements CollectionInt { return NodeProviderInterface::class; } + + /** + * Re-constructs the object from its serialized form + * + * @param string $serialized The serialized PHP string to unserialize into + * a UuidInterface instance + * + * @phpcsSuppress SlevomatCodingStandard.TypeHints.ParameterTypeHint.MissingNativeTypeHint + */ + public function unserialize($serialized): void + { + /** @var mixed[] $data */ + $data = unserialize($serialized, [ + 'allowed_classes' => [ + Hexadecimal::class, + RandomNodeProvider::class, + StaticNodeProvider::class, + SystemNodeProvider::class, + ], + ]); + + $this->data = $data; + } } diff --git a/src/Uuid.php b/src/Uuid.php index 3260e68..0f86cdd 100644 --- a/src/Uuid.php +++ b/src/Uuid.php @@ -291,6 +291,7 @@ class Uuid implements UuidInterface $this->codec = $uuid->codec; $this->numberConverter = $uuid->numberConverter; $this->fields = $uuid->fields; + $this->timeConverter = $uuid->timeConverter; } public function compareTo(UuidInterface $other): int diff --git a/tests/Builder/FallbackBuilderTest.php b/tests/Builder/FallbackBuilderTest.php index d5d0d82..0a811cb 100644 --- a/tests/Builder/FallbackBuilderTest.php +++ b/tests/Builder/FallbackBuilderTest.php @@ -4,14 +4,28 @@ declare(strict_types=1); namespace Ramsey\Uuid\Test\Builder; +use DateTimeInterface; use Mockery; use Ramsey\Uuid\Builder\BuilderCollection; use Ramsey\Uuid\Builder\FallbackBuilder; use Ramsey\Uuid\Builder\UuidBuilderInterface; use Ramsey\Uuid\Codec\CodecInterface; +use Ramsey\Uuid\Codec\StringCodec; +use Ramsey\Uuid\Converter\Number\GenericNumberConverter; +use Ramsey\Uuid\Converter\Time\GenericTimeConverter; +use Ramsey\Uuid\Converter\Time\PhpTimeConverter; use Ramsey\Uuid\Exception\BuilderNotFoundException; use Ramsey\Uuid\Exception\UnableToBuildUuidException; +use Ramsey\Uuid\Guid\GuidBuilder; +use Ramsey\Uuid\Math\BrickMathCalculator; +use Ramsey\Uuid\Nonstandard\UuidBuilder as NonstandardUuidBuilder; +use Ramsey\Uuid\Nonstandard\UuidV6; +use Ramsey\Uuid\Rfc4122\UuidBuilder as Rfc4122UuidBuilder; +use Ramsey\Uuid\Rfc4122\UuidV1; +use Ramsey\Uuid\Rfc4122\UuidV2; use Ramsey\Uuid\Test\TestCase; +use Ramsey\Uuid\UuidInterface; +use Ramsey\Uuid\Validator\GenericValidator; class FallbackBuilderTest extends TestCase { @@ -50,4 +64,148 @@ class FallbackBuilderTest extends TestCase $fallbackBuilder->build($codec, $bytes); } + + /** + * @dataProvider provideBytes + */ + public function testSerializationOfBuilderCollection(string $bytes): void + { + $validator = new GenericValidator(); + $calculator = new BrickMathCalculator(); + $genericNumberConverter = new GenericNumberConverter($calculator); + $genericTimeConverter = new GenericTimeConverter($calculator); + $phpTimeConverter = new PhpTimeConverter($calculator, $genericTimeConverter); + + // Use the GenericTimeConverter. + $guidBuilder = new GuidBuilder($genericNumberConverter, $genericTimeConverter); + $rfc4122Builder = new Rfc4122UuidBuilder($genericNumberConverter, $genericTimeConverter); + $nonstandardBuilder = new NonstandardUuidBuilder($genericNumberConverter, $genericTimeConverter); + + // Use the PhpTimeConverter. + $guidBuilder2 = new GuidBuilder($genericNumberConverter, $phpTimeConverter); + $rfc4122Builder2 = new Rfc4122UuidBuilder($genericNumberConverter, $phpTimeConverter); + $nonstandardBuilder2 = new NonstandardUuidBuilder($genericNumberConverter, $phpTimeConverter); + + $builderCollection = new BuilderCollection( + [ + $guidBuilder, + $guidBuilder2, + $rfc4122Builder, + $rfc4122Builder2, + $nonstandardBuilder, + $nonstandardBuilder2, + ] + ); + + $serializedBuilderCollection = serialize($builderCollection); + + /** @var BuilderCollection $unserializedBuilderCollection */ + $unserializedBuilderCollection = unserialize($serializedBuilderCollection); + + $this->assertInstanceOf(BuilderCollection::class, $unserializedBuilderCollection); + + /** @var UuidBuilderInterface $builder */ + foreach ($unserializedBuilderCollection as $builder) { + $codec = new StringCodec($builder); + + $this->assertInstanceOf(UuidBuilderInterface::class, $builder); + + try { + $uuid = $builder->build($codec, $bytes); + $this->assertInstanceOf(UuidInterface::class, $uuid); + + if (($uuid instanceof UuidV1) || ($uuid instanceof UuidV2) || ($uuid instanceof UuidV6)) { + $this->assertInstanceOf(DateTimeInterface::class, $uuid->getDateTime()); + } + } catch (UnableToBuildUuidException $exception) { + switch ($exception->getMessage()) { + case 'The byte string received does not contain a valid version': + case 'The byte string received does not conform to the RFC 4122 variant': + case 'The byte string received does not conform to the RFC 4122 or Microsoft Corporation variants': + // This is expected; ignoring. + + break; + default: + throw $exception; + } + } + } + } + + /** + * @phpcsSuppress SlevomatCodingStandard.TypeHints.ReturnTypeHint.MissingTraversableTypeHintSpecification + */ + public function provideBytes(): array + { + return [ + [ + // GUID bytes + 'bytes' => hex2bin('b08c6fff7dc5e1110b210800200c9a66'), + ], + [ + // GUID bytes + 'bytes' => hex2bin('b08c6fff7dc5e1111b210800200c9a66'), + ], + [ + // GUID bytes + 'bytes' => hex2bin('b08c6fff7dc5e1112b210800200c9a66'), + ], + [ + // GUID bytes + 'bytes' => hex2bin('b08c6fff7dc5e1113b210800200c9a66'), + ], + [ + // GUID bytes + 'bytes' => hex2bin('b08c6fff7dc5e1114b210800200c9a66'), + ], + [ + // GUID bytes + 'bytes' => hex2bin('b08c6fff7dc5e1115b210800200c9a66'), + ], + [ + // GUID bytes + 'bytes' => hex2bin('b08c6fff7dc5e1116b210800200c9a66'), + ], + [ + // GUID bytes + 'bytes' => hex2bin('b08c6fff7dc5e1117b210800200c9a66'), + ], + [ + // GUID bytes + 'bytes' => hex2bin('b08c6fff7dc5e111eb210800200c9a66'), + ], + [ + // GUID bytes + 'bytes' => hex2bin('b08c6fff7dc5e111fb210800200c9a66'), + ], + [ + // Version 1 bytes + 'bytes' => hex2bin('ff6f8cb0c57d11e19b210800200c9a66'), + ], + [ + // Version 2 bytes + 'bytes' => hex2bin('000001f55cde21ea84000242ac130003'), + ], + [ + // Version 3 bytes + 'bytes' => hex2bin('ff6f8cb0c57d31e1bb210800200c9a66'), + ], + [ + // Version 4 bytes + 'bytes' => hex2bin('ff6f8cb0c57d41e1ab210800200c9a66'), + ], + [ + // Version 5 bytes + 'bytes' => hex2bin('ff6f8cb0c57d51e18b210800200c9a66'), + ], + [ + // Version 6 bytes + 'bytes' => hex2bin('ff6f8cb0c57d61e18b210800200c9a66'), + ], + [ + // NIL bytes + 'bytes' => hex2bin('00000000000000000000000000000000'), + ], + ]; + } } diff --git a/tests/Provider/Node/FallbackNodeProviderTest.php b/tests/Provider/Node/FallbackNodeProviderTest.php index 4cc7f5f..a2b8baf 100644 --- a/tests/Provider/Node/FallbackNodeProviderTest.php +++ b/tests/Provider/Node/FallbackNodeProviderTest.php @@ -7,6 +7,9 @@ namespace Ramsey\Uuid\Test\Provider\Node; use Ramsey\Uuid\Exception\NodeException; use Ramsey\Uuid\Provider\Node\FallbackNodeProvider; use Ramsey\Uuid\Provider\Node\NodeProviderCollection; +use Ramsey\Uuid\Provider\Node\RandomNodeProvider; +use Ramsey\Uuid\Provider\Node\StaticNodeProvider; +use Ramsey\Uuid\Provider\Node\SystemNodeProvider; use Ramsey\Uuid\Provider\NodeProviderInterface; use Ramsey\Uuid\Test\TestCase; use Ramsey\Uuid\Type\Hexadecimal; @@ -65,4 +68,31 @@ class FallbackNodeProviderTest extends TestCase $provider->getNode(); } + + public function testSerializationOfNodeProviderCollection(): void + { + $staticNodeProvider = new StaticNodeProvider(new Hexadecimal('aabbccddeeff')); + $randomNodeProvider = new RandomNodeProvider(); + $systemNodeProvider = new SystemNodeProvider(); + + $nodeProviderCollection = new NodeProviderCollection( + [ + $staticNodeProvider, + $randomNodeProvider, + $systemNodeProvider, + ] + ); + + $serializedNodeProviderCollection = serialize($nodeProviderCollection); + + /** @var NodeProviderCollection $unserializedNodeProviderCollection */ + $unserializedNodeProviderCollection = unserialize($serializedNodeProviderCollection); + + $this->assertInstanceOf(NodeProviderCollection::class, $unserializedNodeProviderCollection); + + foreach ($unserializedNodeProviderCollection as $nodeProvider) { + $this->assertInstanceOf(NodeProviderInterface::class, $nodeProvider); + $this->assertInstanceOf(Hexadecimal::class, $nodeProvider->getNode()); + } + } }