Completed
Push — master ( c814ba...8cfaee )
by Jan
04:18
created

ElementPermissionListener   A

Complexity

Total Complexity 19

Size/Duplication

Total Lines 122
Duplicated Lines 0 %

Importance

Changes 5
Bugs 0 Features 0
Metric Value
eloc 47
c 5
b 0
f 0
dl 0
loc 122
rs 10
wmc 19

4 Methods

Rating   Name   Duplication   Size   Complexity  
B preFlushHandler() 0 41 8
A postLoadHandler() 0 31 6
A isRunningFromCLI() 0 8 4
A __construct() 0 7 1
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\HttpKernel\KernelInterface;
46
use Symfony\Component\Security\Core\Security;
47
48
/**
49
 * The purpose of this class is to hook into the doctrine entity lifecycle and restrict access to entity informations
50
 * configured by ColoumnSecurity Annotation.
51
 * If the current programm is running from CLI (like a CLI command), the security checks are disabled.
52
 * (Commands should be able to do everything they like)
53
 *
54
 * If a user does not have access to an coloumn, it will be filled, with a placeholder, after doctrine loading is finished.
55
 * The edit process is also catched, so that these placeholders, does not get saved to database.
56
 */
57
class ElementPermissionListener
58
{
59
    protected $security;
60
    protected $reader;
61
    protected $em;
62
    protected $disabled;
63
64
    public function __construct(Security $security, Reader $reader, EntityManagerInterface $em, KernelInterface $kernel)
0 ignored issues
show
Unused Code introduced by
The parameter $kernel 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

64
    public function __construct(Security $security, Reader $reader, EntityManagerInterface $em, /** @scrutinizer ignore-unused */ KernelInterface $kernel)

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...
65
    {
66
        $this->security = $security;
67
        $this->reader = $reader;
68
        $this->em = $em;
69
        //Disable security when the current program is running from CLI
70
        $this->disabled = $this->isRunningFromCLI();
71
    }
72
73
    /**
74
     * This function checks if the current script is run from web or from a terminal.
75
     * @return bool Returns true if the current programm is running from CLI (terminal)
76
     */
77
    protected function isRunningFromCLI()
78
    {
79
80
        if (empty($_SERVER['REMOTE_ADDR']) && !isset($_SERVER['HTTP_USER_AGENT']) && count($_SERVER['argv']) > 0) {
81
            return true;
82
        }
83
84
        return false;
85
    }
86
87
    /**
88
     * @PostLoad
89
     * @ORM\PostUpdate()
90
     * This function is called after doctrine filled, the entity properties with db values.
91
     * We use this, to check if the user is allowed to access these properties, and if not, we write a placeholder
92
     * into the element properties, so that a user only gets non sensitve data.
93
     *
94
     * This function is also called after an entity was updated, so we dont show the original data to user,
95
     * after an update.
96
     */
97
    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

97
    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...
98
    {
99
        //Do nothing if security is disabled
100
        if ($this->disabled) {
101
            return;
102
        }
103
104
        //Read Annotations and properties.
105
        $reflectionClass = new ReflectionClass($element);
106
        $properties = $reflectionClass->getProperties();
107
108
        foreach ($properties as $property) {
109
            /**
110
             * @var ColumnSecurity
111
             */
112
            $annotation = $this->reader->getPropertyAnnotation(
113
                $property,
114
                ColumnSecurity::class
115
            );
116
117
            //Check if user is allowed to read info, otherwise apply placeholder
118
            if ((null !== $annotation) && !$this->security->isGranted($annotation->getReadOperationName(), $element)) {
119
                $property->setAccessible(true);
120
                $value = $annotation->getPlaceholder();
121
122
                //Detach placeholder entities, so we dont get cascade errors
123
                if ($value instanceof DBElement) {
124
                    $this->em->detach($value);
125
                }
126
127
                $property->setValue($element, $value);
128
            }
129
        }
130
    }
131
132
    /**
133
     * @ORM\PreFlush()
134
     * This function is called before flushing. We use it, to remove all placeholders.
135
     * We do it here and not in preupdate, because this is called before calculating the changeset,
136
     * and so we dont get problems with orphan removal.
137
     */
138
    public function preFlushHandler(DBElement $element, PreFlushEventArgs $eventArgs)
139
    {
140
        //Do nothing if security is disabled
141
        if ($this->disabled) {
142
            return;
143
        }
144
145
        $em = $eventArgs->getEntityManager();
0 ignored issues
show
Unused Code introduced by
The assignment to $em is dead and can be removed.
Loading history...
146
        $unitOfWork = $eventArgs->getEntityManager()->getUnitOfWork();
147
148
        $reflectionClass = new ReflectionClass($element);
149
        $properties = $reflectionClass->getProperties();
150
151
        $old_data = $unitOfWork->getOriginalEntityData($element);
152
153
        foreach ($properties as $property) {
154
            $annotation = $this->reader->getPropertyAnnotation(
155
                $property,
156
                ColumnSecurity::class
157
            );
158
159
            $changed = false;
160
161
            //Only set the field if it has an annotation
162
            if (null !== $annotation) {
163
                $property->setAccessible(true);
164
165
                //If the user is not allowed to edit or read this property, reset all values.
166
                if ((!$this->security->isGranted($annotation->getEditOperationName(), $element)
167
                    || !$this->security->isGranted($annotation->getReadOperationName(), $element))) {
168
                    //Set value to old value, so that there a no change to this property
169
                    if (isset($old_data[$property->getName()])) {
170
                        $property->setValue($element, $old_data[$property->getName()]);
171
                        $changed = true;
172
                    }
173
174
                }
175
176
                if ($changed) {
177
                    //Schedule for update, so the post update method will be called
178
                    $unitOfWork->scheduleForUpdate($element);
179
                }
180
            }
181
        }
182
    }
183
}
184