Support generation of version 2 (DCE Security) UUIDs

This commit is contained in:
Ben Ramsey
2020-01-18 12:12:01 -06:00
parent b52fff6b52
commit 72a2312f62
19 changed files with 1314 additions and 26 deletions
+4
View File
@@ -36,11 +36,14 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
UUIDs or creating UUIDs from existing strings, bytes, or integers, if the UUID
is an RFC 4122 variant, one of these instances will be returned:
* `Rfc4122\UuidV1`
* `Rfc4122\UuidV2`
* `Rfc4122\UuidV3`
* `Rfc4122\UuidV4`
* `Rfc4122\UuidV5`
* `Rfc4122\NilUuid`
* Add `Rfc4122\UuidBuilder` to build RFC 4122 variant UUIDs. This replaces the
existing `Builder\DefaultUuidBuilder`, which is now deprecated.
* Add ability to generate version 2 (DCE Security) UUIDs.
* Add classes to represent GUIDs and nonstandard (non-RFC 4122 variant) UUIDs:
* `Guid\Guid`
* `Nonstandard\Uuid`.
@@ -68,6 +71,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
returns an instance of `\DateTimeImmutable` instead of `\DateTime`.
* Add `getFields()` method to `UuidInterface`.
* Add `getValidator()` method to `UuidFactoryInterface`.
* Add `uuid2()` method to `UuidFactoryInterface`.
* Add `convertTime()` method to `Converter\TimeConverterInterface`.
* Add `getTime()` method to `Provider\TimeProviderInterface`.
* Change `Uuid::getFields()` to return an instance of `Fields\FieldsInterface`.
+1
View File
@@ -24,6 +24,7 @@
"mockery/mockery": "^1.3",
"moontoast/math": "^1.1",
"paragonie/random-lib": "^2",
"php-mock/php-mock-mockery": "^1.3",
"php-mock/php-mock-phpunit": "^2.5",
"phpstan/extension-installer": "^1.0",
"phpstan/phpdoc-parser": "0.4.1",
+11 -4
View File
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<files psalm-version="3.8.2@90d6b73fd8062432030ef39b7b6694b3902daa31">
<files psalm-version="3.8.3@389af1bfc739bfdff3f9e3dc7bd6499aee51a831">
<file src="src/Builder/DegradedUuidBuilder.php">
<DeprecatedClass occurrences="3">
<code>new DegradedTimeConverter()</code>
@@ -44,11 +44,18 @@
<code>uuid_parse($uuid)</code>
</MixedReturnStatement>
</file>
<file src="src/Provider/Dce/SystemDceSecurityProvider.php">
<ForbiddenCode occurrences="5">
<code>shell_exec('id -u')</code>
<code>shell_exec('id -g')</code>
<code>shell_exec('whoami /user /fo csv /nh')</code>
<code>shell_exec('net user %username% | findstr /b /i "Local Group Memberships"')</code>
<code>shell_exec('wmic group get name,sid | findstr /b /i ' . escapeshellarg($firstGroup))</code>
</ForbiddenCode>
</file>
<file src="src/Provider/Node/SystemNodeProvider.php">
<MixedArgument occurrences="4">
<MixedArgument occurrences="2">
<code>$node</code>
<code>constant('PHP_OS')</code>
<code>constant('PHP_OS')</code>
<code>$macs</code>
</MixedArgument>
<MixedAssignment occurrences="2">
+25
View File
@@ -0,0 +1,25 @@
<?php
/**
* This file is part of the ramsey/uuid library
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @copyright Copyright (c) Ben Ramsey <ben@benramsey.com>
* @license http://opensource.org/licenses/MIT MIT
*/
declare(strict_types=1);
namespace Ramsey\Uuid\Exception;
use RuntimeException as PhpRuntimeException;
/**
* Thrown to indicate an exception occurred while dealing with DCE Security
* (version 2) UUIDs
*/
class DceSecurityException extends PhpRuntimeException
{
}
+39
View File
@@ -24,6 +24,8 @@ use Ramsey\Uuid\Converter\NumberConverterInterface;
use Ramsey\Uuid\Converter\Time\GenericTimeConverter;
use Ramsey\Uuid\Converter\Time\PhpTimeConverter;
use Ramsey\Uuid\Converter\TimeConverterInterface;
use Ramsey\Uuid\Generator\DceSecurityGenerator;
use Ramsey\Uuid\Generator\DceSecurityGeneratorInterface;
use Ramsey\Uuid\Generator\PeclUuidTimeGenerator;
use Ramsey\Uuid\Generator\RandomGeneratorFactory;
use Ramsey\Uuid\Generator\RandomGeneratorInterface;
@@ -33,6 +35,8 @@ use Ramsey\Uuid\Guid\GuidBuilder;
use Ramsey\Uuid\Math\BrickMathCalculator;
use Ramsey\Uuid\Math\CalculatorInterface;
use Ramsey\Uuid\Nonstandard\UuidBuilder as NonstandardUuidBuilder;
use Ramsey\Uuid\Provider\Dce\SystemDceSecurityProvider;
use Ramsey\Uuid\Provider\DceSecurityProviderInterface;
use Ramsey\Uuid\Provider\Node\FallbackNodeProvider;
use Ramsey\Uuid\Provider\Node\RandomNodeProvider;
use Ramsey\Uuid\Provider\Node\SystemNodeProvider;
@@ -81,6 +85,11 @@ class FeatureSet
*/
private $codec;
/**
* @var DceSecurityGeneratorInterface
*/
private $dceSecurityGenerator;
/**
* @var NodeProviderInterface
*/
@@ -145,6 +154,7 @@ class FeatureSet
$this->nodeProvider = $this->buildNodeProvider();
$this->randomGenerator = $this->buildRandomGenerator();
$this->setTimeProvider(new SystemTimeProvider());
$this->setDceSecurityProvider(new SystemDceSecurityProvider());
$this->validator = new GenericValidator();
}
@@ -172,6 +182,14 @@ class FeatureSet
return $this->codec;
}
/**
* Returns the DCE Security generator configured for this environment
*/
public function getDceSecurityGenerator(): DceSecurityGeneratorInterface
{
return $this->dceSecurityGenerator;
}
/**
* Returns the node provider configured for this environment
*/
@@ -222,6 +240,14 @@ class FeatureSet
$this->timeConverter = $this->buildTimeConverter($calculator);
}
/**
* Sets the DCE Security provider to use in this environment
*/
public function setDceSecurityProvider(DceSecurityProviderInterface $dceSecurityProvider): void
{
$this->dceSecurityGenerator = $this->buildDceSecurityGenerator($dceSecurityProvider);
}
/**
* Sets the time provider to use in this environment
*/
@@ -252,6 +278,19 @@ class FeatureSet
return new StringCodec($this->builder);
}
/**
* Returns a DCE Security generator configured for this environment
*/
private function buildDceSecurityGenerator(
DceSecurityProviderInterface $dceSecurityProvider
): DceSecurityGeneratorInterface {
return new DceSecurityGenerator(
$this->numberConverter,
$this->timeGenerator,
$dceSecurityProvider
);
}
/**
* Returns a node provider configured for this environment
*/
+118
View File
@@ -0,0 +1,118 @@
<?php
/**
* This file is part of the ramsey/uuid library
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @copyright Copyright (c) Ben Ramsey <ben@benramsey.com>
* @license http://opensource.org/licenses/MIT MIT
*/
declare(strict_types=1);
namespace Ramsey\Uuid\Generator;
use Ramsey\Uuid\Converter\NumberConverterInterface;
use Ramsey\Uuid\Exception\InvalidArgumentException;
use Ramsey\Uuid\Provider\DceSecurityProviderInterface;
use Ramsey\Uuid\Type\Hexadecimal;
use Ramsey\Uuid\Type\IntegerValue;
use Ramsey\Uuid\Uuid;
/**
* DceSecurityGenerator generates strings of binary data based on a local
* domain, local identifier, node ID, clock sequence, and the current time
*/
class DceSecurityGenerator implements DceSecurityGeneratorInterface
{
private const DOMAINS = [
Uuid::DCE_DOMAIN_PERSON,
Uuid::DCE_DOMAIN_GROUP,
Uuid::DCE_DOMAIN_ORG,
];
/**
* @var NumberConverterInterface
*/
private $numberConverter;
/**
* @var TimeGeneratorInterface
*/
private $timeGenerator;
/**
* @var DceSecurityProviderInterface
*/
private $dceSecurityProvider;
public function __construct(
NumberConverterInterface $numberConverter,
TimeGeneratorInterface $timeGenerator,
DceSecurityProviderInterface $dceSecurityProvider
) {
$this->numberConverter = $numberConverter;
$this->timeGenerator = $timeGenerator;
$this->dceSecurityProvider = $dceSecurityProvider;
}
public function generate(
int $localDomain,
?IntegerValue $localIdentifier = null,
?Hexadecimal $node = null,
?int $clockSeq = null
): string {
if (!in_array($localDomain, self::DOMAINS)) {
throw new InvalidArgumentException(
'Local domain must be a valid DCE Security domain'
);
}
switch ($localDomain) {
case Uuid::DCE_DOMAIN_ORG:
if ($localIdentifier === null) {
throw new InvalidArgumentException(
'A local identifier must be provided for the org domain'
);
}
break;
case Uuid::DCE_DOMAIN_PERSON:
if ($localIdentifier === null) {
$localIdentifier = $this->dceSecurityProvider->getUid();
}
break;
case Uuid::DCE_DOMAIN_GROUP:
default:
if ($localIdentifier === null) {
$localIdentifier = $this->dceSecurityProvider->getGid();
}
break;
}
$domainByte = pack('n', $localDomain)[1];
$identifierBytes = hex2bin(str_pad(
$this->numberConverter->toHex($localIdentifier->toString()),
8,
'0',
STR_PAD_LEFT
));
if ($node instanceof Hexadecimal) {
$node = $node->toString();
}
/** @var string $bytes */
$bytes = $this->timeGenerator->generate($node, $clockSeq);
// Replace bytes in the time-based UUID with DCE Security values.
$bytes = substr_replace($bytes, $identifierBytes, 0, 4);
$bytes = substr_replace($bytes, $domainByte, 9, 1);
return $bytes;
}
}
@@ -0,0 +1,53 @@
<?php
/**
* This file is part of the ramsey/uuid library
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @copyright Copyright (c) Ben Ramsey <ben@benramsey.com>
* @license http://opensource.org/licenses/MIT MIT
*/
declare(strict_types=1);
namespace Ramsey\Uuid\Generator;
use Ramsey\Uuid\Rfc4122\UuidV2;
use Ramsey\Uuid\Type\Hexadecimal;
use Ramsey\Uuid\Type\IntegerValue;
/**
* A DCE Security generator generates strings of binary data based on a local
* domain, local identifier, node ID, clock sequence, and the current time
*
* @see UuidV2
*/
interface DceSecurityGeneratorInterface
{
/**
* Generate a binary string from a local domain, local identifier, node ID,
* clock sequence, and current time
*
* @param int $localDomain The local domain to use when generating bytes,
* according to DCE Security
* @param IntegerValue|null $localIdentifier The local identifier for the
* given domain; this may be a UID or GID on POSIX systems, if the local
* domain is person or group, or it may be a site-defined identifier
* if the local domain is org
* @param Hexadecimal|null $node A 48-bit number representing the hardware
* address
* @param int|null $clockSeq A 14-bit number used to help avoid duplicates
* that could arise when the clock is set backwards in time or if the
* node ID changes
*
* @return string A binary string
*/
public function generate(
int $localDomain,
?IntegerValue $localIdentifier = null,
?Hexadecimal $node = null,
?int $clockSeq = null
): string;
}
+6 -5
View File
@@ -23,11 +23,12 @@ interface TimeGeneratorInterface
/**
* Generate a binary string from a node ID, clock sequence, and current time
*
* @param int|string $node A 48-bit number representing the hardware address;
* this number may be represented as an integer or a hexadecimal string
* @param int $clockSeq A 14-bit number used to help avoid duplicates that
* could arise when the clock is set backwards in time or if the node ID
* changes
* @param int|string|null $node A 48-bit number representing the hardware
* address; this number may be represented as an integer or a
* hexadecimal string
* @param int|null $clockSeq A 14-bit number used to help avoid duplicates
* that could arise when the clock is set backwards in time or if the
* node ID changes
*
* @return string A binary string
*/
@@ -0,0 +1,223 @@
<?php
/**
* This file is part of the ramsey/uuid library
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @copyright Copyright (c) Ben Ramsey <ben@benramsey.com>
* @license http://opensource.org/licenses/MIT MIT
*/
declare(strict_types=1);
namespace Ramsey\Uuid\Provider\Dce;
use Ramsey\Uuid\Exception\DceSecurityException;
use Ramsey\Uuid\Provider\DceSecurityProviderInterface;
use Ramsey\Uuid\Type\IntegerValue;
/**
* SystemDceSecurityProvider retrieves the user or group identifiers from the system
*/
class SystemDceSecurityProvider implements DceSecurityProviderInterface
{
/**
* @throws DceSecurityException if unable to get a user identifier
*
* @inheritDoc
*/
public function getUid(): IntegerValue
{
static $uid = null;
if ($uid instanceof IntegerValue) {
return $uid;
}
if ($uid === null) {
$uid = $this->getSystemUid();
}
if ($uid === '') {
throw new DceSecurityException(
'Unable to get a user identifier using the system DCE '
. 'Security provider; please provide a custom identifier or '
. 'use a different provider'
);
}
$uid = new IntegerValue($uid);
return $uid;
}
/**
* @throws DceSecurityException if unable to get a group identifier
*
* @inheritDoc
*/
public function getGid(): IntegerValue
{
static $gid = null;
if ($gid instanceof IntegerValue) {
return $gid;
}
if ($gid === null) {
$gid = $this->getSystemGid();
}
if ($gid === '') {
throw new DceSecurityException(
'Unable to get a group identifier using the system DCE '
. 'Security provider; please provide a custom identifier or '
. 'use a different provider'
);
}
$gid = new IntegerValue($gid);
return $gid;
}
/**
* Returns the UID from the system
*/
private function getSystemUid(): string
{
if (!$this->hasShellExec()) {
return '';
}
switch ($this->getOs()) {
case 'WIN':
return $this->getWindowsUid();
case 'DAR':
case 'FRE':
case 'LIN':
default:
return trim((string) shell_exec('id -u'));
}
}
/**
* Returns the GID from the system
*/
private function getSystemGid(): string
{
if (!$this->hasShellExec()) {
return '';
}
switch ($this->getOs()) {
case 'WIN':
return $this->getWindowsGid();
case 'DAR':
case 'FRE':
case 'LIN':
default:
return trim((string) shell_exec('id -g'));
}
}
/**
* Returns true if shell_exec() is available for use
*/
private function hasShellExec(): bool
{
$disabledFunctions = strtolower((string) ini_get('disable_functions'));
return strpos($disabledFunctions, 'shell_exec') === false;
}
/**
* Returns the PHP_OS string
*/
private function getOs(): string
{
return strtoupper(substr(constant('PHP_OS'), 0, 3));
}
/**
* Returns the user identifier for a user on a Windows system
*
* Windows does not have the same concept as an effective POSIX UID for the
* running script. Instead, each user is uniquely identified by an SID
* (security identifier). The SID includes three 32-bit unsigned integers
* that make up a unique domain identifier, followed by an RID (relative
* identifier) that we will use as the UID. The primary caveat is that this
* UID may not be unique to the system, since it is, instead, unique to the
* domain.
*
* @link https://www.lifewire.com/what-is-an-sid-number-2626005 What Is an SID Number?
* @link https://bit.ly/30vE7NM Well-known SID Structures
* @link https://bit.ly/2FWcYKJ Well-known security identifiers in Windows operating systems
* @link https://www.windows-commandline.com/get-sid-of-user/ Get SID of user
*/
private function getWindowsUid(): string
{
$response = shell_exec('whoami /user /fo csv /nh');
if ($response === null) {
return '';
}
/** @var string $sid */
$sid = str_getcsv(trim($response))[1] ?? '';
if (($lastHyphen = strrpos($sid, '-')) === false) {
return '';
}
return trim(substr($sid, $lastHyphen + 1));
}
/**
* Returns a group identifier for a user on a Windows system
*
* Since Windows does not have the same concept as an effective POSIX GID
* for the running script, we will get the local group memberships for the
* user running the script. Then, we will get the SID (security identifier)
* for the first group. that appears in that list. Finally, we will return
* the RID (relative identifier) for the group and use that as the GID.
*
* @link https://www.windows-commandline.com/list-of-user-groups-command-line/ List of user groups command line
*/
private function getWindowsGid(): string
{
$response = shell_exec('net user %username% | findstr /b /i "Local Group Memberships"');
if ($response === null) {
return '';
}
/** @var string[] $userGroups */
$userGroups = preg_split('/\s{2,}/', $response, -1, PREG_SPLIT_NO_EMPTY);
$firstGroup = trim($userGroups[1] ?? '', "* \t\n\r\0\x0B");
if ($firstGroup === '') {
return '';
}
$response = shell_exec('wmic group get name,sid | findstr /b /i ' . escapeshellarg($firstGroup));
if ($response === null) {
return '';
}
/** @var string[] $userGroup */
$userGroup = preg_split('/\s{2,}/', $response, -1, PREG_SPLIT_NO_EMPTY);
$sid = $userGroup[1] ?? '';
if (($lastHyphen = strrpos($sid, '-')) === false) {
return '';
}
return trim((string) substr($sid, $lastHyphen + 1));
}
}
@@ -0,0 +1,41 @@
<?php
/**
* This file is part of the ramsey/uuid library
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @copyright Copyright (c) Ben Ramsey <ben@benramsey.com>
* @license http://opensource.org/licenses/MIT MIT
*/
declare(strict_types=1);
namespace Ramsey\Uuid\Provider;
use Ramsey\Uuid\Rfc4122\UuidV2;
use Ramsey\Uuid\Type\IntegerValue;
/**
* A DCE provider provides access to local domain identifiers for version 2,
* DCE Security, UUIDs
*
* @see UuidV2
*/
interface DceSecurityProviderInterface
{
/**
* Returns a user identifier for the system
*
* @link https://en.wikipedia.org/wiki/User_identifier User identifier
*/
public function getUid(): IntegerValue;
/**
* Returns a group identifier for the system
*
* @link https://en.wikipedia.org/wiki/Group_identifier Group identifier
*/
public function getGid(): IntegerValue;
}
+1 -2
View File
@@ -21,7 +21,6 @@ use Ramsey\Uuid\Converter\TimeConverterInterface;
use Ramsey\Uuid\Exception\UnableToBuildUuidException;
use Ramsey\Uuid\Exception\UnsupportedOperationException;
use Ramsey\Uuid\Rfc4122\UuidInterface as Rfc4122UuidInterface;
use Ramsey\Uuid\Uuid;
use Ramsey\Uuid\UuidInterface;
use Throwable;
@@ -79,7 +78,7 @@ class UuidBuilder implements UuidBuilderInterface
case 1:
return new UuidV1($fields, $this->numberConverter, $codec, $this->timeConverter);
case 2:
return new Uuid($fields, $this->numberConverter, $codec, $this->timeConverter);
return new UuidV2($fields, $this->numberConverter, $codec, $this->timeConverter);
case 3:
return new UuidV3($fields, $this->numberConverter, $codec, $this->timeConverter);
case 4:
+33
View File
@@ -0,0 +1,33 @@
<?php
/**
* This file is part of the ramsey/uuid library
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @copyright Copyright (c) Ben Ramsey <ben@benramsey.com>
* @license http://opensource.org/licenses/MIT MIT
*/
declare(strict_types=1);
namespace Ramsey\Uuid\Rfc4122;
use Ramsey\Uuid\Uuid;
/**
* DCE Security version, or version 2, UUIDs include local domain identifier,
* local ID for the specified domain, and node values that are combined into a
* 128-bit unsigned integer
*
* @link https://publications.opengroup.org/c311 DCE 1.1: Authentication and Security Services
* @link https://pubs.opengroup.org/onlinepubs/9696989899/chap5.htm#tagcjh_08_02_01_01 DCE 1.1, §5.2.1.1
* @link https://pubs.opengroup.org/onlinepubs/9696989899/chap11.htm#tagcjh_14_05_01_01 DCE 1.1, §11.5.1.1
* @link https://github.com/google/uuid Go package for UUIDs based on RFC 4122 and DCE 1.1: Auth and Security Services
*
* @psalm-immutable
*/
final class UuidV2 extends Uuid implements UuidInterface
{
}
+21
View File
@@ -143,6 +143,27 @@ class Uuid implements UuidInterface
*/
public const UUID_TYPE_HASH_SHA1 = 5;
/**
* DCE Security principal domain
*
* @link https://pubs.opengroup.org/onlinepubs/9696989899/chap11.htm#tagcjh_14_05_01_01 DCE 1.1, §11.5.1.1
*/
public const DCE_DOMAIN_PERSON = 0;
/**
* DCE Security group domain
*
* @link https://pubs.opengroup.org/onlinepubs/9696989899/chap11.htm#tagcjh_14_05_01_01 DCE 1.1, §11.5.1.1
*/
public const DCE_DOMAIN_GROUP = 1;
/**
* DCE Security organization domain
*
* @link https://pubs.opengroup.org/onlinepubs/9696989899/chap11.htm#tagcjh_14_05_01_01 DCE 1.1, §11.5.1.1
*/
public const DCE_DOMAIN_ORG = 2;
/**
* @var UuidFactoryInterface|null
*/
+27
View File
@@ -17,9 +17,12 @@ namespace Ramsey\Uuid;
use Ramsey\Uuid\Builder\UuidBuilderInterface;
use Ramsey\Uuid\Codec\CodecInterface;
use Ramsey\Uuid\Converter\NumberConverterInterface;
use Ramsey\Uuid\Generator\DceSecurityGeneratorInterface;
use Ramsey\Uuid\Generator\RandomGeneratorInterface;
use Ramsey\Uuid\Generator\TimeGeneratorInterface;
use Ramsey\Uuid\Provider\NodeProviderInterface;
use Ramsey\Uuid\Type\Hexadecimal;
use Ramsey\Uuid\Type\IntegerValue;
use Ramsey\Uuid\Validator\ValidatorInterface;
class UuidFactory implements UuidFactoryInterface
@@ -29,6 +32,11 @@ class UuidFactory implements UuidFactoryInterface
*/
private $codec;
/**
* @var DceSecurityGeneratorInterface
*/
private $dceSecurityGenerator;
/**
* @var NodeProviderInterface
*/
@@ -67,6 +75,7 @@ class UuidFactory implements UuidFactoryInterface
$features = $features ?: new FeatureSet();
$this->codec = $features->getCodec();
$this->dceSecurityGenerator = $features->getDceSecurityGenerator();
$this->nodeProvider = $features->getNodeProvider();
$this->numberConverter = $features->getNumberConverter();
$this->randomGenerator = $features->getRandomGenerator();
@@ -236,6 +245,24 @@ class UuidFactory implements UuidFactoryInterface
return $this->uuidFromHashedName($hex, 1);
}
public function uuid2(
int $localDomain,
?IntegerValue $localIdentifier,
?Hexadecimal $node = null,
?int $clockSeq = null
): UuidInterface {
$bytes = $this->dceSecurityGenerator->generate(
$localDomain,
$localIdentifier,
$node,
$clockSeq
);
$hex = bin2hex($bytes);
return $this->uuidFromHashedName($hex, 2);
}
/**
* @inheritDoc
*/
+25
View File
@@ -14,6 +14,8 @@ declare(strict_types=1);
namespace Ramsey\Uuid;
use Ramsey\Uuid\Type\Hexadecimal;
use Ramsey\Uuid\Type\IntegerValue;
use Ramsey\Uuid\Validator\ValidatorInterface;
/**
@@ -44,6 +46,29 @@ interface UuidFactoryInterface
*/
public function uuid1($node = null, ?int $clockSeq = null): UuidInterface;
/**
* Returns a version 2 (DCE Security) UUID from a local domain, local
* identifier, host ID, clock sequence, and the current time
*
* @param int $localDomain The local domain to use when generating bytes,
* according to DCE Security
* @param IntegerValue|null $localIdentifier The local identifier for the
* given domain; this may be a UID or GID on POSIX systems, if the local
* domain is person or group, or it may be a site-defined identifier
* if the local domain is org
* @param Hexadecimal|null $node A 48-bit number representing the hardware
* address
* @param int|null $clockSeq A 14-bit number used to help avoid duplicates
* that could arise when the clock is set backwards in time or if the
* node ID changes
*/
public function uuid2(
int $localDomain,
?IntegerValue $localIdentifier,
?Hexadecimal $node = null,
?int $clockSeq = null
): UuidInterface;
/**
* Returns a version 3 (name-based) UUID based on the MD5 hash of a
* namespace ID and a name
@@ -0,0 +1,157 @@
<?php
declare(strict_types=1);
namespace Ramsey\Uuid\Test\Generator;
use Mockery;
use Ramsey\Uuid\Converter\Number\GenericNumberConverter;
use Ramsey\Uuid\Converter\NumberConverterInterface;
use Ramsey\Uuid\Converter\Time\GenericTimeConverter;
use Ramsey\Uuid\Exception\InvalidArgumentException;
use Ramsey\Uuid\Generator\DceSecurityGenerator;
use Ramsey\Uuid\Generator\DefaultTimeGenerator;
use Ramsey\Uuid\Generator\TimeGeneratorInterface;
use Ramsey\Uuid\Math\BrickMathCalculator;
use Ramsey\Uuid\Provider\DceSecurityProviderInterface;
use Ramsey\Uuid\Provider\NodeProviderInterface;
use Ramsey\Uuid\Provider\Time\FixedTimeProvider;
use Ramsey\Uuid\Test\TestCase;
use Ramsey\Uuid\Type\Hexadecimal;
use Ramsey\Uuid\Type\IntegerValue;
use Ramsey\Uuid\Type\Time;
use Ramsey\Uuid\Uuid;
class DceSecurityGeneratorTest extends TestCase
{
/**
* @param mixed $uid
* @param mixed $gid
* @param mixed $seconds
* @param mixed $microseconds
*
* @dataProvider provideValuesForDceSecurityGenerator
*/
public function testGenerateBytesReplacesBytesWithDceValues(
$uid,
$gid,
string $node,
$seconds,
$microseconds,
int $providedDomain,
?IntegerValue $providedId,
?Hexadecimal $providedNode,
?int $providedClockSeq,
string $expectedId,
string $expectedDomain,
string $expectedNode,
string $expectedTimeMidHi
): void {
$dceSecurityProvider = Mockery::mock(DceSecurityProviderInterface::class, [
'getUid' => new IntegerValue($uid),
'getGid' => new IntegerValue($gid),
]);
$nodeProvider = Mockery::mock(NodeProviderInterface::class, [
'getNode' => $node,
]);
$timeProvider = new FixedTimeProvider(new Time($seconds, $microseconds));
$calculator = new BrickMathCalculator();
$numberConverter = new GenericNumberConverter($calculator);
$timeConverter = new GenericTimeConverter($calculator);
$timeGenerator = new DefaultTimeGenerator($nodeProvider, $timeConverter, $timeProvider);
$dceSecurityGenerator = new DceSecurityGenerator($numberConverter, $timeGenerator, $dceSecurityProvider);
$bytes = $dceSecurityGenerator->generate($providedDomain, $providedId, $providedNode, $providedClockSeq);
$this->assertSame($expectedId, bin2hex(substr($bytes, 0, 4)));
$this->assertSame($expectedDomain, bin2hex(substr($bytes, 9, 1)));
$this->assertSame($expectedNode, bin2hex(substr($bytes, 10)));
$this->assertSame($expectedTimeMidHi, bin2hex(substr($bytes, 4, 4)));
}
/**
* @phpcsSuppress SlevomatCodingStandard.TypeHints.ReturnTypeHint.MissingTraversableTypeHintSpecification
*/
public function provideValuesForDceSecurityGenerator(): array
{
return [
[
'uid' => '1001',
'gid' => '2001',
'node' => '001122334455',
'seconds' => 1579132397,
'microseconds' => 500000,
'providedDomain' => Uuid::DCE_DOMAIN_PERSON,
'providedId' => null,
'providedNode' => null,
'providedClockSeq' => null,
'expectedId' => '000003e9',
'expectedDomain' => '00',
'expectedNode' => '001122334455',
'expectedTimeMidHi' => '37f211ea',
],
[
'uid' => '1001',
'gid' => '2001',
'node' => '001122334455',
'seconds' => 1579132397,
'microseconds' => 500000,
'providedDomain' => Uuid::DCE_DOMAIN_GROUP,
'providedId' => null,
'providedNode' => null,
'providedClockSeq' => null,
'expectedId' => '000007d1',
'expectedDomain' => '01',
'expectedNode' => '001122334455',
'expectedTimeMidHi' => '37f211ea',
],
[
'uid' => 0,
'gid' => 0,
'node' => '001122334455',
'seconds' => 1579132397,
'microseconds' => 500000,
'providedDomain' => Uuid::DCE_DOMAIN_ORG,
'providedId' => new IntegerValue('4294967295'),
'providedNode' => null,
'providedClockSeq' => null,
'expectedId' => 'ffffffff',
'expectedDomain' => '02',
'expectedNode' => '001122334455',
'expectedTimeMidHi' => '37f211ea',
],
];
}
public function testGenerateThrowsExceptionForInvalidDomain(): void
{
$numberConverter = Mockery::mock(NumberConverterInterface::class);
$timeGenerator = Mockery::mock(TimeGeneratorInterface::class);
$dceSecurityProvider = Mockery::mock(DceSecurityProviderInterface::class);
$generator = new DceSecurityGenerator($numberConverter, $timeGenerator, $dceSecurityProvider);
$this->expectException(InvalidArgumentException::class);
$this->expectExceptionMessage('Local domain must be a valid DCE Security domain');
$generator->generate(42);
}
public function testGenerateThrowsExceptionForOrgWithoutIdentifier(): void
{
$numberConverter = Mockery::mock(NumberConverterInterface::class);
$timeGenerator = Mockery::mock(TimeGeneratorInterface::class);
$dceSecurityProvider = Mockery::mock(DceSecurityProviderInterface::class);
$generator = new DceSecurityGenerator($numberConverter, $timeGenerator, $dceSecurityProvider);
$this->expectException(InvalidArgumentException::class);
$this->expectExceptionMessage('A local identifier must be provided for the org domain');
$generator->generate(Uuid::DCE_DOMAIN_ORG);
}
}
@@ -0,0 +1,502 @@
<?php
declare(strict_types=1);
namespace Ramsey\Uuid\Test\Provider\Dce;
use Ramsey\Uuid\Exception\DceSecurityException;
use Ramsey\Uuid\Provider\Dce\SystemDceSecurityProvider;
use Ramsey\Uuid\Test\TestCase;
use Ramsey\Uuid\Type\IntegerValue;
use phpmock\mockery\PHPMockery;
class SystemDceSecurityProviderTest extends TestCase
{
/**
* @runInSeparateProcess
* @preserveGlobalState disabled
*/
public function testGetUidThrowsExceptionIfShellExecDisabled(): void
{
PHPMockery::mock(
'Ramsey\Uuid\Provider\Dce',
'ini_get'
)->with('disable_functions')->once()->andReturn('foo bar shell_exec baz');
$provider = new SystemDceSecurityProvider();
// Test that we catch the exception multiple times, but the ini_get()
// function is called only once.
$caughtException = 0;
for ($i = 1; $i <= 5; $i++) {
try {
$provider->getUid();
} catch (DceSecurityException $e) {
$caughtException++;
$this->assertSame(
'Unable to get a user identifier using the system DCE '
. 'Security provider; please provide a custom identifier or '
. 'use a different provider',
$e->getMessage()
);
}
}
$this->assertSame(5, $caughtException);
}
/**
* @runInSeparateProcess
* @preserveGlobalState disabled
*/
public function testGetUidForPosixThrowsExceptionIfShellExecReturnsNull(): void
{
PHPMockery::mock(
'Ramsey\Uuid\Provider\Dce',
'ini_get'
)->with('disable_functions')->once()->andReturn('nothing');
PHPMockery::mock(
'Ramsey\Uuid\Provider\Dce',
'constant'
)->with('PHP_OS')->once()->andReturn('Linux');
PHPMockery::mock(
'Ramsey\Uuid\Provider\Dce',
'shell_exec'
)->with('id -u')->once()->andReturnNull();
$provider = new SystemDceSecurityProvider();
$this->expectException(DceSecurityException::class);
$this->expectExceptionMessage(
'Unable to get a user identifier using the system DCE '
. 'Security provider; please provide a custom identifier or '
. 'use a different provider'
);
$provider->getUid();
}
/**
* @param mixed $value
*
* @runInSeparateProcess
* @preserveGlobalState disabled
* @dataProvider provideWindowsBadValues
*/
public function testGetUidForWindowsThrowsExceptionIfShellExecForWhoAmIReturnsBadValues($value): void
{
PHPMockery::mock(
'Ramsey\Uuid\Provider\Dce',
'ini_get'
)->with('disable_functions')->once()->andReturn('nothing');
PHPMockery::mock(
'Ramsey\Uuid\Provider\Dce',
'constant'
)->with('PHP_OS')->once()->andReturn('Windows_NT');
PHPMockery::mock(
'Ramsey\Uuid\Provider\Dce',
'shell_exec'
)->with('whoami /user /fo csv /nh')->once()->andReturn($value);
$provider = new SystemDceSecurityProvider();
$this->expectException(DceSecurityException::class);
$this->expectExceptionMessage(
'Unable to get a user identifier using the system DCE '
. 'Security provider; please provide a custom identifier or '
. 'use a different provider'
);
$provider->getUid();
}
/**
* @runInSeparateProcess
* @preserveGlobalState disabled
* @dataProvider provideWindowsGoodWhoAmIValues
*/
public function testGetUidForWindowsWhenShellExecForWhoAmIReturnsGoodValues(
string $value,
string $expectedId
): void {
PHPMockery::mock(
'Ramsey\Uuid\Provider\Dce',
'ini_get'
)->with('disable_functions')->once()->andReturn('nothing');
PHPMockery::mock(
'Ramsey\Uuid\Provider\Dce',
'constant'
)->with('PHP_OS')->once()->andReturn('Windows_NT');
PHPMockery::mock(
'Ramsey\Uuid\Provider\Dce',
'shell_exec'
)->with('whoami /user /fo csv /nh')->once()->andReturn($value);
$provider = new SystemDceSecurityProvider();
$uid = $provider->getUid();
$this->assertInstanceOf(IntegerValue::class, $uid);
$this->assertSame($expectedId, $uid->toString());
$this->assertSame($uid, $provider->getUid());
}
/**
* @phpcsSuppress SlevomatCodingStandard.TypeHints.ReturnTypeHint.MissingTraversableTypeHintSpecification
*/
public function provideWindowsGoodWhoAmIValues(): array
{
return [
[
'value' => '"Melilot Sackville","S-1-5-21-7375663-6890924511-1272660413-2944159"',
'expectedId' => '2944159',
],
[
'value' => '"Brutus Sandheaver","S-1-3-12-1234525106-3567804255-30012867-1437"',
'expectedId' => '1437',
],
[
'value' => '"Cora Rumble","S-345"',
'expectedId' => '345',
],
];
}
/**
* @runInSeparateProcess
* @preserveGlobalState disabled
* @dataProvider providePosixTestValues
*/
public function testGetUidForPosixSystems(string $os, string $id): void
{
PHPMockery::mock(
'Ramsey\Uuid\Provider\Dce',
'ini_get'
)->with('disable_functions')->once()->andReturn('nothing');
PHPMockery::mock(
'Ramsey\Uuid\Provider\Dce',
'constant'
)->with('PHP_OS')->once()->andReturn($os);
PHPMockery::mock(
'Ramsey\Uuid\Provider\Dce',
'shell_exec'
)->with('id -u')->once()->andReturn($id);
$provider = new SystemDceSecurityProvider();
$uid = $provider->getUid();
$this->assertInstanceOf(IntegerValue::class, $uid);
$this->assertSame($id, $uid->toString());
$this->assertSame($uid, $provider->getUid());
}
/**
* @runInSeparateProcess
* @preserveGlobalState disabled
*/
public function testGetGidThrowsExceptionIfShellExecDisabled(): void
{
PHPMockery::mock(
'Ramsey\Uuid\Provider\Dce',
'ini_get'
)->with('disable_functions')->once()->andReturn('foo bar shell_exec baz');
$provider = new SystemDceSecurityProvider();
// Test that we catch the exception multiple times, but the ini_get()
// function is called only once.
$caughtException = 0;
for ($i = 1; $i <= 5; $i++) {
try {
$provider->getGid();
} catch (DceSecurityException $e) {
$caughtException++;
$this->assertSame(
'Unable to get a group identifier using the system DCE '
. 'Security provider; please provide a custom identifier or '
. 'use a different provider',
$e->getMessage()
);
}
}
$this->assertSame(5, $caughtException);
}
/**
* @runInSeparateProcess
* @preserveGlobalState disabled
*/
public function testGetGidForPosixThrowsExceptionIfShellExecReturnsNull(): void
{
PHPMockery::mock(
'Ramsey\Uuid\Provider\Dce',
'ini_get'
)->with('disable_functions')->once()->andReturn('nothing');
PHPMockery::mock(
'Ramsey\Uuid\Provider\Dce',
'constant'
)->with('PHP_OS')->once()->andReturn('Linux');
PHPMockery::mock(
'Ramsey\Uuid\Provider\Dce',
'shell_exec'
)->with('id -g')->once()->andReturnNull();
$provider = new SystemDceSecurityProvider();
$this->expectException(DceSecurityException::class);
$this->expectExceptionMessage(
'Unable to get a group identifier using the system DCE '
. 'Security provider; please provide a custom identifier or '
. 'use a different provider'
);
$provider->getGid();
}
/**
* @runInSeparateProcess
* @preserveGlobalState disabled
* @dataProvider providePosixTestValues
*/
public function testGetGidForPosixSystems(string $os, string $id): void
{
PHPMockery::mock(
'Ramsey\Uuid\Provider\Dce',
'ini_get'
)->with('disable_functions')->once()->andReturn('nothing');
PHPMockery::mock(
'Ramsey\Uuid\Provider\Dce',
'constant'
)->with('PHP_OS')->once()->andReturn($os);
PHPMockery::mock(
'Ramsey\Uuid\Provider\Dce',
'shell_exec'
)->with('id -g')->once()->andReturn($id);
$provider = new SystemDceSecurityProvider();
$gid = $provider->getGid();
$this->assertInstanceOf(IntegerValue::class, $gid);
$this->assertSame($id, $gid->toString());
$this->assertSame($gid, $provider->getGid());
}
/**
* @param mixed $value
*
* @runInSeparateProcess
* @preserveGlobalState disabled
* @dataProvider provideWindowsBadValues
*/
public function testGetGidForWindowsThrowsExceptionWhenShellExecForNetUserReturnsBadValues($value): void
{
PHPMockery::mock(
'Ramsey\Uuid\Provider\Dce',
'ini_get'
)->with('disable_functions')->once()->andReturn('nothing');
PHPMockery::mock(
'Ramsey\Uuid\Provider\Dce',
'constant'
)->with('PHP_OS')->once()->andReturn('Windows_NT');
PHPMockery::mock(
'Ramsey\Uuid\Provider\Dce',
'shell_exec'
)->with('net user %username% | findstr /b /i "Local Group Memberships"')->once()->andReturn($value);
$provider = new SystemDceSecurityProvider();
$this->expectException(DceSecurityException::class);
$this->expectExceptionMessage(
'Unable to get a group identifier using the system DCE '
. 'Security provider; please provide a custom identifier or '
. 'use a different provider'
);
$provider->getGid();
}
/**
* @param mixed $value
*
* @runInSeparateProcess
* @preserveGlobalState disabled
* @dataProvider provideWindowsBadGroupValues
*/
public function testGetGidForWindowsThrowsExceptionWhenShellExecForWmicGroupGetReturnsBadValues($value): void
{
PHPMockery::mock(
'Ramsey\Uuid\Provider\Dce',
'ini_get'
)->with('disable_functions')->once()->andReturn('nothing');
PHPMockery::mock(
'Ramsey\Uuid\Provider\Dce',
'constant'
)->with('PHP_OS')->once()->andReturn('Windows_NT');
$shellExec = PHPMockery::mock('Ramsey\Uuid\Provider\Dce', 'shell_exec');
$shellExec
->with('net user %username% | findstr /b /i "Local Group Memberships"')
->once()
->andReturn('Local Group Memberships *Users');
$shellExec
->with("wmic group get name,sid | findstr /b /i 'Users'")
->once()
->andReturn($value);
$provider = new SystemDceSecurityProvider();
$this->expectException(DceSecurityException::class);
$this->expectExceptionMessage(
'Unable to get a group identifier using the system DCE '
. 'Security provider; please provide a custom identifier or '
. 'use a different provider'
);
$provider->getGid();
}
/**
* @runInSeparateProcess
* @preserveGlobalState disabled
* @dataProvider provideWindowsGoodNetUserAndWmicGroupValues
*/
public function testGetGidForWindowsSucceeds(
string $netUserResponse,
string $wmicGroupResponse,
string $expectedGroup,
string $expectedId
): void {
PHPMockery::mock(
'Ramsey\Uuid\Provider\Dce',
'ini_get'
)->with('disable_functions')->once()->andReturn('nothing');
PHPMockery::mock(
'Ramsey\Uuid\Provider\Dce',
'constant'
)->with('PHP_OS')->once()->andReturn('Windows_NT');
$shellExec = PHPMockery::mock('Ramsey\Uuid\Provider\Dce', 'shell_exec');
$shellExec
->with('net user %username% | findstr /b /i "Local Group Memberships"')
->once()
->andReturn($netUserResponse);
$shellExec
->with("wmic group get name,sid | findstr /b /i '{$expectedGroup}'")
->once()
->andReturn($wmicGroupResponse);
$provider = new SystemDceSecurityProvider();
$gid = $provider->getGid();
$this->assertInstanceOf(IntegerValue::class, $gid);
$this->assertSame($expectedId, $gid->toString());
$this->assertSame($gid, $provider->getGid());
}
/**
* @phpcsSuppress SlevomatCodingStandard.TypeHints.ReturnTypeHint.MissingTraversableTypeHintSpecification
*/
public function provideWindowsGoodNetUserAndWmicGroupValues(): array
{
return [
[
'netUserResponse' => 'Local Group Memberships *Administrators *Users',
'wmicGroupResponse' => 'Administrators S-1-5-32-544',
'expectedGroup' => 'Administrators',
'expectedId' => '544',
],
[
'netUserResponse' => 'Local Group Memberships Users',
'wmicGroupResponse' => 'Users S-1-5-32-545',
'expectedGroup' => 'Users',
'expectedId' => '545',
],
[
'netUserResponse' => 'Local Group Memberships Guests Nobody',
'wmicGroupResponse' => 'Guests S-1-5-32-546',
'expectedGroup' => 'Guests',
'expectedId' => '546',
],
[
'netUserReponse' => 'Local Group Memberships Some Group Another Group',
'wmicGroupResponse' => 'Some Group S-1-5-80-19088743-1985229328-4294967295-1324',
'expectedGroup' => 'Some Group',
'expectedId' => '1324',
],
];
}
/**
* @phpcsSuppress SlevomatCodingStandard.TypeHints.ReturnTypeHint.MissingTraversableTypeHintSpecification
*/
public function providePosixTestValues(): array
{
return [
['os' => 'Darwin', 'id' => '1042'],
['os' => 'FreeBSD', 'id' => '672'],
['os' => 'GNU', 'id' => '1008'],
['os' => 'Linux', 'id' => '567'],
['os' => 'NetBSD', 'id' => '7234'],
['os' => 'OpenBSD', 'id' => '2347'],
['os' => 'OS400', 'id' => '1234'],
];
}
/**
* @phpcsSuppress SlevomatCodingStandard.TypeHints.ReturnTypeHint.MissingTraversableTypeHintSpecification
*/
public function provideWindowsBadValues(): array
{
return [
['value' => null],
['value' => 'foobar'],
['value' => 'foo,bar,baz'],
['value' => ''],
['value' => '1234'],
['value' => 'Local Group Memberships'],
['value' => 'Local Group Memberships **** Foo'],
];
}
/**
* @phpcsSuppress SlevomatCodingStandard.TypeHints.ReturnTypeHint.MissingTraversableTypeHintSpecification
*/
public function provideWindowsBadGroupValues(): array
{
return array_merge(
$this->provideWindowsBadValues(),
[
['value' => 'Users Not a valid SID string'],
['value' => 'Users 344aab9758bb0d018b93739e7893fb3a'],
]
);
}
}
+2 -2
View File
@@ -12,11 +12,11 @@ use Ramsey\Uuid\Math\BrickMathCalculator;
use Ramsey\Uuid\Rfc4122\Fields;
use Ramsey\Uuid\Rfc4122\UuidBuilder;
use Ramsey\Uuid\Rfc4122\UuidV1;
use Ramsey\Uuid\Rfc4122\UuidV2;
use Ramsey\Uuid\Rfc4122\UuidV3;
use Ramsey\Uuid\Rfc4122\UuidV4;
use Ramsey\Uuid\Rfc4122\UuidV5;
use Ramsey\Uuid\Test\TestCase;
use Ramsey\Uuid\Uuid;
class UuidBuilderTest extends TestCase
{
@@ -57,7 +57,7 @@ class UuidBuilderTest extends TestCase
],
[
'uuid' => 'ff6f8cb0-c57d-21e1-9b21-0800200c9a66',
'expectedClass' => Uuid::class,
'expectedClass' => UuidV2::class,
'expectedVersion' => 2,
],
[
+25 -13
View File
@@ -4,15 +4,18 @@ declare(strict_types=1);
namespace Ramsey\Uuid\Test;
use Mockery;
use PHPUnit\Framework\MockObject\MockObject;
use Ramsey\Uuid\Builder\UuidBuilderInterface;
use Ramsey\Uuid\Codec\CodecInterface;
use Ramsey\Uuid\Converter\NumberConverterInterface;
use Ramsey\Uuid\FeatureSet;
use Ramsey\Uuid\Generator\DceSecurityGeneratorInterface;
use Ramsey\Uuid\Generator\RandomGeneratorInterface;
use Ramsey\Uuid\Generator\TimeGeneratorInterface;
use Ramsey\Uuid\Provider\NodeProviderInterface;
use Ramsey\Uuid\UuidFactory;
use Ramsey\Uuid\Validator\ValidatorInterface;
class UuidFactoryTest extends TestCase
{
@@ -49,37 +52,46 @@ class UuidFactoryTest extends TestCase
public function testGettersReturnValueFromFeatureSet(): void
{
$codec = $this->getMockBuilder(CodecInterface::class)->getMock();
$nodeProvider = $this->getMockBuilder(NodeProviderInterface::class)->getMock();
$randomGenerator = $this->getMockBuilder(RandomGeneratorInterface::class)->getMock();
$timeGenerator = $this->getMockBuilder(TimeGeneratorInterface::class)->getMock();
$codec = Mockery::mock(CodecInterface::class);
$nodeProvider = Mockery::mock(NodeProviderInterface::class);
$randomGenerator = Mockery::mock(RandomGeneratorInterface::class);
$timeGenerator = Mockery::mock(TimeGeneratorInterface::class);
$dceSecurityGenerator = Mockery::mock(DceSecurityGeneratorInterface::class);
$numberConverter = Mockery::mock(NumberConverterInterface::class);
$builder = Mockery::mock(UuidBuilderInterface::class);
$validator = Mockery::mock(ValidatorInterface::class);
$featureSet = $this->getMockBuilder(FeatureSet::class)->getMock();
$featureSet->method('getCodec')->willReturn($codec);
$featureSet->method('getNodeProvider')->willReturn($nodeProvider);
$featureSet->method('getRandomGenerator')->willReturn($randomGenerator);
$featureSet->method('getTimeGenerator')->willReturn($timeGenerator);
$featureSet = Mockery::mock(FeatureSet::class, [
'getCodec' => $codec,
'getNodeProvider' => $nodeProvider,
'getRandomGenerator' => $randomGenerator,
'getTimeGenerator' => $timeGenerator,
'getDceSecurityGenerator' => $dceSecurityGenerator,
'getNumberConverter' => $numberConverter,
'getBuilder' => $builder,
'getValidator' => $validator,
]);
$uuidFactory = new UuidFactory($featureSet);
$this->assertEquals(
$this->assertSame(
$codec,
$uuidFactory->getCodec(),
'getCodec did not return CodecInterface from FeatureSet'
);
$this->assertEquals(
$this->assertSame(
$nodeProvider,
$uuidFactory->getNodeProvider(),
'getNodeProvider did not return NodeProviderInterface from FeatureSet'
);
$this->assertEquals(
$this->assertSame(
$randomGenerator,
$uuidFactory->getRandomGenerator(),
'getRandomGenerator did not return RandomGeneratorInterface from FeatureSet'
);
$this->assertEquals(
$this->assertSame(
$timeGenerator,
$uuidFactory->getTimeGenerator(),
'getTimeGenerator did not return TimeGeneratorInterface from FeatureSet'