diff --git a/Configuration/Encrypted.php b/Configuration/Encrypted.php index fbaad58d..035147fb 100644 --- a/Configuration/Encrypted.php +++ b/Configuration/Encrypted.php @@ -9,5 +9,5 @@ * @Annotation */ class Encrypted { - // some parameters will be added + //Just an placeholder } \ No newline at end of file diff --git a/DependencyInjection/Compiler/RegisterServiceCompilerPass.php b/DependencyInjection/Compiler/RegisterServiceCompilerPass.php index d17d43ab..aca21dc6 100644 --- a/DependencyInjection/Compiler/RegisterServiceCompilerPass.php +++ b/DependencyInjection/Compiler/RegisterServiceCompilerPass.php @@ -9,7 +9,7 @@ use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; /** - * Description of RegisterServiceCompilerPass + * The RegisterServiceCompilerPass class * * @author wpigott */ @@ -18,8 +18,6 @@ class RegisterServiceCompilerPass implements CompilerPassInterface { public function process(ContainerBuilder $container) { //Nothing here } - - } ?> diff --git a/DependencyInjection/Configuration.php b/DependencyInjection/Configuration.php index 6e91ca58..fff3d610 100644 --- a/DependencyInjection/Configuration.php +++ b/DependencyInjection/Configuration.php @@ -18,10 +18,10 @@ class Configuration implements ConfigurationInterface { * {@inheritDoc} */ public function getConfigTreeBuilder() { + + //Create tree builder $treeBuilder = new TreeBuilder(); $rootNode = $treeBuilder->root('ambta_doctrine_encrypt'); - $supportedDrivers = array('orm'); - $supportedEncryptors = array('aes256'); // Grammar of config tree $rootNode @@ -32,26 +32,8 @@ public function getConfigTreeBuilder() { ->thenInvalid('You must specifiy secret_key option') ->end() ->end() - ->scalarNode('encryptor') - ->validate() - ->ifNotInArray($supportedEncryptors) - ->thenInvalid('You must choose from one of provided encryptors or specify your own encryptor class through encryptor_class option') - ->end() - ->defaultValue($supportedEncryptors[0]) - ->end() ->scalarNode('encryptor_class') ->end() - ->scalarNode('encryptor_service') - ->end() - ->scalarNode('db_driver') - ->validate() - ->ifNotInArray($supportedDrivers) - ->thenInvalid('The driver %s is not supported. Please choose one of ' . json_encode($supportedDrivers)) - ->end() - ->cannotBeOverwritten() - ->defaultValue($supportedDrivers[0]) - ->cannotBeEmpty() - ->end() ->end(); return $treeBuilder; diff --git a/DependencyInjection/DoctrineEncryptExtension.php b/DependencyInjection/DoctrineEncryptExtension.php index dbd08a28..e77cfd20 100644 --- a/DependencyInjection/DoctrineEncryptExtension.php +++ b/DependencyInjection/DoctrineEncryptExtension.php @@ -22,12 +22,17 @@ class DoctrineEncryptExtension extends Extension { */ public function load(array $configs, ContainerBuilder $container) { + //Create configuration object $configuration = new Configuration(); $config = $this->processConfiguration($configuration, $configs); + //Set orm-service in array of services $services = array('orm' => 'orm-services'); + + //set supported encryptor classes $supportedEncryptorClasses = array('aes256' => 'Ambta\DoctrineEncryptBundle\Encryptors\AES256Encryptor'); + //If no secret key is set, check for framework secret, otherwise throw exception if (empty($config['secret_key'])) { if ($container->hasParameter('secret')) { $config['secret_key'] = $container->getParameter('secret'); @@ -36,26 +41,21 @@ public function load(array $configs, ContainerBuilder $container) { } } - if (!empty($config['encryptor_class'])) { - $encryptorFullName = $config['encryptor_class']; - } else { - $encryptorFullName = $supportedEncryptorClasses[$config['encryptor']]; + //If empty encryptor class, use AES256 encryptor + if (empty($config['encryptor_class'])) { + $config['encryptor_class'] = $supportedEncryptorClasses['aes256']; } - $container->setParameter('ambta_doctrine_encrypt.encryptor_class_name', $encryptorFullName); + //Set parameters + $container->setParameter('ambta_doctrine_encrypt.encryptor_class_name', $config['encryptor_class']); $container->setParameter('ambta_doctrine_encrypt.secret_key', $config['secret_key']); - if (!empty($config['encryptor_service'])) { - $container->setParameter('ambta_doctrine_encrypt.encryptor_service', $config['encryptor_service']); - } - + //Load service file $loader = new Loader\YamlFileLoader($container, new FileLocator(__DIR__ . '/../Resources/config')); - $loader->load(sprintf('%s.yml', $services[$config['db_driver']])); + $loader->load(sprintf('%s.yml', $services['orm'])); } - public function getAlias() { return 'ambta_doctrine_encrypt'; } - } diff --git a/README.md b/README.md index d23cf355..96263267 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ More about us can be found on our website. [Ambta.com](https://ambta.com) ###What does it do exactly -It gives you the opportunity to add the @Encrypt annotation above each (string/text) field +It gives you the opportunity to add the @Encrypt annotation above each string property ``` /** @@ -48,4 +48,18 @@ This bundle is under the MIT license. See the complete license in the bundle ###Versions -I'm using Semantic Versioning like described [here](http://semver.org) \ No newline at end of file +I'm using Semantic Versioning like described [here](http://semver.org) + +###Todos + +The following items will be done in order + +1. Review of complete code + fixes/improvements and inline documentation +2. Add support for the other doctrine relationships (manyToMany, ManyToOne) +3. Add "Encryption" (reformating based on key) of integers, data time object +4. Recreate documentation +5. Create example code +6. Create an function to encrypt unencrypted database and vice versa +7. Look for a posibility of automatic encryption of query parameters +8. Look for a positbility to override findOneBy for automatic encryption of parameters +9. Add "Encryption" (reformating based on key) on all other database types) [Doctrine documentation Types](http://doctrine-dbal.readthedocs.org/en/latest/reference/types.html) diff --git a/Subscribers/DoctrineEncryptSubscriber.php b/Subscribers/DoctrineEncryptSubscriber.php index 13e6447a..30383ed3 100644 --- a/Subscribers/DoctrineEncryptSubscriber.php +++ b/Subscribers/DoctrineEncryptSubscriber.php @@ -47,11 +47,12 @@ class DoctrineEncryptSubscriber implements EventSubscriber { /** * Initialization of subscriber + * * @param Reader $annReader - * @param string $encryptorClass The encryptor class. This can be empty if - * a service is being provided. + * @param string $encryptorClass The encryptor class. This can be empty if a service is being provided. * @param string $secretKey The secret key. * @param EncryptorInterface|NULL $service (Optional) An EncryptorInterface. + * * This allows for the use of dependency injection for the encrypters. */ public function __construct(Reader $annReader, $encryptorClass, $secretKey, EncryptorInterface $service = NULL) { @@ -64,8 +65,12 @@ public function __construct(Reader $annReader, $encryptorClass, $secretKey, Encr } /** - * Listen a postUpdate lifecycle event. Checking and encrypt entities - * which have @Encrypted annotation + * Listen a postUpdate lifecycle event. + * Decrypt entity's property's values when post updated. + * + * So for example after form submit the preUpdate encrypted the entity + * We have to decrypt them before showing them again. + * * @param LifecycleEventArgs $args */ public function postUpdate(LifecycleEventArgs $args) { @@ -76,9 +81,9 @@ public function postUpdate(LifecycleEventArgs $args) { } /** - * Listen a preUpdate lifecycle event. Checking and encrypt entities fields - * which have @Encrypted annotation. Using changesets to avoid preUpdate event - * restrictions + * Listen a preUpdate lifecycle event. + * Encrypt entity's property's values on preUpdate, so they will be stored encrypted + * * @param PreUpdateEventArgs $args */ public function preUpdate(PreUpdateEventArgs $args) { @@ -89,12 +94,14 @@ public function preUpdate(PreUpdateEventArgs $args) { } /** - * Listen a postLoad lifecycle event. Checking and decrypt entities - * which have @Encrypted annotations + * Listen a postLoad lifecycle event. + * Decrypt entity's property's values when loaded into the entity manger + * * @param LifecycleEventArgs $args */ public function postLoad(LifecycleEventArgs $args) { + //Get entity and process fields $entity = $args->getEntity(); $this->processFields($entity, false); @@ -102,6 +109,7 @@ public function postLoad(LifecycleEventArgs $args) { /** * Realization of EventSubscriber interface method. + * * @return Array Return all events which this subscriber is listening */ public function getSubscribedEvents() { @@ -112,65 +120,70 @@ public function getSubscribedEvents() { ); } - /** - * Capitalize string - * @param string $word - * @return string - */ - public static function capitalize($word) { - if (is_array($word)) { - $word = $word[0]; - } - - return str_replace(' ', '', ucwords(str_replace(array('-', '_'), ' ', $word))); - } /** * Process (encrypt/decrypt) entities fields - * @param Obj $entity Some doctrine entity + * + * @param Object $entity doctrine entity * @param Boolean $isEncryptOperation If true - encrypt, false - decrypt entity * * @throws \RuntimeException - * - * @return boolean */ private function processFields($entity, $isEncryptOperation = true) { + //Check which operation to be used $encryptorMethod = $isEncryptOperation ? 'encrypt' : 'decrypt'; - + //Get the real class, we don't want to use the proxy classes $realClass = \Doctrine\Common\Util\ClassUtils::getClass($entity); + //Get ReflectionClass of our entity $reflectionClass = new ReflectionClass($realClass); - $properties = $reflectionClass->getProperties(); - $withAnnotation = false; - + //Foreach property in the reflection class foreach ($properties as $refProperty) { + /** + * If followed standards, method name is getPropertyName, the propertyName is lowerCamelCase + * So just uppercase first character of the property, later on get and set{$methodName} wil be used + */ + $methodName = ucfirst($refProperty->getName()); + + /** + * Lazy loading, check if the property has an manyToOne relationship. + * if it has look if the set/get exists and recursively call this function based on the entity inside. Only if not empty ofcourse + */ if($this->annReader->getPropertyAnnotation($refProperty, 'Doctrine\ORM\Mapping\ManyToOne')) { - $getter = "get".ucfirst($refProperty->getName()); - $this->processFields($entity->$getter(), $isEncryptOperation); + if ($reflectionClass->hasMethod($getter = 'get' . $methodName) && $reflectionClass->hasMethod($setter = 'set' . $methodName)) { + $entity = $entity->$getter(); + if(!empty($entity)) { + $this->processFields($entity, $isEncryptOperation); + } + } } + /** + * If property is an normal value and contains the Encrypt tag, lets encrypt/decrypt that property + */ if ($this->annReader->getPropertyAnnotation($refProperty, self::ENCRYPTED_ANN_NAME)) { - $withAnnotation = true; - - // we have annotation and if it decrypt operation, we must avoid double decryption - $propName = $refProperty->getName(); - + /** + * If it is public lets not use the getter/setter + */ if ($refProperty->isPublic()) { + $propName = $refProperty->getName(); $entity->$propName = $this->encryptor->$encryptorMethod($refProperty->getValue()); - } else { - $methodName = self::capitalize($propName); - + //If private or protected check if there is an getter/setter for the property, based on the $methodName if ($reflectionClass->hasMethod($getter = 'get' . $methodName) && $reflectionClass->hasMethod($setter = 'set' . $methodName)) { + //Get the information (value) of the property $getInformation = $entity->$getter(); + + //Then decrypt, encrypt the information if not empty en the tag is there or not + //The will be added at the end of an encrypted string so it is marked as encrypted if($encryptorMethod == "decrypt") { if(!is_null($getInformation) and !empty($getInformation)) { if(substr($entity->$getter(), -5) == "") { @@ -194,8 +207,6 @@ private function processFields($entity, $isEncryptOperation = true) { } } - - return $withAnnotation; } /**