Completed
Push — master ( be5baf...1af7e4 )
by Julito
11:10
created

ResourceNodeVoter::getReaderMask()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 8
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 4
nc 1
nop 0
dl 0
loc 8
rs 10
c 0
b 0
f 0
1
<?php
2
3
/* For licensing terms, see /license.txt */
4
5
namespace Chamilo\CoreBundle\Security\Authorization\Voter;
6
7
use Chamilo\CoreBundle\Entity\Course;
8
use Chamilo\CoreBundle\Entity\ResourceLink;
9
use Chamilo\CoreBundle\Entity\ResourceNode;
10
use Chamilo\CoreBundle\Entity\ResourceRight;
11
use Chamilo\CoreBundle\Entity\Session;
12
use Doctrine\ORM\EntityManagerInterface;
13
use Laminas\Permissions\Acl\Acl;
14
use Laminas\Permissions\Acl\Resource\GenericResource as SecurityResource;
15
use Laminas\Permissions\Acl\Role\GenericRole as Role;
16
use Symfony\Component\HttpFoundation\RequestStack;
17
use Symfony\Component\Security\Acl\Permission\MaskBuilder;
18
use Symfony\Component\Security\Core\Authentication\Token\AnonymousToken;
19
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
20
use Symfony\Component\Security\Core\Authorization\Voter\Voter;
21
use Symfony\Component\Security\Core\Security;
22
use Symfony\Component\Security\Core\User\UserInterface;
23
24
/**
25
 * Class ResourceNodeVoter.
26
 */
27
class ResourceNodeVoter extends Voter
28
{
29
    public const VIEW = 'VIEW';
30
    public const CREATE = 'CREATE';
31
    public const EDIT = 'EDIT';
32
    public const DELETE = 'DELETE';
33
    public const EXPORT = 'EXPORT';
34
35
    public const ROLE_CURRENT_COURSE_TEACHER = 'ROLE_CURRENT_COURSE_TEACHER';
36
    public const ROLE_CURRENT_COURSE_STUDENT = 'ROLE_CURRENT_COURSE_STUDENT';
37
    public const ROLE_CURRENT_SESSION_COURSE_TEACHER = 'ROLE_CURRENT_SESSION_COURSE_TEACHER';
38
    public const ROLE_CURRENT_SESSION_COURSE_STUDENT = 'ROLE_CURRENT_SESSION_COURSE_STUDENT';
39
40
    private $requestStack;
41
    private $security;
42
    private $entityManager;
43
44
    /**
45
     * Constructor.
46
     */
47
    public function __construct(Security $security, RequestStack $requestStack, EntityManagerInterface $entityManager)
48
    {
49
        $this->security = $security;
50
        $this->entityManager = $entityManager;
51
        $this->requestStack = $requestStack;
52
    }
53
54
    public static function getReaderMask(): int
55
    {
56
        $builder = new MaskBuilder();
57
        $builder
58
            ->add(self::VIEW)
59
        ;
60
61
        return $builder->get();
62
    }
63
64
    public static function getEditorMask(): int
65
    {
66
        $builder = new MaskBuilder();
67
        $builder
68
            ->add(self::VIEW)
69
            ->add(self::EDIT)
70
        ;
71
72
        return $builder->get();
73
    }
74
75
    protected function supports(string $attribute, $subject): bool
76
    {
77
        $options = [
78
            self::VIEW,
79
            self::CREATE,
80
            self::EDIT,
81
            self::DELETE,
82
            self::EXPORT,
83
        ];
84
85
        // if the attribute isn't one we support, return false
86
        if (!in_array($attribute, $options)) {
87
            return false;
88
        }
89
90
        // only vote on ResourceNode objects inside this voter
91
        if (!$subject instanceof ResourceNode) {
92
            return false;
93
        }
94
95
        return true;
96
    }
97
98
    protected function voteOnAttribute(string $attribute, $subject, TokenInterface $token): bool
99
    {
100
        $user = $token->getUser();
101
102
        // Make sure there is a user object (i.e. that the user is logged in)
103
        // Update. No, anons can enter a node depending in the visibility.
104
        /*if (!$user instanceof UserInterface) {
105
            return false;
106
        }*/
107
108
        /** @var ResourceNode $resourceNode */
109
        $resourceNode = $subject;
110
111
        // Illustrations are always visible.
112
        if ('illustrations' === $resourceNode->getResourceType()->getName()) {
113
            return true;
114
        }
115
116
        // Courses are also a ResourceNode. Courses are protected using the CourseVoter not by ResourceNodeVoter.
117
        if ('courses' === $resourceNode->getResourceType()->getName()) {
118
            return true;
119
        }
120
121
        // Checking admin role.
122
        if ($this->security->isGranted('ROLE_ADMIN')) {
123
            return true;
124
        }
125
126
        // Check if I'm the owner.
127
        $creator = $resourceNode->getCreator();
128
        if ($creator instanceof UserInterface &&
129
            $user instanceof UserInterface &&
130
            $user->getUsername() === $creator->getUsername()) {
131
            return true;
132
        }
133
134
        // Checking links connected to this resource.
135
        $request = $this->requestStack->getCurrentRequest();
136
137
        // @todo fix parameters.
138
        $courseId = $request->get('cid');
139
        $sessionId = $request->get('sid');
140
141
        $links = $resourceNode->getResourceLinks();
142
        $linkFound = false;
143
        $courseManager = $this->entityManager->getRepository(Course::class);
144
        $sessionManager = $this->entityManager->getRepository(Session::class);
145
146
        // @todo implement view, edit, delete.
147
        foreach ($links as $link) {
148
            // Block access if visibility is deleted. Creator and admin already can access before.
149
            if (ResourceLink::VISIBILITY_DELETED === $link->getVisibility()) {
150
                $linkFound = false;
151
152
                break;
153
            }
154
155
            // Check if resource was sent to the current user.
156
            $linkUser = $link->getUser();
157
            if ($linkUser instanceof UserInterface && $linkUser->getUsername() === $creator->getUsername()) {
158
                $linkFound = true;
159
160
                break;
161
            }
162
163
            $linkCourse = $link->getCourse();
164
            $linkSession = $link->getSession();
165
            //$linkUserGroup = $link->getUserGroup();
166
167
            // Course found, but courseId not set, skip course checking.
168
            if ($linkCourse instanceof Course && empty($courseId)) {
169
                continue;
170
            }
171
172
            // @todo Check if resource was sent to a usergroup
173
            // @todo Check if resource was sent to a group inside a course
174
            // Check if resource was sent to a course inside a session
175
            if ($linkSession instanceof Session && !empty($sessionId) &&
176
                $linkCourse instanceof Course && !empty($courseId)
177
            ) {
178
                $session = $sessionManager->find($sessionId);
179
                $course = $courseManager->find($courseId);
180
                if ($session instanceof Session &&
181
                    $course instanceof Course &&
182
                    $linkCourse->getCode() === $course->getCode() &&
183
                    $linkSession->getId() === $session->getId()
184
                ) {
185
                    $linkFound = true;
186
187
                    break;
188
                }
189
            }
190
191
            // Check if resource was sent to a course
192
            if ($linkCourse instanceof Course && !empty($courseId)) {
193
                $course = $courseManager->find($courseId);
194
195
                if ($course instanceof Course &&
196
                    $linkCourse->getCode() === $course->getCode()
197
                ) {
198
                    $linkFound = true;
199
200
                    break;
201
                }
202
            }
203
204
            if (ResourceLink::VISIBILITY_PUBLISHED === $link->getVisibility()) {
205
                $linkFound = true;
206
207
                break;
208
            }
209
        }
210
211
        // No link was found or not available.
212
        if (false === $linkFound) {
213
            return false;
214
        }
215
216
        // Getting rights from the link
217
        $rightFromResourceLink = $link->getResourceRight();
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $link seems to be defined by a foreach iteration on line 147. Are you sure the iterator is never empty, otherwise this variable is not defined?
Loading history...
218
219
        $rights = [];
220
        if ($rightFromResourceLink->count() > 0) {
221
            // Taken rights from the link
222
            $rights = $rightFromResourceLink;
223
        } else {
224
            // Taken the rights from the default tool
225
            //$rights = $link->getResourceNode()->getTool()->getToolResourceRight();
226
            //$rights = $link->getResourceNode()->getResourceType()->getTool()->getToolResourceRight();
227
228
            // By default the rights are:
229
            // Teachers: CRUD.
230
            // Students: Only read.
231
            // Anons: Only read.
232
            $readerMask = self::getReaderMask();
233
            $editorMask = self::getEditorMask();
234
235
            if (!empty($courseId)) {
236
                $resourceRight = new ResourceRight();
237
                $resourceRight
238
                    ->setMask($editorMask)
239
                    ->setRole(self::ROLE_CURRENT_COURSE_TEACHER);
240
                $rights[] = $resourceRight;
241
242
                $resourceRight = new ResourceRight();
243
                $resourceRight
244
                    ->setMask($readerMask)
245
                    ->setRole(self::ROLE_CURRENT_COURSE_STUDENT);
246
                $rights[] = $resourceRight;
247
248
                if ($course->isPublic() && ResourceLink::VISIBILITY_PUBLISHED === $link->getVisibility()) {
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $course does not seem to be defined for all execution paths leading up to this point.
Loading history...
249
                    $resourceRight = new ResourceRight();
250
                    $resourceRight
251
                        ->setMask($readerMask)
252
                        ->setRole('IS_AUTHENTICATED_ANONYMOUSLY');
253
                    $rights[] = $resourceRight;
254
                }
255
            }
256
257
            if (!empty($sessionId)) {
258
                $resourceRight = new ResourceRight();
259
                $resourceRight
260
                    ->setMask($editorMask)
261
                    ->setRole(self::ROLE_CURRENT_SESSION_COURSE_TEACHER)
262
                ;
263
                $rights[] = $resourceRight;
264
265
                $resourceRight = new ResourceRight();
266
                $resourceRight
267
                    ->setMask($readerMask)
268
                    ->setRole(self::ROLE_CURRENT_SESSION_COURSE_STUDENT)
269
                ;
270
                $rights[] = $resourceRight;
271
            }
272
273
            if (empty($rights) && ResourceLink::VISIBILITY_PUBLISHED === $link->getVisibility()) {
274
                // Give just read access
275
                $resourceRight = new ResourceRight();
276
                $resourceRight
277
                    ->setMask($readerMask)
278
                    ->setRole('ROLE_USER')
279
                ;
280
                $rights[] = $resourceRight;
281
            }
282
        }
283
284
        // Asked mask
285
        $mask = new MaskBuilder();
286
        $mask->add($attribute);
287
        $askedMask = $mask->get();
288
289
        // Setting Laminas simple ACL
290
        $acl = new Acl();
291
292
        // Creating roles
293
        // @todo move this in a service
294
        $anon = new Role('IS_AUTHENTICATED_ANONYMOUSLY');
295
        $userRole = new Role('ROLE_USER');
296
        $teacher = new Role('ROLE_TEACHER');
297
        $student = new Role('ROLE_STUDENT');
298
        $currentTeacher = new Role(self::ROLE_CURRENT_COURSE_TEACHER);
299
        $currentStudent = new Role(self::ROLE_CURRENT_COURSE_STUDENT);
300
301
        $currentTeacherSession = new Role(self::ROLE_CURRENT_SESSION_COURSE_TEACHER);
302
        $currentStudentSession = new Role(self::ROLE_CURRENT_SESSION_COURSE_STUDENT);
303
304
        $superAdmin = new Role('ROLE_SUPER_ADMIN');
305
        $admin = new Role('ROLE_ADMIN');
306
307
        // Adding roles to the ACL.
308
        $acl
309
            ->addRole($anon)
310
            ->addRole($userRole)
311
            ->addRole($student)
312
            ->addRole($teacher)
313
            ->addRole($currentStudent)
314
            ->addRole($currentTeacher, self::ROLE_CURRENT_COURSE_STUDENT)
315
            ->addRole($currentStudentSession)
316
            ->addRole($currentTeacherSession, self::ROLE_CURRENT_SESSION_COURSE_STUDENT)
317
            ->addRole($superAdmin)
318
            ->addRole($admin)
319
        ;
320
321
        // Add a resource.
322
        $resource = new SecurityResource($link);
323
        $acl->addResource($resource);
324
325
        // Check all the right this link has.
326
        // Set rights from the ResourceRight.
327
        foreach ($rights as $right) {
328
            $acl->allow($right->getRole(), null, $right->getMask());
329
        }
330
331
        // var_dump($askedMask, $roles);
332
        // Role and permissions settings
333
        // Student can just view (read)
334
        $acl->allow($student, null, self::getReaderMask());
335
336
        // Anons can see.
337
        $acl->allow($anon, null, self::getReaderMask());
338
339
        // Teacher can view/edit
340
        $acl->allow(
341
            $teacher,
342
            null,
343
            [
344
                self::getReaderMask(),
345
                self::getEditorMask(),
346
            ]
347
        );
348
349
        // Admin can do everything
350
        $acl->allow($admin);
351
        $acl->allow($superAdmin);
352
353
        if ($token instanceof AnonymousToken) {
354
            if ($acl->isAllowed('IS_AUTHENTICATED_ANONYMOUSLY', $resource, $askedMask)) {
355
                return true;
356
            }
357
358
            return false;
359
        }
360
361
        foreach ($user->getRoles() as $role) {
362
            if ($acl->isAllowed($role, $resource, $askedMask)) {
363
                return true;
364
            }
365
        }
366
367
        //dump('not allowed to '.$attribute);
368
        return false;
369
    }
370
}
371