Issues (779)

Subscribers/DoctrineEncryptSubscriber.php (6 issues)

1
<?php
2
3
namespace VersionControl\DoctrineEncryptBundle\Subscribers;
4
5
use Doctrine\ORM\Events;
6
use Doctrine\Common\EventSubscriber;
7
use Doctrine\ORM\Event\LifecycleEventArgs;
8
use Doctrine\ORM\Event\PreUpdateEventArgs;
9
use Doctrine\Common\Annotations\Reader;
10
use Doctrine\ORM\EntityManager;
11
use ReflectionClass;
12
use VersionControl\DoctrineEncryptBundle\Encryptors\EncryptorInterface;
13
14
/**
15
 * Doctrine event subscriber which encrypt/decrypt entities.
16
 */
17
class DoctrineEncryptSubscriber implements EventSubscriber
18
{
19
    /**
20
     * Encryptor interface namespace.
21
     */
22
    const ENCRYPTOR_INTERFACE_NS = 'VersionControl\DoctrineEncryptBundle\Encryptors\EncryptorInterface';
23
24
    /**
25
     * Encrypted annotation full name.
26
     */
27
    const ENCRYPTED_ANN_NAME = 'VersionControl\DoctrineEncryptBundle\Configuration\Encrypted';
28
29
    /**
30
     * Encryptor.
31
     *
32
     * @var EncryptorInterface
33
     */
34
    private $encryptor;
35
36
    /**
37
     * Annotation reader.
38
     *
39
     * @var Doctrine\Common\Annotations\Reader
0 ignored issues
show
The type VersionControl\DoctrineE...mmon\Annotations\Reader was not found. Did you mean Doctrine\Common\Annotations\Reader? If so, make sure to prefix the type with \.
Loading history...
40
     */
41
    private $annReader;
42
43
    /**
44
     * Registr to avoid multi decode operations for one entity.
45
     *
46
     * @var array
47
     */
48
    private $decodedRegistry = array();
49
50
    /**
51
     * Initialization of subscriber.
52
     *
53
     * @param string $encryptorClass
54
     * @param string $secretKey
55
     */
56
    public function __construct(Reader $annReader, $encryptorClass, $secretKey)
57
    {
58
        $this->annReader = $annReader;
0 ignored issues
show
Documentation Bug introduced by
It seems like $annReader of type Doctrine\Common\Annotations\Reader is incompatible with the declared type VersionControl\DoctrineE...mmon\Annotations\Reader of property $annReader.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
59
        $this->encryptor = $this->encryptorFactory($encryptorClass, $secretKey);
60
    }
61
62
    /**
63
     * Listen a prePersist lifecycle event. Checking and encrypt entities
64
     * which have @Encrypted annotation.
65
     *
66
     * @param LifecycleEventArgs $args
67
     */
68
    public function prePersist(LifecycleEventArgs $args)
69
    {
70
        $entity = $args->getEntity();
71
        $this->processFields($entity);
72
    }
73
74
    /**
75
     * Listen a preUpdate lifecycle event. Checking and encrypt entities fields
76
     * which have @Encrypted annotation. Using changesets to avoid preUpdate event
77
     * restrictions.
78
     *
79
     * @param LifecycleEventArgs $args
80
     */
81
    public function preUpdate(PreUpdateEventArgs $args)
82
    {
83
        $reflectionClass = new ReflectionClass($args->getEntity());
84
        $properties = $reflectionClass->getProperties();
85
        foreach ($properties as $refProperty) {
86
            if ($this->annReader->getPropertyAnnotation($refProperty, self::ENCRYPTED_ANN_NAME)) {
87
                $propName = $refProperty->getName();
88
                if ($args->hasChangedField($propName)) {
89
                    $args->setNewValue($propName, $this->encryptor->encrypt($args->getNewValue($propName)));
90
                }
91
            }
92
        }
93
    }
94
95
    /**
96
     * Listen a postLoad lifecycle event. Checking and decrypt entities
97
     * which have @Encrypted annotations.
98
     *
99
     * @param LifecycleEventArgs $args
100
     */
101
    public function postLoad(LifecycleEventArgs $args)
102
    {
103
        $entity = $args->getEntity();
104
        if (!$this->hasInDecodedRegistry($entity, $args->getEntityManager())) {
105
            if ($this->processFields($entity, false)) {
106
                $this->addToDecodedRegistry($entity, $args->getEntityManager());
107
            }
108
        }
109
    }
110
111
    /**
112
     * Realization of EventSubscriber interface method.
113
     *
114
     * @return array Return all events which this subscriber is listening
115
     */
116
    public function getSubscribedEvents()
117
    {
118
        return array(
119
            Events::prePersist,
120
            Events::preUpdate,
121
            Events::postLoad,
122
        );
123
    }
124
125
    /**
126
     * Capitalize string.
127
     *
128
     * @param string $word
129
     *
130
     * @return string
131
     */
132
    public static function capitalize($word)
133
    {
134
        if (is_array($word)) {
0 ignored issues
show
The condition is_array($word) is always false.
Loading history...
135
            $word = $word[0];
136
        }
137
138
        return str_replace(' ', '', ucwords(str_replace(array('-', '_'), ' ', $word)));
139
    }
140
141
    /**
142
     * Process (encrypt/decrypt) entities fields.
143
     *
144
     * @param Obj  $entity             Some doctrine entity
0 ignored issues
show
The type VersionControl\DoctrineE...tBundle\Subscribers\Obj was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
145
     * @param bool $isEncryptOperation If true - encrypt, false - decrypt entity
146
     */
147
    private function processFields($entity, $isEncryptOperation = true)
148
    {
149
        $encryptorMethod = $isEncryptOperation ? 'encrypt' : 'decrypt';
150
        $reflectionClass = new ReflectionClass($entity);
151
        $properties = $reflectionClass->getProperties();
152
        $withAnnotation = false;
153
        foreach ($properties as $refProperty) {
154
            if ($this->annReader->getPropertyAnnotation($refProperty, self::ENCRYPTED_ANN_NAME)) {
155
                $withAnnotation = true;
156
                // we have annotation and if it decrypt operation, we must avoid duble decryption
157
                $propName = $refProperty->getName();
158
                if ($refProperty->isPublic()) {
159
                    $entity->$propName = $this->encryptor->$encryptorMethod($refProperty->getValue());
160
                } else {
161
                    $methodName = self::capitalize($propName);
162
                    if ($reflectionClass->hasMethod($getter = 'get'.$methodName) && $reflectionClass->hasMethod($setter = 'set'.$methodName)) {
163
                        $currentPropValue = $this->encryptor->$encryptorMethod($entity->$getter());
164
                        $entity->$setter($currentPropValue);
165
                    } else {
166
                        throw new \RuntimeException(sprintf("Property %s isn't public and doesn't has getter/setter"));
167
                    }
168
                }
169
            }
170
        }
171
172
        return $withAnnotation;
173
    }
174
175
    /**
176
     * Encryptor factory. Checks and create needed encryptor.
177
     *
178
     * @param string $classFullName Encryptor namespace and name
179
     * @param string $secretKey     Secret key for encryptor
180
     *
181
     * @return EncryptorInterface
182
     *
183
     * @throws \RuntimeException
184
     */
185
    private function encryptorFactory($classFullName, $secretKey)
186
    {
187
        $refClass = new \ReflectionClass($classFullName);
188
        if ($refClass->implementsInterface(self::ENCRYPTOR_INTERFACE_NS)) {
189
            return new $classFullName($secretKey);
190
        } else {
191
            throw new \RuntimeException('Encryptor must implements interface EncryptorInterface');
192
        }
193
    }
194
195
    /**
196
     * Check if we have entity in decoded registry.
197
     *
198
     * @param object                      $entity Some doctrine entity
199
     * @param \Doctrine\ORM\EntityManager $em
200
     *
201
     * @return bool
202
     */
203
    private function hasInDecodedRegistry($entity, EntityManager $em)
204
    {
205
        $className = get_class($entity);
206
        $metadata = $em->getClassMetadata($className);
207
        $getter = 'get'.self::capitalize($metadata->getIdentifier());
0 ignored issues
show
$metadata->getIdentifier() of type array is incompatible with the type string expected by parameter $word of VersionControl\DoctrineE...ubscriber::capitalize(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

207
        $getter = 'get'.self::capitalize(/** @scrutinizer ignore-type */ $metadata->getIdentifier());
Loading history...
208
209
        return isset($this->decodedRegistry[$className][$entity->$getter()]);
210
    }
211
212
    /**
213
     * Adds entity to decoded registry.
214
     *
215
     * @param object                      $entity Some doctrine entity
216
     * @param \Doctrine\ORM\EntityManager $em
217
     */
218
    private function addToDecodedRegistry($entity, EntityManager $em)
219
    {
220
        $className = get_class($entity);
221
        $metadata = $em->getClassMetadata($className);
222
        $getter = 'get'.self::capitalize($metadata->getIdentifier());
0 ignored issues
show
$metadata->getIdentifier() of type array is incompatible with the type string expected by parameter $word of VersionControl\DoctrineE...ubscriber::capitalize(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

222
        $getter = 'get'.self::capitalize(/** @scrutinizer ignore-type */ $metadata->getIdentifier());
Loading history...
223
        $this->decodedRegistry[$className][$entity->$getter()] = true;
224
    }
225
}
226