Completed
Push — master ( 4c7767...ac3e5d )
by Jan
04:30
created

ElementPermissionListener::preFlushHandler()   B

Complexity

Conditions 7
Paths 8

Size

Total Lines 36
Code Lines 19

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 0 Features 0
Metric Value
cc 7
eloc 19
c 2
b 0
f 0
nc 8
nop 2
dl 0
loc 36
rs 8.8333
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\Collections\Collection;
36
use Doctrine\Common\Persistence\Event\LifecycleEventArgs;
37
use Doctrine\ORM\EntityManager;
38
use Doctrine\ORM\EntityManagerInterface;
39
use Doctrine\ORM\Event\PreFlushEventArgs;
40
use Doctrine\ORM\Event\PreUpdateEventArgs;
41
use Doctrine\ORM\Mapping as ORM;
42
use Doctrine\ORM\Mapping\PostLoad;
43
use Doctrine\ORM\Mapping\PreUpdate;
44
use ReflectionClass;
45
use Symfony\Component\Security\Core\Security;
46
47
/**
48
 * The purpose of this class is to hook into the doctrine entity lifecycle and restrict access to entity informations
49
 * configured by ColoumnSecurity Annotation.
50
 *
51
 * If a user does not have access to an coloumn, it will be filled, with a placeholder, after doctrine loading is finished.
52
 * The edit process is also catched, so that these placeholders, does not get saved to database.
53
 */
54
class ElementPermissionListener
55
{
56
    protected $security;
57
    protected $reader;
58
    protected $em;
59
60
    public function __construct(Security $security, Reader $reader, EntityManagerInterface $em)
61
    {
62
        $this->security = $security;
63
        $this->reader = $reader;
64
        $this->em = $em;
65
    }
66
67
    /**
68
     * @PostLoad
69
     * @ORM\PostUpdate()
70
     * This function is called after doctrine filled, the entity properties with db values.
71
     * We use this, to check if the user is allowed to access these properties, and if not, we write a placeholder
72
     * into the element properties, so that a user only gets non sensitve data.
73
     *
74
     * This function is also called after an entity was updated, so we dont show the original data to user,
75
     * after an update.
76
     */
77
    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

77
    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...
78
    {
79
        //Read Annotations and properties.
80
        $reflectionClass = new ReflectionClass($element);
81
        $properties = $reflectionClass->getProperties();
82
83
        foreach ($properties as $property) {
84
            /**
85
             * @var ColumnSecurity
86
             */
87
            $annotation = $this->reader->getPropertyAnnotation(
88
                $property,
89
                ColumnSecurity::class
90
            );
91
92
            //Check if user is allowed to read info, otherwise apply placeholder
93
            if ((null !== $annotation) && !$this->security->isGranted($annotation->getReadOperationName(), $element)) {
94
                $property->setAccessible(true);
95
                $value = $annotation->getPlaceholder();
96
97
                //Detach placeholder entities, so we dont get cascade errors
98
                if ($value instanceof DBElement) {
99
                    $this->em->detach($value);
100
                }
101
102
                $property->setValue($element, $value);
103
            }
104
        }
105
    }
106
107
    /**
108
     * @ORM\PreFlush()
109
     * This function is called before flushing. We use it, to remove all placeholders.
110
     * We do it here and not in preupdate, because this is called before calculating the changeset,
111
     * and so we dont get problems with orphan removal.
112
     */
113
    public function preFlushHandler(DBElement $element, PreFlushEventArgs $eventArgs)
114
    {
115
        $em = $eventArgs->getEntityManager();
0 ignored issues
show
Unused Code introduced by
The assignment to $em is dead and can be removed.
Loading history...
116
        $unitOfWork = $eventArgs->getEntityManager()->getUnitOfWork();
117
118
        $reflectionClass = new ReflectionClass($element);
119
        $properties = $reflectionClass->getProperties();
120
121
        $old_data = $unitOfWork->getOriginalEntityData($element);
122
123
        foreach ($properties as $property) {
124
            $annotation = $this->reader->getPropertyAnnotation(
125
                $property,
126
                ColumnSecurity::class
127
            );
128
129
            $changed = false;
130
131
            //Only set the field if it has an annotation
132
            if (null !== $annotation) {
133
                $property->setAccessible(true);
134
135
                //If the user is not allowed to edit or read this property, reset all values.
136
                if ((!$this->security->isGranted($annotation->getEditOperationName(), $element)
137
                    || !$this->security->isGranted($annotation->getReadOperationName(), $element))) {
138
                    //Set value to old value, so that there a no change to this property
139
                    if (isset($old_data[$property->getName()])) {
140
                        $property->setValue($element, $old_data[$property->getName()]);
141
                        $changed = true;
142
                    }
143
144
                }
145
146
                if ($changed) {
147
                    //Schedule for update, so the post update method will be called
148
                    $unitOfWork->scheduleForUpdate($element);
149
                }
150
            }
151
        }
152
    }
153
}
154