diff --git a/Annotation/Anonymize.php b/Annotation/Anonymize.php index 84dafd0..5280ffe 100644 --- a/Annotation/Anonymize.php +++ b/Annotation/Anonymize.php @@ -17,7 +17,6 @@ use Doctrine\Common\Annotations\Annotation; use Doctrine\Common\Annotations\Annotation\Attribute; use Doctrine\Common\Annotations\Annotation\Attributes; -use Doctrine\Common\Annotations\AnnotationException; /** * Annotation to flag an entity field to be anonymized @@ -33,54 +32,9 @@ */ class Anonymize { - /** - * Alias of TYPE_CONCAT - */ - const TYPE_STRING = 'string'; - - /** - * Anonymize a string to - - */ - const TYPE_CONCAT = 'concat'; - - /** - * Anonymize a field by emptying it - * Alias of TYPE_FIXED with an empty value. - */ - const TYPE_TRUNCATE = 'truncate'; - - /** - * Anonymize an IP field by setting the last bytes to 0 - * - * @see https://support.google.com/analytics/answer/2763052 - * - * Supports string fields only - */ - const TYPE_IP = 'ip'; - - /** - * Anonymize by providing a fixed value - * Requires the value property to be given - */ - const TYPE_FIXED = 'fixed'; - - /** - * Anonymize an email to "@localhost" - */ - const TYPE_EMAIL = 'email'; - /** * The type used to specify what kind of anonymizer should be used for the field * - * @Annotation\Enum({ - * Anonymize::TYPE_STRING, - * Anonymize::TYPE_IP, - * Anonymize::TYPE_FIXED, - * Anonymize::TYPE_EMAIL, - * Anonymize::TYPE_CONCAT, - * Anonymize::TYPE_TRUNCATE - * }) - * * @var string */ public $type; @@ -91,23 +45,4 @@ class Anonymize * @var string */ public $value; - - /** - * Performs a validation check according to the type given. - * If this type is 'Fixed' then a value has to be passed in as second argument - * - * @param array $arguments Array of arguments passed into the annotation - * - * @throws \Exception - * @throws AnnotationException - */ - public function __construct(array $arguments) - { - $this->type = $arguments['type']; - $this->value = $arguments['value']; - - if (self::TYPE_FIXED === $this->type && null === $this->value) { - throw new AnnotationException("'fixed' type requires 'value' property to be set."); - } - } } diff --git a/Anonymize/Anonymizer.php b/Anonymize/Anonymizer.php new file mode 100644 index 0000000..7d21551 --- /dev/null +++ b/Anonymize/Anonymizer.php @@ -0,0 +1,78 @@ + + * @copyright 2018 SuperBrave + * @license https://github.com/superbrave/gdpr-bundle/blob/master/LICENSE MIT + * @link https://www.superbrave.nl/ + */ + +namespace SuperBrave\GdprBundle\Anonymize; + +use SuperBrave\GdprBundle\Annotation\AnnotationReader; +use SuperBrave\GdprBundle\Annotation\Anonymize; +use InvalidArgumentException; +use ReflectionException; +use ReflectionClass; + +/** + * Class Anonymizer + * + * @package SuperBrave\GdprBundle\Anonymize + */ +class Anonymizer +{ + /** + * @var AnnotationReader + */ + private $annotationReader; + + /** + * @var PropertyAnonymizer + */ + private $propertyAnonymizer; + + /** + * Anonymizer constructor. + * + * @param AnnotationReader $annotationReader The annotation reader that should be used. + * @param PropertyAnonymizer $propertyAnonymizer The property anonymizer. + */ + public function __construct( + AnnotationReader $annotationReader, + PropertyAnonymizer $propertyAnonymizer + ) { + $this->annotationReader = $annotationReader; + $this->propertyAnonymizer = $propertyAnonymizer; + } + + /** + * Anonymizes the given object which should contain the @see Anonymize annotations. + * + * @param object $object The object to anonymize. + * + * @return void + * + * @throws InvalidArgumentException If argument supplied is not an object. + * @throws ReflectionException If class doesn't exist. + */ + public function anonymize(/*object */$object) + { + if (!is_object($object)) { + throw new InvalidArgumentException(sprintf( + 'Invalid argument given "%s" should be of type object.', + gettype($object) + )); + } + + $reflectionClass = new ReflectionClass($object); + $annotations = $this->annotationReader->getPropertiesWithAnnotation($reflectionClass, Anonymize::class); + + foreach ($annotations as $property => $annotation) { + $this->propertyAnonymizer->anonymizeField($object, $property, $annotation); + } + } +} diff --git a/Anonymize/AnonymizerCollection.php b/Anonymize/AnonymizerCollection.php new file mode 100644 index 0000000..f5e22a9 --- /dev/null +++ b/Anonymize/AnonymizerCollection.php @@ -0,0 +1,68 @@ + + * @copyright 2018 SuperBrave + * @license https://github.com/superbrave/gdpr-bundle/blob/master/LICENSE MIT + * @link https://www.superbrave.nl/ + */ + +namespace SuperBrave\GdprBundle\Anonymize; + +use LogicException; +use SuperBrave\GdprBundle\Anonymize\Type\AnonymizerInterface; + +/** + * Class AnonymizerCollection + * + * @package SuperBrave\GdprBundle\Anonymize + */ +class AnonymizerCollection +{ + /** + * Array of anonymizers + * + * @var AnonymizerInterface[] + */ + private $anonymizers = []; + + /** + * Adds an anonymizer to the collection. + * + * @param string $type The anonymizer type to be added. + * @param AnonymizerInterface $anonymizer The anonymizer class to be added. + * + * @return void + * + * @throws LogicException On duplicate anonymizer keys. + */ + public function addAnonymizer($type, $anonymizer) + { + if (array_key_exists($type, $this->anonymizers)) { + throw new LogicException(sprintf('Anonymizer %s already exists.', $type)); + } + + $this->anonymizers[$type] = $anonymizer; + } + + /** + * Get an anonymizer by its type from the collection. + * + * @param string $type The anonymizer type to be fetched. + * + * @return AnonymizerInterface + * + * @throws LogicException If the anonymizer type is not registered. + */ + public function getAnonymizer($type) + { + if (!array_key_exists($type, $this->anonymizers)) { + throw new LogicException(sprintf('Anonymizer %s is not registered.', $type)); + } + + return $this->anonymizers[$type]; + } +} diff --git a/Anonymize/PropertyAnonymizer.php b/Anonymize/PropertyAnonymizer.php new file mode 100644 index 0000000..c26b558 --- /dev/null +++ b/Anonymize/PropertyAnonymizer.php @@ -0,0 +1,74 @@ + + * @copyright 2018 SuperBrave + * @license https://github.com/superbrave/gdpr-bundle/blob/master/LICENSE MIT + * @link https://www.superbrave.nl/ + */ + +namespace SuperBrave\GdprBundle\Anonymize; + +use SuperBrave\GdprBundle\Annotation\Anonymize; +use SuperBrave\GdprBundle\Manipulator\PropertyManipulator; + +/** + * Class PropertyAnonymizer + * + * @package SuperBrave\GdprBundle\Anonymizer + */ +class PropertyAnonymizer +{ + /** + * Property manipulator service + * + * @var PropertyManipulator + */ + private $propertyManipulator; + + /** + * The collection of anonymizers + * + * @var AnonymizerCollection + */ + private $anonymizerCollection; + + /** + * Constructs the class given the parameters + * + * @param PropertyManipulator $propertyManipulator The PropertyManipulator class used to get the property value + * @param AnonymizerCollection $anonymizerCollection A collection of anonymizers registered by the compiler pass + */ + public function __construct(PropertyManipulator $propertyManipulator, AnonymizerCollection $anonymizerCollection) + { + $this->propertyManipulator = $propertyManipulator; + $this->anonymizerCollection = $anonymizerCollection; + } + + /** + * Anonymize the property the annotation is on. + * Takes into account the type specified in the annotation + * + * @param object $object The owner of the property being anonymized + * @param string $property The property being anonymized + * @param Anonymize $annotation The annotation gotten from the object + * + * @return void + */ + public function anonymizeField($object, $property, Anonymize $annotation) + { + $anonymizer = $this->anonymizerCollection->getAnonymizer($annotation->type); + + $propertyValue = $this->propertyManipulator->getPropertyValue($object, $property); + + $newPropertyValue = $anonymizer->anonymize($propertyValue, array( + 'annotationValue' => $annotation->value, + 'object' => $object, + )); + + $this->propertyManipulator->setPropertyValue($object, $property, $newPropertyValue); + } +} diff --git a/Anonymize/Type/AnonymizerInterface.php b/Anonymize/Type/AnonymizerInterface.php new file mode 100644 index 0000000..a9f8544 --- /dev/null +++ b/Anonymize/Type/AnonymizerInterface.php @@ -0,0 +1,31 @@ + + * @copyright 2018 SuperBrave + * @license https://github.com/superbrave/gdpr-bundle/blob/master/LICENSE MIT + * @link https://www.superbrave.nl/ + */ + +namespace SuperBrave\GdprBundle\Anonymize\Type; + +/** + * Interface AnonymizerInterface + * + * @package SuperBrave\GdprBundle\Anonymize\Type + */ +interface AnonymizerInterface +{ + /** + * Anonymizes the given property value. + * + * @param mixed $propertyValue The value of the property. + * @param array $options The options for the anonymizer. + * + * @return mixed + */ + public function anonymize($propertyValue, array $options = []); +} diff --git a/Anonymize/Type/ArrayAnonymizer.php b/Anonymize/Type/ArrayAnonymizer.php new file mode 100644 index 0000000..89fa0ba --- /dev/null +++ b/Anonymize/Type/ArrayAnonymizer.php @@ -0,0 +1,58 @@ + + * @copyright 2018 SuperBrave + * @license https://github.com/superbrave/gdpr-bundle/blob/master/LICENSE MIT + * @link https://www.superbrave.nl/ + */ + +namespace SuperBrave\GdprBundle\Anonymize\Type; + +/** + * Class ArrayAnonymizer + * + * @package SuperBrave\GdprBundle\Anonymize\Type + */ +class ArrayAnonymizer implements AnonymizerInterface +{ + /** + * @var AnonymizerInterface + */ + private $anonymizer; + + /** + * ArrayAnonymizer constructor. + * + * @param AnonymizerInterface $anonymizer The anonymizer. + */ + public function __construct(AnonymizerInterface $anonymizer) + { + $this->anonymizer = $anonymizer; + } + + /** + * {@inheritdoc} + * + * @throws \InvalidArgumentException If property is not iterable. + */ + public function anonymize($propertyValue, array $options = []) + { + if (!is_iterable($propertyValue)) { + throw new \InvalidArgumentException(sprintf( + 'Invalid argument given; "%s" for class "%s" should be of type iterable.', + gettype($propertyValue), + __CLASS__ + )); + } + + foreach ($propertyValue as $index => $value) { + $propertyValue[$index] = $this->anonymizer->anonymize($value, $options); + } + + return $propertyValue; + } +} diff --git a/Anonymize/Type/DateTimeAnonymizer.php b/Anonymize/Type/DateTimeAnonymizer.php new file mode 100644 index 0000000..2b31b1d --- /dev/null +++ b/Anonymize/Type/DateTimeAnonymizer.php @@ -0,0 +1,138 @@ + + * @copyright 2018 SuperBrave + * @license https://github.com/superbrave/gdpr-bundle/blob/master/LICENSE MIT + * @link https://www.superbrave.nl/ + */ + +namespace SuperBrave\GdprBundle\Anonymize\Type; + +/** + * DATE_RFC7231 is not natively supported in PHP 5.6 + * + * @deprecated since PHP 7.0.19 + */ +defined('DATE_RFC7231') or define('DATE_RFC7231', "D, d M Y H:i:s \G\M\T"); + +/** + * DateTime anonymizer class + * + * @package SuperBrave\GdprBundle\Anonymize\Type + */ +class DateTimeAnonymizer implements AnonymizerInterface +{ + /** + * Array with supported string formats + * + * Currently supported string formats are the ATOM, W3C, RSS, COOKIE, RFC822, RFC850, RFC1036, RFC1123, RFC2822, + * RFC3339, RFC7231 and ISO8601 formats. + * + * Constants DATE_ATOM and DATE_W3C are the same as DATE_RFC3339; + * constants DATE_RFC1036, DATE_RFC1123, DATE_RFC2822 and DATE_RSS are the same as DATE_RFC822; + * constant DATE_COOKIE is the same as DATE_RFC850 + * so they're not added in this array + * + * @see http://php.net/manual/en/class.datetime.php#datetime.constants.types + * @var array + */ + private $stringFormats = [ + // PHP predefined standards + DATE_ISO8601 => '/^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}[+-]{1}[0-9]{4}$/', + DATE_RFC822 => '/^[a-z]{3}, [0-9]{2} [a-z]{3} [0-9]{2} [0-9]{2}:[0-9]{2}:[0-9]{2} [+-]{1}[0-9]{4}$/i', + DATE_RFC850 => '/^[a-z]{4,}, [0-9]{2}-[a-z]{3}-[0-9]{2} [0-9]{2}:[0-9]{2}:[0-9]{2} [a-z]{1,}$/i', + DATE_RFC7231 => '/^[a-z]{3}, [0-9]{2} [a-z]{3} [0-9]{4} [0-9]{2}:[0-9]{2}:[0-9]{2} GMT$/i', + DATE_RFC3339 => '/^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}[+-]{1}[0-9]{2}:[0-9]{2}$/', + // Variants on ISO8601 which are used by different database storage designs + 'Y-m-d' => '/^[0-9]{4}-[0-9]{2}-[0-9]{2}$/', + 'Y-m-d H:i:s' => '/^[0-9]{4}-[0-9]{2}-[0-9]{2} [0-9]{2}:[0-9]{2}:[0-9]{2}$/', + 'Y-m-d\TH:i:s' => '/^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}$/', + ]; + + /** + * Anonymizes a DateTime by setting month and day to 01 and hours, minutes and seconds to 00. + * + * The return value will be of the same type as $propertyValue. + * Actual anonimizing will happen in {@see DateTimeAnonymizer::anonymizeByDateTime()}; string and int are converted. + * Supported string formats are documented in {@see DateTimeAnonymizer::$stringFormats} + * + * @param \DateTimeInterface|int|string $propertyValue The value that has to be converted + * @param array $options Options to help the anonymizer do its job + * + * @return \DateTimeInterface|int|string The anonymized result + * + * @throws \InvalidArgumentException When the $propertyValue is invalid, an exception will be thrown + */ + public function anonymize($propertyValue, array $options = []) + { + // Regular DateTime object + if ($propertyValue instanceof \DateTimeInterface) { + return $this->anonymizeByDateTime($propertyValue); + } + + // Unix timestamp + if (is_numeric($propertyValue)) { + $dateTime = new \DateTime(date('Y-m-d H:i:s', $propertyValue)); + return $this->anonymizeByDateTime($dateTime)->getTimestamp(); + } + + // String date + if (is_string($propertyValue)) { + $result = $this->anonymizeByString($propertyValue); + if ($result !== false) { + return $result; + } + } + + throw new \InvalidArgumentException("Invalid format for \$propertyValue in ".__CLASS__."::".__METHOD__); + } + + /** + * Anonymize a DateTime object by setting day and month to 1, hours, minutes and seconds to 0. + * + * @param \DateTimeInterface $dateTime Original DateTime object + * + * @return \DateTimeInterface + */ + private function anonymizeByDateTime(\DateTimeInterface $dateTime) + { + if (is_a($dateTime, \DateTimeImmutable::class)) { + // Immutable doesn't modify the current object but returns a new object instead. + // This looks a bit like the singleton pattern but isn't really the same. + return $dateTime->setDate($dateTime->format('Y'), 1, 1)->setTime(0, 0, 0); + } else { + // For a DateTime object, clone the object, instead of modifying the existing DateTime object. + $result = clone $dateTime; + $result->setDate($dateTime->format('Y'), 1, 1); + $result->setTime(0, 0, 0); + return $result; + } + } + + /** + * Anonymize a DateTime string by setting day and month to 1, hours, minutes and seconds to 0. + * + * Supported string formats are documented in {@see DateTimeAnonymizer::$stringFormats} + * + * @param string $dateTime The date/time as string + * + * @return string|boolean False on failure + */ + private function anonymizeByString($dateTime) + { + foreach ($this->stringFormats as $dateFormat => $regexTest) { + if (!preg_match($regexTest, $dateTime)) { + continue; + } + $value = new \DateTime($dateTime); + return $this->anonymizeByDateTime($value)->format($dateFormat); + } + + // No regex matches the string; unknown datetime format + return false; + } +} diff --git a/Anonymize/Type/FixedAnonymizer.php b/Anonymize/Type/FixedAnonymizer.php new file mode 100644 index 0000000..0386589 --- /dev/null +++ b/Anonymize/Type/FixedAnonymizer.php @@ -0,0 +1,93 @@ + + * @copyright 2018 SuperBrave + * @license https://github.com/superbrave/gdpr-bundle/blob/master/LICENSE MIT + * @link https://www.superbrave.nl/ + */ + +namespace SuperBrave\GdprBundle\Anonymize\Type; + +use SuperBrave\GdprBundle\Manipulator\PropertyManipulator; +use Symfony\Component\OptionsResolver\OptionsResolver; + +/** + * Fixed anonymizer class + * + * @package SuperBrave\GdprBundle\Anonymize\Type + */ +class FixedAnonymizer implements AnonymizerInterface +{ + /** + * Property Manipulator service + * + * @var PropertyManipulator + */ + private $propertyManipulator; + + /** + * FixedAnonymizer constructor. + * + * @param PropertyManipulator $propertyManipulator Used to get the value of a property + */ + public function __construct(PropertyManipulator $propertyManipulator) + { + $this->propertyManipulator = $propertyManipulator; + } + + /** + * Anonymize the data given according to the options provided + * The value is required in the annotation for this anonymizer + * + * You can specify a property you want to be resolved using a wildcard manner + * + * example: + * 'my-fixed-value-{id}' would become 'my-fixed-value-10' + * when the id property on the object returns 10 + * + * @param mixed $propertyValue The value that has to be converted + * @param array $options Options to help the anonymizer do its job + * + * @return string The Anonymized string + * + * @throws \InvalidArgumentException if annotationValue is empty. + */ + public function anonymize($propertyValue, array $options = []) + { + $resolver = new OptionsResolver(); + $this->configureOptions($resolver); + $options = $resolver->resolve($options); + + if (null === $options['annotationValue']) { + throw new \InvalidArgumentException('The option "annotationValue" cannot be empty'); + } + + $annotationValue = $options['annotationValue']; + + $matches = []; + if (preg_match_all('/{(.*?)}/', $options['annotationValue'], $matches)) { + foreach ((array)$matches[1] as $key => $property) { + $wildcardValue = $this->propertyManipulator->getPropertyValue($options['object'], $property); + $annotationValue = str_replace($matches[0][$key], $wildcardValue, $annotationValue); + } + } + + return $annotationValue; + } + + /** + * Configures the options for this anonymizer. + * + * @param OptionsResolver $resolver The resolver for the options. + * + * @return void + */ + private function configureOptions(OptionsResolver $resolver) + { + $resolver->setRequired(['annotationValue', 'object']); + } +} diff --git a/Anonymize/Type/IpAnonymizer.php b/Anonymize/Type/IpAnonymizer.php new file mode 100644 index 0000000..fb5c64d --- /dev/null +++ b/Anonymize/Type/IpAnonymizer.php @@ -0,0 +1,104 @@ + + * @copyright 2018 SuperBrave + * @license https://github.com/superbrave/gdpr-bundle/blob/master/LICENSE MIT + * @link https://www.superbrave.nl/ + */ + +namespace SuperBrave\GdprBundle\Anonymize\Type; + +use InvalidArgumentException; + +/** + * Class IpAnonymizer + * + * @package SuperBrave\GdprBundle\Anonymize\Type + */ +class IpAnonymizer implements AnonymizerInterface +{ + /** + * The mask used for IPv4 + * + * @var string + */ + private $ipv4Mask; + + /** + * The mask used for IPv6 + * + * @var string + */ + private $ipv6Mask; + + /** + * IpAnonymizer constructor. + * + * @param string $ipv4Mask The mask that should be used for IPv4 + * @param string $ipv6Mask The mask that should be user for IPv6 + */ + public function __construct($ipv4Mask = '255.255.255.0', $ipv6Mask = 'ffff:ffff:ffff::') + { + $this->ipv4Mask = $ipv4Mask; + $this->ipv6Mask = $ipv6Mask; + } + + /** + * {@inheritdoc} + */ + public function anonymize($propertyValue, array $options = []) + { + if (is_numeric($propertyValue)) { + $propertyValue = long2ip($propertyValue); + + return ip2long($this->anonymizeIpAddress($propertyValue, $this->getIpMask($propertyValue))); + } + + return $this->anonymizeIpAddress($propertyValue, $this->getIpMask($propertyValue)); + } + + /** + * Checks if the address is a valid ipv4 or ipv6 string, and returns the appropriate mask + * + * @param string $address The IP address being checked + * + * @return string + * + * @throws \InvalidArgumentException if the address is not a valid ipv4 or ipv6 address. + */ + private function getIpMask($address) + { + $ipv4 = filter_var($address, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4); + $ipv6 = filter_var($address, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6); + + if (($ipv4 === false) && ($ipv6 === false)) { + throw new InvalidArgumentException(sprintf('%s is not a valid ipv4 or ipv6 address', $address)); + } + + return $ipv4 ? $this->ipv4Mask : $this->ipv6Mask; + } + + /** + * Anonymize an ip adress + * + * @param string $address The IP being anonymized + * @param string $mask The Mask being used + * + * @return string + */ + private function anonymizeIpAddress($address, $mask) + { + $maskBytes = unpack('C*', inet_pton($mask)); + $addressBytes = unpack('C*', inet_pton($address)); + $anonymizedAddress = ''; + foreach ($addressBytes as $index => $byte) { + $anonymizedAddress .= chr($byte & $maskBytes[$index]); + } + + return inet_ntop($anonymizedAddress); + } +} diff --git a/Anonymize/Type/NullAnonymizer.php b/Anonymize/Type/NullAnonymizer.php new file mode 100644 index 0000000..52aa77f --- /dev/null +++ b/Anonymize/Type/NullAnonymizer.php @@ -0,0 +1,29 @@ + + * @copyright 2018 SuperBrave + * @license https://github.com/superbrave/gdpr-bundle/blob/master/LICENSE MIT + * @link https://www.superbrave.nl/ + */ + +namespace SuperBrave\GdprBundle\Anonymize\Type; + +/** + * Class NullAnonymizer + * + * @package SuperBrave\GdprBundle\Anonymize\Type + */ +class NullAnonymizer implements AnonymizerInterface +{ + /** + * {@inheritdoc} + */ + public function anonymize($propertyValue, array $options = []) + { + return null; + } +} diff --git a/Anonymize/Type/ObjectAnonymizer.php b/Anonymize/Type/ObjectAnonymizer.php new file mode 100644 index 0000000..74b0c62 --- /dev/null +++ b/Anonymize/Type/ObjectAnonymizer.php @@ -0,0 +1,59 @@ + + * @copyright 2018 SuperBrave + * @license https://github.com/superbrave/gdpr-bundle/blob/master/LICENSE MIT + * @link https://www.superbrave.nl/ + */ + +namespace SuperBrave\GdprBundle\Anonymize\Type; + +use SuperBrave\GdprBundle\Anonymize\Anonymizer; + +/** + * Class ObjectAnonymizer + * + * @package SuperBrave\GdprBundle\Anonymize\Type + */ +class ObjectAnonymizer implements AnonymizerInterface +{ + /** + * @var Anonymizer + */ + private $anonymizer; + + /** + * ObjectAnonymizer constructor. + * + * @param Anonymizer $anonymizer The anonymizer. + */ + public function __construct(Anonymizer $anonymizer) + { + $this->anonymizer = $anonymizer; + } + + /** + * {@inheritdoc} + * + * @throws \InvalidArgumentException if propertyValue is not an object. + * @throws \ReflectionException if class does't exist. + */ + public function anonymize($propertyValue, array $options = []) + { + if (!is_object($propertyValue)) { + throw new \InvalidArgumentException(sprintf( + 'Invalid argument given; "%s" for class "%s" should be of type object.', + gettype($propertyValue), + __CLASS__ + )); + } + + $this->anonymizer->anonymize($propertyValue); + + return $propertyValue; + } +} diff --git a/DependencyInjection/Compiler/AddAnonymizersCompilerPass.php b/DependencyInjection/Compiler/AddAnonymizersCompilerPass.php new file mode 100644 index 0000000..597782f --- /dev/null +++ b/DependencyInjection/Compiler/AddAnonymizersCompilerPass.php @@ -0,0 +1,67 @@ + + * @copyright 2018 SuperBrave + * @license https://github.com/superbrave/gdpr-bundle/blob/master/LICENSE MIT + * @link https://www.superbrave.nl/ + */ + +namespace SuperBrave\GdprBundle\DependencyInjection\Compiler; + +use LogicException; +use SuperBrave\GdprBundle\Anonymize\Type\AnonymizerInterface; +use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Reference; + +/** + * Compiler pass to add the tagged anonymizers to the anonymizer manager's definition + * + * @package SuperBrave\GdprBundle\DependencyInjection\Compiler + */ +class AddAnonymizersCompilerPass implements CompilerPassInterface +{ + /** + * Gets all the anonymizers from the services tagged with 'superbrave_gdpr.anonymizer' + * The adds them to the AnonymizerCollection + * + * @param ContainerBuilder $container The service container + * + * @return void + * + * @throws LogicException on invalid interface usage. + */ + public function process(ContainerBuilder $container) + { + if ($container->hasDefinition('super_brave_gdpr.anonymizer_collection') === false) { + return; + } + + $anonymizerManagerDefinition = $container->getDefinition('super_brave_gdpr.anonymizer_collection'); + + $anonymizers = $container->findTaggedServiceIds('super_brave_gdpr.anonymizer'); + foreach ($anonymizers as $anonymizer => $attributes) { + $type = $attributes[0]['type']; + + $definition = $container->getDefinition($anonymizer); + + //validate class interface + $class = $container->getParameterBag()->resolveValue($definition->getClass()); + if (is_subclass_of($class, AnonymizerInterface::class) === false) { + throw new LogicException( + sprintf( + '%s should implement the %s interface when used as anonymizer.', + $class, + AnonymizerInterface::class + ) + ); + } + + $anonymizerManagerDefinition->addMethodCall('addAnonymizer', [$type, new Reference($anonymizer)]); + } + } +} diff --git a/DependencyInjection/SuperBraveGdprExtension.php b/DependencyInjection/SuperBraveGdprExtension.php index b977dcd..73c8990 100644 --- a/DependencyInjection/SuperBraveGdprExtension.php +++ b/DependencyInjection/SuperBraveGdprExtension.php @@ -33,7 +33,8 @@ class SuperBraveGdprExtension extends Extension * @param ContainerBuilder $container A DI container that provides an API to easily describe services * * @return void - * @throws \InvalidArgumentException When provided tag is not defined in this extension + * + * @throws \InvalidArgumentException When provided tag is not defined in this extension. */ public function load(array $configs, ContainerBuilder $container) { @@ -42,5 +43,6 @@ public function load(array $configs, ContainerBuilder $container) $loader = new Loader\YamlFileLoader($container, new FileLocator(__DIR__.'/../Resources/config')); $loader->load('services.yml'); + $loader->load('services.anonymizer.types.yml'); } } diff --git a/Manipulator/PropertyManipulator.php b/Manipulator/PropertyManipulator.php new file mode 100644 index 0000000..a91a24f --- /dev/null +++ b/Manipulator/PropertyManipulator.php @@ -0,0 +1,99 @@ + + * @copyright 2018 SuperBrave + * @license https://github.com/superbrave/gdpr-bundle/blob/master/LICENSE MIT + * @link https://www.superbrave.nl/ + */ + +namespace SuperBrave\GdprBundle\Manipulator; + +use ReflectionProperty; +use Symfony\Component\PropertyAccess\Exception\NoSuchPropertyException; +use Symfony\Component\PropertyAccess\PropertyAccessorInterface; + +/** + * Class PropertyManipulator + * + * @package SuperBrave\GdprBundle\Manipulator + */ +class PropertyManipulator +{ + /** + * @var PropertyAccessorInterface + */ + private $propertyAccessor; + + /** + * Constructs a new PropertyManipulator instance. + * + * @param PropertyAccessorInterface $propertyAccessor The property accessor instance + */ + public function __construct(PropertyAccessorInterface $propertyAccessor) + { + $this->propertyAccessor = $propertyAccessor; + } + + /** + * Returns the value of specified property + * + * @param object $object The object containing the property + * @param string $propertyName The property name where the value is taken from + * + * @return mixed + * + * @throws \InvalidArgumentException if property does not exist. + */ + public function getPropertyValue($object, $propertyName) + { + try { + $propertyData = $this->propertyAccessor->getValue($object, $propertyName); + } catch (NoSuchPropertyException $exception) { + try { + $reflectionProperty = new ReflectionProperty($object, $propertyName); + $reflectionProperty->setAccessible(true); + + $propertyData = $reflectionProperty->getValue($object); + } catch (\Exception $e) { + throw new \InvalidArgumentException( + sprintf('The property "$%s" does not exist on class "%s"', $propertyName, get_class($object)) + ); + } + } + + return $propertyData; + } + + /** + * Sets a new value on the specified property + * + * @param object $object The object containing the property + * @param string $propertyName The property name where the value is written to + * @param mixed $value The new value for the property + * + * @return void + * + * @throws \InvalidArgumentException if property does not exist. + */ + public function setPropertyValue($object, $propertyName, $value) + { + try { + $this->propertyAccessor->setValue($object, $propertyName, $value); + } catch (NoSuchPropertyException $exception) { + try { + $reflectionProperty = new ReflectionProperty($object, $propertyName); + $reflectionProperty->setAccessible(true); + + $reflectionProperty->setValue($object, $value); + } catch (\Exception $e) { + throw new \InvalidArgumentException( + sprintf('The property "$%s" does not exist on class "%s"', $propertyName, get_class($object)) + ); + } + } + } +} diff --git a/Resources/config/services.anonymizer.types.yml b/Resources/config/services.anonymizer.types.yml new file mode 100644 index 0000000..f9b9b3a --- /dev/null +++ b/Resources/config/services.anonymizer.types.yml @@ -0,0 +1,39 @@ +services: + _defaults: + public: false + + super_brave_gdpr.fixed_anonymizer: + class: SuperBrave\GdprBundle\Anonymize\Type\FixedAnonymizer + arguments: + - "@super_brave_gdpr.property_manipulator" + tags: + - { name: super_brave_gdpr.anonymizer, type: fixed } + + super_brave_gdpr.date_time_anonymizer: + class: SuperBrave\GdprBundle\Anonymize\Type\DateTimeAnonymizer + tags: + - { name: super_brave_gdpr.anonymizer, type: dateTime } + + super_brave_gdpr.ip_anonymizer: + class: SuperBrave\GdprBundle\Anonymize\Type\IpAnonymizer + tags: + - { name: super_brave_gdpr.anonymizer, type: ip } + + super_brave_gdpr.object_anonymizer: + class: SuperBrave\GdprBundle\Anonymize\Type\ObjectAnonymizer + arguments: + - "@super_brave_gdpr.anonymizer" + tags: + - { name: super_brave_gdpr.anonymizer, type: object } + + super_brave_gdpr.collection_anonymizer: + class: SuperBrave\GdprBundle\Anonymize\Type\ArrayAnonymizer + arguments: + - "@super_brave_gdpr.object_anonymizer" + tags: + - { name: super_brave_gdpr.anonymizer, type: collection } + + super_brave_gdpr.null_anonymizer: + class: SuperBrave\GdprBundle\Anonymize\Type\NullAnonymizer + tags: + - { name: super_brave_gdpr.anonymizer, type: "null" } diff --git a/Resources/config/services.yml b/Resources/config/services.yml index 8249d7d..634c8d5 100644 --- a/Resources/config/services.yml +++ b/Resources/config/services.yml @@ -12,6 +12,13 @@ services: - "xml" public: true + super_brave_gdpr.anonymizer: + class: SuperBrave\GdprBundle\Anonymize\Anonymizer + arguments: + - "@super_brave_gdpr.annotation.reader" + - "@super_brave_gdpr.property_anonymizer" + public: true + super_brave_gdpr.exporter.serializer: class: Symfony\Component\Serializer\Serializer factory: ["SuperBrave\\GdprBundle\\DependencyInjection\\ObjectFactory", "createSerializer"] @@ -24,7 +31,7 @@ services: arguments: - "@super_brave_gdpr.annotation.reader" - "SuperBrave\\GdprBundle\\Annotation\\Export" - - "@super_brave_gdpr.exporter.property_accessor" + - "@super_brave_gdpr.property_manipulator" tags: - { name: "super_brave_gdpr.serializer.normalizer" } @@ -38,5 +45,20 @@ services: tags: - { name: "super_brave_gdpr.serializer.encoder" } - super_brave_gdpr.exporter.property_accessor: + super_brave_gdpr.property_accessor: class: Symfony\Component\PropertyAccess\PropertyAccessor + + super_brave_gdpr.property_manipulator: + class: SuperBrave\GdprBundle\Manipulator\PropertyManipulator + arguments: + - "@super_brave_gdpr.property_accessor" + + super_brave_gdpr.anonymizer_collection: + class: SuperBrave\GdprBundle\Anonymize\AnonymizerCollection + public: true + + super_brave_gdpr.property_anonymizer: + class: SuperBrave\GdprBundle\Anonymize\PropertyAnonymizer + arguments: + - "@super_brave_gdpr.property_manipulator" + - "@super_brave_gdpr.anonymizer_collection" diff --git a/Serializer/Normalizer/AnnotationNormalizer.php b/Serializer/Normalizer/AnnotationNormalizer.php index 2859316..40b35a1 100644 --- a/Serializer/Normalizer/AnnotationNormalizer.php +++ b/Serializer/Normalizer/AnnotationNormalizer.php @@ -13,9 +13,8 @@ namespace SuperBrave\GdprBundle\Serializer\Normalizer; use ReflectionClass; -use ReflectionProperty; use SuperBrave\GdprBundle\Annotation\AnnotationReader; -use Symfony\Component\PropertyAccess\Exception\NoSuchPropertyException; +use SuperBrave\GdprBundle\Manipulator\PropertyManipulator; use Symfony\Component\PropertyAccess\PropertyAccessorInterface; use Symfony\Component\Serializer\Normalizer\NormalizerInterface; @@ -45,23 +44,23 @@ class AnnotationNormalizer implements NormalizerInterface * * @var PropertyAccessorInterface */ - private $propertyAccessor; + private $propertyManipulator; /** * Constructs a new AnnotationNormalizer instance. * - * @param AnnotationReader $annotationReader The AnnotationReader instance - * @param string $annotationName The FQCN of the annotation class - * @param PropertyAccessorInterface $propertyAccessor The property accessor instance + * @param AnnotationReader $annotationReader The AnnotationReader instance + * @param string $annotationName The FQCN of the annotation class + * @param PropertyManipulator $propertyManipulator The property accessor instance */ public function __construct( AnnotationReader $annotationReader, $annotationName, - PropertyAccessorInterface $propertyAccessor + PropertyManipulator $propertyManipulator ) { $this->annotationReader = $annotationReader; $this->annotationName = $annotationName; - $this->propertyAccessor = $propertyAccessor; + $this->propertyManipulator = $propertyManipulator; } /** @@ -104,7 +103,7 @@ public function normalize($object, $format = null, array $context = array()) ); foreach ($propertyAnnotations as $propertyName => $propertyAnnotation) { - $propertyValue = $this->getPropertyValue($object, $propertyName); + $propertyValue = $this->propertyManipulator->getPropertyValue($object, $propertyName); if (property_exists($propertyAnnotation, 'alias') && isset($propertyAnnotation->alias)) { $propertyName = $propertyAnnotation->alias; } @@ -115,28 +114,6 @@ public function normalize($object, $format = null, array $context = array()) return $normalizedData; } - /** - * Returns the value of specified property through the getter of the object. - * - * @param object $object The object being normalized - * @param string $propertyName The property name where the value gotten from - * - * @return mixed - */ - private function getPropertyValue($object, $propertyName) - { - try { - $propertyData = $this->propertyAccessor->getValue($object, $propertyName); - } catch (NoSuchPropertyException $exception) { - $reflectionProperty = new ReflectionProperty($object, $propertyName); - $reflectionProperty->setAccessible(true); - - $propertyData = $reflectionProperty->getValue($object); - } - - return $propertyData; - } - /** * Returns the mapped value of a property or the original value when no value map is configured on the annotation. * diff --git a/SuperBraveGdprBundle.php b/SuperBraveGdprBundle.php index 6922762..3339aa9 100644 --- a/SuperBraveGdprBundle.php +++ b/SuperBraveGdprBundle.php @@ -14,6 +14,8 @@ namespace SuperBrave\GdprBundle; +use SuperBrave\GdprBundle\DependencyInjection\Compiler\AddAnonymizersCompilerPass; +use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\HttpKernel\Bundle\Bundle; /** @@ -21,4 +23,17 @@ */ class SuperBraveGdprBundle extends Bundle { + /** + * Build the bundle and dependencies + * + * @param ContainerBuilder $container The service container + * + * @return void + */ + public function build(ContainerBuilder $container) + { + parent::build($container); + + $container->addCompilerPass(new AddAnonymizersCompilerPass()); + } } diff --git a/Tests/AnnotatedMock.php b/Tests/AnnotatedMock.php index 20d2c7b..b3545ff 100644 --- a/Tests/AnnotatedMock.php +++ b/Tests/AnnotatedMock.php @@ -26,6 +26,7 @@ class AnnotatedMock * The foo property. * * @GDPR\Export() + * @GDPR\Anonymize(type="fixed", value="foo") * * @var string */ diff --git a/Tests/Annotation/AnnotationReaderTest.php b/Tests/Annotation/AnnotationReaderTest.php index 97e2d98..ad26b2e 100644 --- a/Tests/Annotation/AnnotationReaderTest.php +++ b/Tests/Annotation/AnnotationReaderTest.php @@ -15,6 +15,7 @@ use PHPUnit_Framework_TestCase; use ReflectionClass; use SuperBrave\GdprBundle\Annotation\AnnotationReader; +use SuperBrave\GdprBundle\Annotation\Anonymize; use SuperBrave\GdprBundle\Annotation\Export; use SuperBrave\GdprBundle\Tests\AnnotatedMock; use SuperBrave\GdprBundle\Tests\ExtendedAnnotedMock; @@ -46,14 +47,16 @@ public function setUp() // used in the AnnotationReader does not use the existing classloaders. // Only the AnnotationRegistry classloader. class_exists(Export::class); + class_exists(Anonymize::class); } /** - * Tests if AnnotationReader::getPropertiesWithAnnotation returns a keyed array with the annotation instances. + * Tests if AnnotationReader::getPropertiesWithAnnotation returns a keyed array with the annotation export + * instances. * * @return void */ - public function testGetPropertiesWithAnnotation() + public function testGetPropertiesWithAnnotationExport() { $result = $this->annotationReader->getPropertiesWithAnnotation( new ReflectionClass(AnnotatedMock::class), @@ -69,6 +72,28 @@ public function testGetPropertiesWithAnnotation() $this->assertInstanceOf(Export::class, current($result)); } + /** + * Tests if AnnotationReader::getPropertiesWithAnnotation returns a keyed array with the annotation anonymize + * instances. + * + * @return void + */ + public function testGetPropertiesWithAnnotationAnonymize() + { + $result = $this->annotationReader->getPropertiesWithAnnotation( + new ReflectionClass(AnnotatedMock::class), + Anonymize::class + ); + + $this->assertInternalType('array', $result); + $this->assertCount(1, $result); + $this->assertSame( + array('foo'), + array_keys($result) + ); + $this->assertInstanceOf(Anonymize::class, current($result)); + } + /** * Tests if AnnotationReader::getPropertiesWithAnnotation returns a keyed array with the annotation instances * of both the class and the parent class. diff --git a/Tests/Anonymizer/AnonymizerTest.php b/Tests/Anonymizer/AnonymizerTest.php new file mode 100644 index 0000000..c1bc185 --- /dev/null +++ b/Tests/Anonymizer/AnonymizerTest.php @@ -0,0 +1,119 @@ + + * @copyright 2018 SuperBrave + * @license https://github.com/superbrave/gdpr-bundle/blob/master/LICENSE MIT + * @link https://www.superbrave.nl/ + */ + +namespace SuperBrave\GdprBundle\Tests\Anonymizer; + +use PHPUnit_Framework_MockObject_MockObject; +use PHPUnit_Framework_TestCase; +use SuperBrave\GdprBundle\Annotation\AnnotationReader; +use SuperBrave\GdprBundle\Annotation\Anonymize; +use SuperBrave\GdprBundle\Anonymize\Anonymizer; +use SuperBrave\GdprBundle\Anonymize\PropertyAnonymizer; +use SuperBrave\GdprBundle\Tests\AnnotatedMock; + +/** + * Class AnonymizerTest + * + * @package SuperBrave\GdprBundle\Tests\Anonymizer + */ +class AnonymizerTest extends PHPUnit_Framework_TestCase +{ + /** + * @var Anonymizer + */ + private $anonymizer; + + /** + * @var PHPUnit_Framework_MockObject_MockObject + */ + private $annotationReaderMock; + + /** + * @var PHPUnit_Framework_MockObject_MockObject + */ + private $propertyAnonymizer; + + /** + * {@inheritdoc} + */ + public function setUp() + { + $this->annotationReaderMock = $this->getMockBuilder(AnnotationReader::class) + ->getMock(); + + $this->propertyAnonymizer = $this->getMockBuilder(PropertyAnonymizer::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->anonymizer = new Anonymizer( + $this->annotationReaderMock, + $this->propertyAnonymizer + ); + } + + /** + * Tests if constructing a new Anonymizer instance sets the instance properties. + * + * @return void + */ + public function testConstruct() + { + $this->assertAttributeSame($this->annotationReaderMock, 'annotationReader', $this->anonymizer); + $this->assertAttributeSame($this->propertyAnonymizer, 'propertyAnonymizer', $this->anonymizer); + } + + /** + * Tests if Anonymizer::anonymize calls the annotation reader instance and property anonymizer instance. + * + * @return void + */ + public function testAnonymizeObject() + { + $annotatedMock = new AnnotatedMock(); + $anonymize = new Anonymize(); + $anonymize->type = 'fixed'; + $anonymize->value = 'foo'; + + $this->annotationReaderMock->expects($this->once()) + ->method('getPropertiesWithAnnotation') + ->with( + (new \ReflectionClass($annotatedMock)), + Anonymize::class + ) + ->willReturn([ + 'foo' => $anonymize, + ]); + + $this->propertyAnonymizer->expects($this->any()) + ->method('anonymizeField') + ->with( + $annotatedMock, + 'foo', + $anonymize + ); + + $this->anonymizer->anonymize($annotatedMock); + } + + /** + * Test if Anonymizer can only handle objects as argument. + * + * @return void + */ + public function testAnonymizerShouldReceiveExceptionWhenArgumentIsNoObject() + { + $this->expectException(\InvalidArgumentException::class); + $this->expectExceptionMessage('Invalid argument given "string" should be of type object.'); + + $this->anonymizer->anonymize('test'); + } +} diff --git a/Tests/Anonymizer/PropertyAnonymizerTest.php b/Tests/Anonymizer/PropertyAnonymizerTest.php new file mode 100644 index 0000000..15006c7 --- /dev/null +++ b/Tests/Anonymizer/PropertyAnonymizerTest.php @@ -0,0 +1,125 @@ + + * @copyright 2018 SuperBrave + * @license https://github.com/superbrave/gdpr-bundle/blob/master/LICENSE MIT + * @link https://www.superbrave.nl/ + */ + +namespace SuperBrave\GdprBundle\Tests\Anonymizer; + +use PHPUnit_Framework_TestCase; +use PHPUnit_Framework_MockObject_MockObject; +use ReflectionClass; +use SuperBrave\GdprBundle\Annotation\Anonymize; +use SuperBrave\GdprBundle\Anonymize\AnonymizerCollection; +use SuperBrave\GdprBundle\Anonymize\Type\AnonymizerInterface; +use SuperBrave\GdprBundle\Anonymize\PropertyAnonymizer; +use SuperBrave\GdprBundle\Manipulator\PropertyManipulator; +use SuperBrave\GdprBundle\Tests\AnnotatedMock; + +/** + * Test the behaviour of the PropertyAnonymizer + */ +class PropertyAnonymizerTest extends PHPUnit_Framework_TestCase +{ + /** + * Mock for the PropertyManipulator + * + * @var PHPUnit_Framework_MockObject_MockObject + */ + private $propertyManipulatorMock; + + /** + * Mock for the AnonymizerCollection + * + * @var PHPUnit_Framework_MockObject_MockObject + */ + private $anonymizerCollection; + + /** + * The propertyAnonymizer to be tested + * + * @var PropertyAnonymizer + */ + private $propertyAnonymizer; + + /** + * A ReflectionClass build from AnnotatedMock class + * + * @var ReflectionClass + */ + private $reflectionClass; + + /** + * Sets up the properties to be used in further tests + * + * @return void + */ + public function setUp() + { + $this->anonymizerCollection = $this->getMockBuilder(AnonymizerCollection::class) + ->getMock(); + + $this->propertyManipulatorMock = $this->getMockBuilder(PropertyManipulator::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->propertyAnonymizer = new PropertyAnonymizer($this->propertyManipulatorMock, $this->anonymizerCollection); + + $this->reflectionClass = new ReflectionClass(AnnotatedMock::class); + } + + /** + * Test that the property anonymizer correctly utilizes its dependencies to do its job + * + * @return void + */ + public function testPropertyAnonymizerUsesDependencies() + { + $annotation = new Anonymize(); + $annotation->type = 'testType'; + $annotation->value = 'testValue'; + + $anonymizerMock = $this->getMockBuilder(AnonymizerInterface::class)->getMock(); + + $theObject = new \stdClass(); + + //first it uses the collection to find an anonymizer + $this->anonymizerCollection + ->expects($this->once()) + ->method('getAnonymizer') + ->with('testType') + ->willReturn($anonymizerMock); + + //then it uses the manipulator to get the property's value + $this->propertyManipulatorMock + ->expects($this->once()) + ->method('getPropertyValue') + ->with($theObject, 'testProperty') + ->willReturn('testValue'); + + //after that the anonymizer is used to anonymize the value + $anonymizerMock + ->expects($this->once()) + ->method('anonymize') + ->with('testValue') + ->willReturn('anonymizedValue'); + + //and last the anonymized value should be handed back to the manipulator to update the object + $this->propertyManipulatorMock + ->expects($this->once()) + ->method('setPropertyValue') + ->with($theObject, 'testProperty', 'anonymizedValue'); + + $this->propertyAnonymizer->anonymizeField( + $theObject, + 'testProperty', + $annotation + ); + } +} diff --git a/Tests/Anonymizer/Type/DateTimeAnonymizerTest.php b/Tests/Anonymizer/Type/DateTimeAnonymizerTest.php new file mode 100644 index 0000000..87fe005 --- /dev/null +++ b/Tests/Anonymizer/Type/DateTimeAnonymizerTest.php @@ -0,0 +1,77 @@ + + * @copyright 2018 SuperBrave + * @license https://github.com/superbrave/gdpr-bundle/blob/master/LICENSE MIT + * @link https://www.superbrave.nl/ + */ + +namespace SuperBrave\GdprBundle\Tests\Anonymizer; + +use PHPUnit\Framework\TestCase; +use SuperBrave\GdprBundle\Anonymize\Type\DateTimeAnonymizer; + +/** + * Class DateTimeAnonymizerTest + * + * @package Gdpr + */ +class DateTimeAnonymizerTest extends TestCase +{ + /** + * Tests the DateTime anonymizer + * + * @param \DateTimeInterface|int|string $testDate The original date/time set + * @param \DateTimeInterface|int|string $expectedResult The expected result after anonymizing the data + * + * @return void + * + * @dataProvider dateTimeDataProvider + */ + public function testDateTime($testDate, $expectedResult) + { + $anonymizer = new DateTimeAnonymizer(); + $this->assertEquals($anonymizer->anonymize($testDate), $expectedResult); + } + + /** + * Returns a list of test cases for the testDateTime test + * + * @return array + */ + public function dateTimeDataProvider() + { + return array( + // DateTime objects + 'datetime1' => [new \DateTime('2016-04-27'), new \DateTime('2016-01-01')], + 'datetime2' => [new \DateTime('2018-05-25 13:37:00'), new \DateTime('2018-01-01 00:00:00')], + + // DateTimeImmutable objects + 'datetime3' => [new \DateTimeImmutable('2016-04-27'), new \DateTimeImmutable('2016-01-01')], + 'datetime4' => [new \DateTimeImmutable('2018-05-25 13:37'), new \DateTimeImmutable('2018-01-01 00:00')], + + // Timestamps + 'timestamp1' => [strtotime("2018-05-25 13:37:00"), strtotime("2018-01-01 00:00:00")], + 'timestamp2' => [strtotime("1945-05-05 00:00:00"), strtotime("1945-01-01 00:00:00")], + 'timestamp3' => [strtotime("2999-01-20 03:14:07"), strtotime("2999-01-01 00:00:00")], + + // Several ISO8601 string formats + 'stringdate1' => ["2016-04-27", "2016-01-01"], + 'stringdate2' => ["2018-05-25 13:37:00", "2018-01-01 00:00:00"], + 'stringdate3' => ["2018-05-25T13:37:00", "2018-01-01T00:00:00"], + 'stringdate4' => ["2018-05-25T13:37:00+0200", "2018-01-01T00:00:00+0200"], + // RFC3339 string format + 'stringdate5' => ["2018-05-25T13:37:00+02:00", "2018-01-01T00:00:00+02:00"], + // RFC822 string format + 'stringdate7' => ["Thu, 11 Aug 83 13:37:00 +0200", "Sat, 01 Jan 83 00:00:00 +0200"], + // RFC850 string format + 'stringdate8' => ["Thursday, 11-Aug-83 13:37:00 CEST", "Saturday, 01-Jan-83 00:00:00 CEST"], + // RFC7231 string format + 'stringdate9' => ["Thu, 11 Aug 1983 13:37:00 GMT", "Sat, 01 Jan 1983 00:00:00 GMT"], + ); + } +} diff --git a/Tests/Anonymizer/Type/FixedAnonymizerTest.php b/Tests/Anonymizer/Type/FixedAnonymizerTest.php new file mode 100644 index 0000000..3f554b8 --- /dev/null +++ b/Tests/Anonymizer/Type/FixedAnonymizerTest.php @@ -0,0 +1,160 @@ + + * @copyright 2018 SuperBrave + * @license https://github.com/superbrave/gdpr-bundle/blob/master/LICENSE MIT + * @link https://www.superbrave.nl/ + */ + +namespace SuperBrave\GdprBundle\Tests\Anonymizer; + +use PHPUnit_Framework_MockObject_MockObject; +use SuperBrave\GdprBundle\Anonymize\Type\FixedAnonymizer; +use SuperBrave\GdprBundle\Manipulator\PropertyManipulator; +use SuperBrave\GdprBundle\Tests\AnnotatedMock; +use Symfony\Component\OptionsResolver\Exception\MissingOptionsException; +use Symfony\Component\PropertyAccess\PropertyAccess; + +/** + * Tests the behaviour of the EmailAnonymizer + */ +class FixedAnonymizerTest extends \PHPUnit_Framework_TestCase +{ + /** + * The mock property accessor instance. + * + * @var PHPUnit_Framework_MockObject_MockObject + * @return void + */ + private $propertyManipulator; + + /** + * An instance of StringAnonymizer being tested. + * + * @var FixedAnonymizer + * @return void + */ + private $anonymizer; + + /** + * Creates a new AnnotationNormalizer instance for testing. + * + * @return void + */ + public function setUp() + { + $this->propertyManipulator = new PropertyManipulator( + PropertyAccess::createPropertyAccessor() + ); + + $this->anonymizer = new FixedAnonymizer($this->propertyManipulator); + } + + /** + * Tests if constructing a new StringAnonymizer sets the instance properties. + * + * @return void + */ + public function testConstructHasCorrectProperty() + { + $this->assertAttributeSame($this->propertyManipulator, 'propertyManipulator', $this->anonymizer); + } + + /** + * An exception should be thrown when the annotationValue isn't provided. + * + * @return void + */ + public function testAnonymizerShouldReceiveAnnotationValueOptionOrThrowException() + { + $this->expectException(MissingOptionsException::class); + $this->expectExceptionMessage('The required option "annotationValue" is missing.'); + + $this->anonymizer->anonymize('johndoe@appleseed.com', [ + 'object' => new AnnotatedMock(), + ]); + } + + /** + * An exception should be thrown when the object option isn't provided + * + * @return void + */ + public function testAnonymizerShouldReceiveObjectOptionOrThrowException() + { + $this->expectException(MissingOptionsException::class); + $this->expectExceptionMessage('The required option "object" is missing.'); + + $this->anonymizer->anonymize('johndoe@appleseed.com', [ + 'annotationValue' => 'foobar', + ]); + } + + /** + * Test wether or not getting the wildcards properties will be replaced with their values + * + * @return void + */ + public function testAnonymizeStringGivenAFormatWithMultipleProperties() + { + $this->assertEquals( + 'email-1-bar@localhost', + $this->anonymizer->anonymize('johndoe@appleseed.com', [ + 'annotationValue' => 'email-{baz}-{foo}@localhost', + 'object' => new AnnotatedMock(), + ]) + ); + } + + /** + * Test that an exception is thrown when the object does not have the specified property + * + * @return void + */ + public function testAnonymizeStringGivenAFormatWithInvalidPropertiesThrowsAnException() + { + $this->expectException(\InvalidArgumentException::class); + $this->expectExceptionMessage( + 'The property "$nonexistent" does not exist on class "SuperBrave\GdprBundle\Tests\AnnotatedMock"' + ); + + $this->anonymizer->anonymize('johndoe@appleseed.com', [ + 'annotationValue' => 'email-{nonexistent}', + 'object' => new AnnotatedMock(), + ]); + } + + /** + * Test that the value is returned if there aren't any wildcards + * + * @return void + */ + public function testAnonymizeStringGivenAFormatWithNoWildcardReturnsTheString() + { + $this->assertEquals('foobarbaz', $this->anonymizer->anonymize('johndoe@appleseed.com', [ + 'annotationValue' => 'foobarbaz', + 'object' => new AnnotatedMock(), + ])); + } + + /** + * Tests that an exception is thrown when the value provided in the annotation is an empty string + * Type "fixed" is expected to receive a value + * + * @return void + */ + public function testAnonymizeRequiresTheValueToBeANonEmptyStringOrThrowException() + { + $this->expectException(\InvalidArgumentException::class); + $this->expectExceptionMessage('The option "annotationValue" cannot be empty'); + + $this->anonymizer->anonymize('johndoe@appleseed.com', [ + 'annotationValue' => null, + 'object' => new AnnotatedMock(), + ]); + } +} diff --git a/Tests/Anonymizer/Type/IpAnonymizerTest.php b/Tests/Anonymizer/Type/IpAnonymizerTest.php new file mode 100644 index 0000000..1932ed2 --- /dev/null +++ b/Tests/Anonymizer/Type/IpAnonymizerTest.php @@ -0,0 +1,90 @@ + + * @copyright 2018 SuperBrave + * @license https://github.com/superbrave/gdpr-bundle/blob/master/LICENSE MIT + * @link https://www.superbrave.nl/ + */ + +namespace SuperBrave\GdprBundle\Tests\Anonymizer; + +use PHPUnit\Framework\TestCase; +use SuperBrave\GdprBundle\Anonymize\Type\IpAnonymizer; + +/** + * Class IpAnonymizerTest + * + * @package SuperBrave\GdprBundle\Tests\Anonymizer + */ +class IpAnonymizerTest extends TestCase +{ + /** + * Tests an IPv4 address with a default mask + * + * @return void + */ + public function testIpv4WithDefaultMask() + { + $anonymizer = new IpAnonymizer(); + + $this->assertEquals('10.20.30.0', $anonymizer->anonymize('10.20.30.40', [])); + } + + /** + * Tests an IPv6 address with a default mask + * + * @return void + */ + public function testIpv6WithDefaultMask() + { + $anonymizer = new IpAnonymizer(); + + $this->assertEquals( + '1234:5678:90ab::', + $anonymizer->anonymize('1234:5678:90ab:cdef:1234:5678:90ab:cdef', []) + ); + } + + /** + * Tests an IPv4 address with a custom mask + * + * @return void + */ + public function testIpv4WithCustomMask() + { + $anonymizer = new IpAnonymizer('255.192.0.0'); + + $this->assertEquals('10.128.0.0', $anonymizer->anonymize('10.140.30.40', [])); + } + + /** + * Tests an IPv6 address with a custpm mask + * + * @return void + */ + public function testIpv6WithCustomMask() + { + $anonymizer = new IpAnonymizer('255.255.255.0', 'ffff:ffff:ffff:7730::'); + + $this->assertEquals( + '1234:5678:90ab:4520::', + $anonymizer->anonymize('1234:5678:90ab:cdef:1234:5678:90ab:cdef', []) + ); + } + + /** + * Test an IPv4 as a long int using INET_ATON + * + * @return void + */ + public function testIpv4AsLong() + { + $anonymizer = new IpAnonymizer('255.192.0.0'); + + $this->assertEquals(176160768, $anonymizer->anonymize(176954920, [])); + } +} diff --git a/Tests/Serializer/Normalizer/AnnotationNormalizerTest.php b/Tests/Serializer/Normalizer/AnnotationNormalizerTest.php index 67ee438..9b59dfb 100644 --- a/Tests/Serializer/Normalizer/AnnotationNormalizerTest.php +++ b/Tests/Serializer/Normalizer/AnnotationNormalizerTest.php @@ -13,13 +13,14 @@ namespace SuperBrave\GdprBundle\Tests\Serializer\Normalizer; use Doctrine\Common\Collections\ArrayCollection; +use PHPUnit_Framework_MockObject_MockObject; use ReflectionClass; use SuperBrave\GdprBundle\Annotation\AnnotationReader; use SuperBrave\GdprBundle\Annotation\Export; +use SuperBrave\GdprBundle\Manipulator\PropertyManipulator; use SuperBrave\GdprBundle\Serializer\Normalizer\AnnotationNormalizer; use SuperBrave\GdprBundle\Tests\AnnotatedMock; use Symfony\Component\PropertyAccess\PropertyAccess; -use Symfony\Component\PropertyAccess\PropertyAccessorInterface; use Symfony\Component\Serializer\Encoder\XmlEncoder; use Symfony\Component\Serializer\Serializer; @@ -49,7 +50,7 @@ class AnnotationNormalizerTest extends \PHPUnit_Framework_TestCase * * @var PHPUnit_Framework_MockObject_MockObject */ - private $propertyAccessorMock; + private $propertyManipulatorMock; /** * Creates a new AnnotationNormalizer instance for testing. @@ -62,13 +63,14 @@ public function setUp() ->disableOriginalConstructor() ->getMock(); - $this->propertyAccessorMock = $this->getMockBuilder(PropertyAccessorInterface::class) + $this->propertyManipulatorMock = $this->getMockBuilder(PropertyManipulator::class) + ->disableOriginalConstructor() ->getMock(); $this->normalizer = new AnnotationNormalizer( $this->annotationReaderMock, Export::class, - $this->propertyAccessorMock + $this->propertyManipulatorMock ); } @@ -81,7 +83,7 @@ public function testConstruct() { $this->assertAttributeSame($this->annotationReaderMock, 'annotationReader', $this->normalizer); $this->assertAttributeSame(Export::class, 'annotationName', $this->normalizer); - $this->assertAttributeSame($this->propertyAccessorMock, 'propertyAccessor', $this->normalizer); + $this->assertAttributeSame($this->propertyManipulatorMock, 'propertyManipulator', $this->normalizer); } /** @@ -146,9 +148,11 @@ public function testSupportsNormalizationReturnsTrueWhenAnnotationReaderReturnsA public function testNormalize() { $annotationReader = new AnnotationReader(); - $propertyAccessor = PropertyAccess::createPropertyAccessor(); + $propertyManipulator = new PropertyManipulator( + PropertyAccess::createPropertyAccessor() + ); - $normalizer = new AnnotationNormalizer($annotationReader, Export::class, $propertyAccessor); + $normalizer = new AnnotationNormalizer($annotationReader, Export::class, $propertyManipulator); $annotatedMock = new AnnotatedMock(); @@ -173,9 +177,11 @@ public function testNormalize() public function testNormalizeThroughSerializer() { $annotationReader = new AnnotationReader(); - $propertyAccessor = PropertyAccess::createPropertyAccessor(); + $propertyManipulator = new PropertyManipulator( + PropertyAccess::createPropertyAccessor() + ); - $normalizer = new AnnotationNormalizer($annotationReader, Export::class, $propertyAccessor); + $normalizer = new AnnotationNormalizer($annotationReader, Export::class, $propertyManipulator); $encoder = new XmlEncoder('mock'); $serializer = new Serializer( diff --git a/composer.json b/composer.json index 568b9fe..d0c19a9 100644 --- a/composer.json +++ b/composer.json @@ -12,6 +12,7 @@ "php": ">=5.6.0", "doctrine/annotations": "^1.4", "symfony/framework-bundle": "^3.0.0", + "symfony/options-resolver": "^3.4", "symfony/property-access": "^3.4", "symfony/serializer": "^3.4" }, diff --git a/composer.lock b/composer.lock index 5b29c22..764ca98 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", "This file is @generated automatically" ], - "content-hash": "7680fa63bc4c739804734dca66c07e42", + "content-hash": "768e07409b339c531c1adaf441a98b0d", "packages": [ { "name": "doctrine/annotations", @@ -368,16 +368,16 @@ }, { "name": "symfony/cache", - "version": "v3.4.8", + "version": "v3.4.9", "source": { "type": "git", "url": "https://github.com/symfony/cache.git", - "reference": "13255ddd056e49f3154747943f8ee175d555d394" + "reference": "51a9eef3091b2c06f63d8b1b98de9d101b5e0e77" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/cache/zipball/13255ddd056e49f3154747943f8ee175d555d394", - "reference": "13255ddd056e49f3154747943f8ee175d555d394", + "url": "https://api.github.com/repos/symfony/cache/zipball/51a9eef3091b2c06f63d8b1b98de9d101b5e0e77", + "reference": "51a9eef3091b2c06f63d8b1b98de9d101b5e0e77", "shasum": "" }, "require": { @@ -434,11 +434,11 @@ "caching", "psr6" ], - "time": "2018-04-02T14:35:16+00:00" + "time": "2018-04-27T05:49:57+00:00" }, { "name": "symfony/class-loader", - "version": "v3.4.8", + "version": "v3.4.9", "source": { "type": "git", "url": "https://github.com/symfony/class-loader.git", @@ -494,7 +494,7 @@ }, { "name": "symfony/config", - "version": "v3.4.8", + "version": "v3.4.9", "source": { "type": "git", "url": "https://github.com/symfony/config.git", @@ -557,16 +557,16 @@ }, { "name": "symfony/debug", - "version": "v3.4.8", + "version": "v3.4.9", "source": { "type": "git", "url": "https://github.com/symfony/debug.git", - "reference": "9cf7c2271cfb89ef9727db1b740ca77be57bf9d7" + "reference": "1b95888cfd996484527cb41e8952d9a5eaf7454f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/debug/zipball/9cf7c2271cfb89ef9727db1b740ca77be57bf9d7", - "reference": "9cf7c2271cfb89ef9727db1b740ca77be57bf9d7", + "url": "https://api.github.com/repos/symfony/debug/zipball/1b95888cfd996484527cb41e8952d9a5eaf7454f", + "reference": "1b95888cfd996484527cb41e8952d9a5eaf7454f", "shasum": "" }, "require": { @@ -609,20 +609,20 @@ ], "description": "Symfony Debug Component", "homepage": "https://symfony.com", - "time": "2018-04-03T05:22:50+00:00" + "time": "2018-04-30T16:53:52+00:00" }, { "name": "symfony/dependency-injection", - "version": "v3.4.8", + "version": "v3.4.9", "source": { "type": "git", "url": "https://github.com/symfony/dependency-injection.git", - "reference": "24a68710c6ddc1e3d159a110cef94cedfcf3c611" + "reference": "54ff9d78b56429f9a1ac12e60bfb6d169c0468e3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/dependency-injection/zipball/24a68710c6ddc1e3d159a110cef94cedfcf3c611", - "reference": "24a68710c6ddc1e3d159a110cef94cedfcf3c611", + "url": "https://api.github.com/repos/symfony/dependency-injection/zipball/54ff9d78b56429f9a1ac12e60bfb6d169c0468e3", + "reference": "54ff9d78b56429f9a1ac12e60bfb6d169c0468e3", "shasum": "" }, "require": { @@ -680,11 +680,11 @@ ], "description": "Symfony DependencyInjection Component", "homepage": "https://symfony.com", - "time": "2018-03-29T11:25:31+00:00" + "time": "2018-04-29T14:04:08+00:00" }, { "name": "symfony/event-dispatcher", - "version": "v3.4.8", + "version": "v3.4.9", "source": { "type": "git", "url": "https://github.com/symfony/event-dispatcher.git", @@ -747,7 +747,7 @@ }, { "name": "symfony/filesystem", - "version": "v3.4.8", + "version": "v3.4.9", "source": { "type": "git", "url": "https://github.com/symfony/filesystem.git", @@ -796,7 +796,7 @@ }, { "name": "symfony/finder", - "version": "v3.4.8", + "version": "v3.4.9", "source": { "type": "git", "url": "https://github.com/symfony/finder.git", @@ -845,16 +845,16 @@ }, { "name": "symfony/framework-bundle", - "version": "v3.4.8", + "version": "v3.4.9", "source": { "type": "git", "url": "https://github.com/symfony/framework-bundle.git", - "reference": "ab19033db282fd56be8f4c444c1d07b11999fc48" + "reference": "7b48684e20bc84b6083cc44bf7d14057d98701b6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/framework-bundle/zipball/ab19033db282fd56be8f4c444c1d07b11999fc48", - "reference": "ab19033db282fd56be8f4c444c1d07b11999fc48", + "url": "https://api.github.com/repos/symfony/framework-bundle/zipball/7b48684e20bc84b6083cc44bf7d14057d98701b6", + "reference": "7b48684e20bc84b6083cc44bf7d14057d98701b6", "shasum": "" }, "require": { @@ -956,20 +956,20 @@ ], "description": "Symfony FrameworkBundle", "homepage": "https://symfony.com", - "time": "2018-04-04T17:20:26+00:00" + "time": "2018-04-30T16:54:07+00:00" }, { "name": "symfony/http-foundation", - "version": "v3.4.8", + "version": "v3.4.9", "source": { "type": "git", "url": "https://github.com/symfony/http-foundation.git", - "reference": "b11e6d165ff4cbf5685d185ab19a90f2f3bb7d1e" + "reference": "edc43b1a50402bb06b5111eb86b275c87a93e373" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-foundation/zipball/b11e6d165ff4cbf5685d185ab19a90f2f3bb7d1e", - "reference": "b11e6d165ff4cbf5685d185ab19a90f2f3bb7d1e", + "url": "https://api.github.com/repos/symfony/http-foundation/zipball/edc43b1a50402bb06b5111eb86b275c87a93e373", + "reference": "edc43b1a50402bb06b5111eb86b275c87a93e373", "shasum": "" }, "require": { @@ -1010,20 +1010,20 @@ ], "description": "Symfony HttpFoundation Component", "homepage": "https://symfony.com", - "time": "2018-04-03T05:22:50+00:00" + "time": "2018-04-30T01:05:13+00:00" }, { "name": "symfony/http-kernel", - "version": "v3.4.8", + "version": "v3.4.9", "source": { "type": "git", "url": "https://github.com/symfony/http-kernel.git", - "reference": "3cc2d4374aa9590c09277ad68657671cf49dbbf4" + "reference": "280fcedbcb3dabcc467a9c1734054af61928fe4f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-kernel/zipball/3cc2d4374aa9590c09277ad68657671cf49dbbf4", - "reference": "3cc2d4374aa9590c09277ad68657671cf49dbbf4", + "url": "https://api.github.com/repos/symfony/http-kernel/zipball/280fcedbcb3dabcc467a9c1734054af61928fe4f", + "reference": "280fcedbcb3dabcc467a9c1734054af61928fe4f", "shasum": "" }, "require": { @@ -1098,11 +1098,11 @@ ], "description": "Symfony HttpKernel Component", "homepage": "https://symfony.com", - "time": "2018-04-06T15:19:48+00:00" + "time": "2018-04-30T19:27:02+00:00" }, { "name": "symfony/inflector", - "version": "v3.4.8", + "version": "v3.4.9", "source": { "type": "git", "url": "https://github.com/symfony/inflector.git", @@ -1157,18 +1157,72 @@ ], "time": "2018-01-03T17:14:19+00:00" }, + { + "name": "symfony/options-resolver", + "version": "v3.4.9", + "source": { + "type": "git", + "url": "https://github.com/symfony/options-resolver.git", + "reference": "f3109a6aedd20e35c3a33190e932c2b063b7b50e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/options-resolver/zipball/f3109a6aedd20e35c3a33190e932c2b063b7b50e", + "reference": "f3109a6aedd20e35c3a33190e932c2b063b7b50e", + "shasum": "" + }, + "require": { + "php": "^5.5.9|>=7.0.8" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.4-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\OptionsResolver\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony OptionsResolver Component", + "homepage": "https://symfony.com", + "keywords": [ + "config", + "configuration", + "options" + ], + "time": "2018-01-11T07:56:07+00:00" + }, { "name": "symfony/polyfill-apcu", - "version": "v1.7.0", + "version": "v1.8.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-apcu.git", - "reference": "e8ae2136ddb53dea314df56fcd88e318ab936c00" + "reference": "9b83bd010112ec196410849e840d9b9fefcb15ad" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-apcu/zipball/e8ae2136ddb53dea314df56fcd88e318ab936c00", - "reference": "e8ae2136ddb53dea314df56fcd88e318ab936c00", + "url": "https://api.github.com/repos/symfony/polyfill-apcu/zipball/9b83bd010112ec196410849e840d9b9fefcb15ad", + "reference": "9b83bd010112ec196410849e840d9b9fefcb15ad", "shasum": "" }, "require": { @@ -1177,7 +1231,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.7-dev" + "dev-master": "1.8-dev" } }, "autoload": { @@ -1211,20 +1265,20 @@ "portable", "shim" ], - "time": "2018-01-30T19:27:44+00:00" + "time": "2018-04-26T10:06:28+00:00" }, { "name": "symfony/polyfill-mbstring", - "version": "v1.7.0", + "version": "v1.8.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-mbstring.git", - "reference": "78be803ce01e55d3491c1397cf1c64beb9c1b63b" + "reference": "3296adf6a6454a050679cde90f95350ad604b171" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/78be803ce01e55d3491c1397cf1c64beb9c1b63b", - "reference": "78be803ce01e55d3491c1397cf1c64beb9c1b63b", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/3296adf6a6454a050679cde90f95350ad604b171", + "reference": "3296adf6a6454a050679cde90f95350ad604b171", "shasum": "" }, "require": { @@ -1236,7 +1290,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.7-dev" + "dev-master": "1.8-dev" } }, "autoload": { @@ -1270,20 +1324,20 @@ "portable", "shim" ], - "time": "2018-01-30T19:27:44+00:00" + "time": "2018-04-26T10:06:28+00:00" }, { "name": "symfony/polyfill-php70", - "version": "v1.7.0", + "version": "v1.8.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php70.git", - "reference": "3532bfcd8f933a7816f3a0a59682fc404776600f" + "reference": "77454693d8f10dd23bb24955cffd2d82db1007a6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php70/zipball/3532bfcd8f933a7816f3a0a59682fc404776600f", - "reference": "3532bfcd8f933a7816f3a0a59682fc404776600f", + "url": "https://api.github.com/repos/symfony/polyfill-php70/zipball/77454693d8f10dd23bb24955cffd2d82db1007a6", + "reference": "77454693d8f10dd23bb24955cffd2d82db1007a6", "shasum": "" }, "require": { @@ -1293,7 +1347,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.7-dev" + "dev-master": "1.8-dev" } }, "autoload": { @@ -1329,11 +1383,11 @@ "portable", "shim" ], - "time": "2018-01-30T19:27:44+00:00" + "time": "2018-04-26T10:06:28+00:00" }, { "name": "symfony/property-access", - "version": "v3.4.8", + "version": "v3.4.9", "source": { "type": "git", "url": "https://github.com/symfony/property-access.git", @@ -1401,16 +1455,16 @@ }, { "name": "symfony/routing", - "version": "v3.4.8", + "version": "v3.4.9", "source": { "type": "git", "url": "https://github.com/symfony/routing.git", - "reference": "50f333b707bef9f6972ad04e6df3ec8875c9a67c" + "reference": "9deb375986f5d1f37283d8386716d26985a0f4b6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/routing/zipball/50f333b707bef9f6972ad04e6df3ec8875c9a67c", - "reference": "50f333b707bef9f6972ad04e6df3ec8875c9a67c", + "url": "https://api.github.com/repos/symfony/routing/zipball/9deb375986f5d1f37283d8386716d26985a0f4b6", + "reference": "9deb375986f5d1f37283d8386716d26985a0f4b6", "shasum": "" }, "require": { @@ -1475,11 +1529,11 @@ "uri", "url" ], - "time": "2018-04-04T13:22:16+00:00" + "time": "2018-04-12T09:01:03+00:00" }, { "name": "symfony/serializer", - "version": "v3.4.8", + "version": "v3.4.9", "source": { "type": "git", "url": "https://github.com/symfony/serializer.git", @@ -2328,17 +2382,17 @@ "source": { "type": "git", "url": "https://github.com/Roave/SecurityAdvisories.git", - "reference": "2b28787651b69de2b14ae222737deb05befbb33b" + "reference": "d3268ea60c79284f9a8db7b78eb48cc99cc5d471" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Roave/SecurityAdvisories/zipball/2b28787651b69de2b14ae222737deb05befbb33b", - "reference": "2b28787651b69de2b14ae222737deb05befbb33b", + "url": "https://api.github.com/repos/Roave/SecurityAdvisories/zipball/d3268ea60c79284f9a8db7b78eb48cc99cc5d471", + "reference": "d3268ea60c79284f9a8db7b78eb48cc99cc5d471", "shasum": "" }, "conflict": { "3f/pygmentize": "<1.2", - "adodb/adodb-php": "<5.20.6", + "adodb/adodb-php": "<5.20.12", "amphp/artax": "<1.0.6|>=2,<2.0.6", "amphp/http": "<1.0.1", "asymmetricrypt/asymmetricrypt": ">=0,<9.9.99", @@ -2431,7 +2485,7 @@ "symfony/web-profiler-bundle": ">=2,<2.3.19|>=2.4,<2.4.9|>=2.5,<2.5.4", "symfony/yaml": ">=2,<2.0.22|>=2.1,<2.1.7", "thelia/backoffice-default-template": ">=2.1,<2.1.2", - "thelia/thelia": ">=2.1,<2.1.2|>=2.1.0-beta1,<2.1.3", + "thelia/thelia": ">=2.1.0-beta1,<2.1.3|>=2.1,<2.1.2", "titon/framework": ">=0,<9.9.99", "twig/twig": "<1.20", "typo3/cms": ">=6.2,<6.2.30|>=7,<7.6.22|>=8,<8.7.5", @@ -2483,7 +2537,7 @@ } ], "description": "Prevents installation of composer packages with known security vulnerabilities: no API, simply require it", - "time": "2018-04-18T09:24:34+00:00" + "time": "2018-04-30T07:23:33+00:00" }, { "name": "sebastian/code-unit-reverse-lookup", @@ -3084,16 +3138,16 @@ }, { "name": "symfony/yaml", - "version": "v3.4.8", + "version": "v3.4.9", "source": { "type": "git", "url": "https://github.com/symfony/yaml.git", - "reference": "a42f9da85c7c38d59f5e53f076fe81a091f894d0" + "reference": "033cfa61ef06ee0847e056e530201842b6e926c3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/yaml/zipball/a42f9da85c7c38d59f5e53f076fe81a091f894d0", - "reference": "a42f9da85c7c38d59f5e53f076fe81a091f894d0", + "url": "https://api.github.com/repos/symfony/yaml/zipball/033cfa61ef06ee0847e056e530201842b6e926c3", + "reference": "033cfa61ef06ee0847e056e530201842b6e926c3", "shasum": "" }, "require": { @@ -3138,7 +3192,7 @@ ], "description": "Symfony Yaml Component", "homepage": "https://symfony.com", - "time": "2018-04-03T05:14:20+00:00" + "time": "2018-04-08T08:21:29+00:00" }, { "name": "webmozart/assert", diff --git a/phpcs.xml b/phpcs.xml index 278d217..d75b450 100644 --- a/phpcs.xml +++ b/phpcs.xml @@ -1,6 +1,5 @@ - - SuperBrave Code Standards. + vendor/autoload.php . @@ -13,4 +12,9 @@ + + + + Anonymize/Type/DateTimeAnonymizer.php +