Completed
Push — master ( 44bad9...3ecbe1 )
by Jan
04:21
created

ElementPermissionListener::preUpdateHandler()   A

Complexity

Conditions 6
Paths 4

Size

Total Lines 21
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Importance

Changes 3
Bugs 0 Features 0
Metric Value
cc 6
eloc 11
c 3
b 0
f 0
nc 4
nop 2
dl 0
loc 21
rs 9.2222
1
<?php
2
/**
3
 * part-db version 0.1
4
 * Copyright (C) 2005 Christoph Lechner
5
 * http://www.cl-projects.de/.
6
 *
7
 * part-db version 0.2+
8
 * Copyright (C) 2009 K. Jacobs and others (see authors.php)
9
 * http://code.google.com/p/part-db/
10
 *
11
 * Part-DB Version 0.4+
12
 * Copyright (C) 2016 - 2019 Jan Böhmer
13
 * https://github.com/jbtronics
14
 *
15
 * This program is free software; you can redistribute it and/or
16
 * modify it under the terms of the GNU General Public License
17
 * as published by the Free Software Foundation; either version 2
18
 * of the License, or (at your option) any later version.
19
 *
20
 * This program is distributed in the hope that it will be useful,
21
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
22
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
23
 * GNU General Public License for more details.
24
 *
25
 * You should have received a copy of the GNU General Public License
26
 * along with this program; if not, write to the Free Software
27
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
28
 */
29
30
namespace App\Security\EntityListeners;
31
32
use App\Entity\Base\DBElement;
33
use App\Security\Annotations\ColumnSecurity;
34
use Doctrine\Common\Annotations\Reader;
35
use Doctrine\Common\Persistence\Event\LifecycleEventArgs;
36
use Doctrine\ORM\EntityManager;
37
use Doctrine\ORM\EntityManagerInterface;
38
use Doctrine\ORM\Event\PreFlushEventArgs;
39
use Doctrine\ORM\Event\PreUpdateEventArgs;
40
use Doctrine\ORM\Mapping as ORM;
41
use Doctrine\ORM\Mapping\PostLoad;
42
use Doctrine\ORM\Mapping\PreUpdate;
43
use ReflectionClass;
44
use Symfony\Component\Security\Core\Security;
45
46
/**
47
 * The purpose of this class is to hook into the doctrine entity lifecycle and restrict access to entity informations
48
 * configured by ColoumnSecurity Annotation.
49
 *
50
 * If a user does not have access to an coloumn, it will be filled, with a placeholder, after doctrine loading is finished.
51
 * The edit process is also catched, so that these placeholders, does not get saved to database.
52
 */
53
class ElementPermissionListener
54
{
55
    protected $security;
56
    protected $reader;
57
    protected $em;
58
59
    public function __construct(Security $security, Reader $reader, EntityManagerInterface $em)
60
    {
61
        $this->security = $security;
62
        $this->reader = $reader;
63
        $this->em = $em;
64
    }
65
66
    /**
67
     * @PostLoad
68
     *
69
     * This function is called after doctrine filled, the entity properties with db values.
70
     * We use this, to check if the user is allowed to access these properties, and if not, we write a placeholder
71
     * into the element properties, so that a user only gets non sensitve data.
72
     */
73
    public function postLoadHandler(DBElement $element, LifecycleEventArgs $event)
0 ignored issues
show
Unused Code introduced by
The parameter $event is not used and could be removed. ( Ignorable by Annotation )

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

73
    public function postLoadHandler(DBElement $element, /** @scrutinizer ignore-unused */ LifecycleEventArgs $event)

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
74
    {
75
        //Read Annotations and properties.
76
        $reflectionClass = new ReflectionClass($element);
77
        $properties = $reflectionClass->getProperties();
78
79
        foreach ($properties as $property) {
80
            /**
81
             * @var ColumnSecurity
82
             */
83
            $annotation = $this->reader->getPropertyAnnotation($property,
84
                ColumnSecurity::class);
85
86
            //Check if user is allowed to read info, otherwise apply placeholder
87
            if ((null !== $annotation) && !$this->security->isGranted($annotation->getReadOperationName(), $element)) {
88
                $property->setAccessible(true);
89
                $value = $annotation->getPlaceholder();
90
                if($value instanceof DBElement) {
91
                    $this->em->detach($value);
92
                }
93
                $property->setValue($element, $value);
94
            }
95
        }
96
    }
97
98
    /**
99
     * @ORM\PreFlush()
100
     * This function is called before flushing. We use it, to remove all placeholder DBElements (with name=???),
101
     * so we dont get a no cascade persistance error.
102
     */
103
    public function preFlushHandler(DBElement $element, PreFlushEventArgs $eventArgs)
0 ignored issues
show
Unused Code introduced by
The parameter $eventArgs is not used and could be removed. ( Ignorable by Annotation )

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

103
    public function preFlushHandler(DBElement $element, /** @scrutinizer ignore-unused */ PreFlushEventArgs $eventArgs)

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
104
    {
105
        //$eventArgs->getEntityManager()->getUnitOfWork()->
106
107
        $reflectionClass = new ReflectionClass($element);
108
        $properties = $reflectionClass->getProperties();
109
110
        foreach ($properties as $property) {
111
            $annotation = $this->reader->getPropertyAnnotation(
112
                $property,
113
                ColumnSecurity::class
114
            );
115
            if (null !== $annotation) {
116
                //Check if the current property is an DBElement
117
                $property->setAccessible(true);
118
                $value = $property->getValue($element);
119
                if ($value instanceof DBElement && !$this->security->isGranted($annotation->getEditOperationName(), $element)) {
120
                    $property->setValue($element, null);
121
                }
122
            }
123
        }
124
    }
125
126
    /**
127
     * @PreUpdate
128
     * This function is called before Doctrine saves the property values to the database.
129
     * We use this function to revert the changes made in postLoadHandler(), so nothing gets changed persistently.
130
     */
131
    public function preUpdateHandler(DBElement $element, PreUpdateEventArgs $event)
132
    {
133
        $reflectionClass = new ReflectionClass($element);
134
        $properties = $reflectionClass->getProperties();
135
136
        foreach ($properties as $property) {
137
            /**
138
             * @var ColumnSecurity $annotation
139
             */
140
            $annotation = $this->reader->getPropertyAnnotation($property,
141
                ColumnSecurity::class);
142
143
            if (null !== $annotation) {
144
                $field_name = $property->getName();
145
146
                //Check if user is allowed to edit info, otherwise overwrite the new value
147
                // so that nothing is changed in the DB.
148
                if ($event->hasChangedField($field_name) &&
149
                    (!$this->security->isGranted($annotation->getEditOperationName(), $element)
150
                    || !$this->security->isGranted($annotation->getReadOperationName(), $element))) {
151
                    $event->setNewValue($field_name, $event->getOldValue($field_name));
152
                }
153
            }
154
        }
155
    }
156
}
157