Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 232
0.00% covered (danger)
0.00%
0 / 13
CRAP
0.00% covered (danger)
0.00%
0 / 1
tao_actions_Users
0.00% covered (danger)
0.00%
0 / 232
0.00% covered (danger)
0.00%
0 / 13
3080
0.00% covered (danger)
0.00%
0 / 1
 getEventManager
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 index
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
2
 data
0.00% covered (danger)
0.00%
0 / 88
0.00% covered (danger)
0.00%
0 / 1
272
 delete
0.00% covered (danger)
0.00%
0 / 19
0.00% covered (danger)
0.00%
0 / 1
30
 add
0.00% covered (danger)
0.00%
0 / 23
0.00% covered (danger)
0.00%
0 / 1
20
 addInstanceForm
0.00% covered (danger)
0.00%
0 / 14
0.00% covered (danger)
0.00%
0 / 1
20
 checkLogin
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 1
12
 edit
0.00% covered (danger)
0.00%
0 / 39
0.00% covered (danger)
0.00%
0 / 1
110
 unlock
0.00% covered (danger)
0.00%
0 / 14
0.00% covered (danger)
0.00%
0 / 1
12
 lock
0.00% covered (danger)
0.00%
0 / 14
0.00% covered (danger)
0.00%
0 / 1
12
 getUserResource
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
6
 checkUser
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
6
 getUserLocksService
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
1<?php
2
3/**
4 * This program is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU General Public License
6 * as published by the Free Software Foundation; under version 2
7 * of the License (non-upgradable).
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with this program; if not, write to the Free Software
16 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
17 *
18 * Copyright (c) 2002-2008 (original work) Public Research Centre Henri Tudor & University of Luxembourg
19 *                         (under the project TAO & TAO2);
20 *               2008-2010 (update and modification) Deutsche Institut für Internationale Pädagogische Forschung
21 *                         (under the project TAO-TRANSFER);
22 *               2009-2012 (update and modification) Public Research Centre Henri Tudor
23 *                         (under the project TAO-SUSTAIN & TAO-DEV);
24 *               2013-2018 (update and modification) Open Assessment Technologies SA;
25 *
26 */
27
28use oat\generis\model\user\UserRdf;
29use oat\generis\Helper\UserHashForEncryption;
30use oat\generis\model\GenerisRdf;
31use oat\generis\model\OntologyAwareTrait;
32use oat\oatbox\event\EventManager;
33use oat\tao\helpers\ApplicationHelper;
34use oat\tao\helpers\UserHelper;
35use oat\tao\model\TaoOntology;
36use oat\tao\model\user\UserLocks;
37use oat\oatbox\user\UserLanguageServiceInterface;
38use oat\oatbox\log\LoggerAwareTrait;
39use tao_helpers_form_FormContainer as FormContainer;
40use tao_helpers_Display as DisplayHelper;
41
42/**
43 * This controller provide the actions to manage the application users (list/add/edit/delete)
44 *
45 * @author CRP Henri Tudor - TAO Team - {@link http://www.tao.lu}
46 * @license GPLv2  http://www.opensource.org/licenses/gpl-2.0.php
47 * @package tao
48 *
49 */
50class tao_actions_Users extends tao_actions_CommonModule
51{
52    use OntologyAwareTrait;
53    use LoggerAwareTrait;
54
55    /**
56     * @return EventManager
57     */
58    protected function getEventManager()
59    {
60        return $this->getServiceLocator()->get(EventManager::SERVICE_ID);
61    }
62
63    /**
64     * Show the list of users
65     * @return void
66     */
67    public function index()
68    {
69        $this->defaultData();
70        $userLangService = $this->getServiceLocator()->get(UserLanguageServiceInterface::class);
71        $this->setData('user-data-lang-enabled', $userLangService->isDataLanguageEnabled());
72        $this->setView('user/list.tpl');
73    }
74
75    /**
76     * Provide the user list data via json
77     * @return string|json
78     * @throws Exception
79     * @throws common_exception_InvalidArgumentType
80     */
81    public function data()
82    {
83        $userService = $this->getServiceLocator()->get(tao_models_classes_UserService::class);
84        $userLangService = $this->getServiceLocator()->get(UserLanguageServiceInterface::class);
85        $page = $this->getRequestParameter('page');
86        $limit = $this->getRequestParameter('rows');
87        $sortBy = $this->getRequestParameter('sortby');
88        $sortOrder = $this->getRequestParameter('sortorder');
89        $filterQuery = $this->getRequestParameter('filterquery');
90        $filterColumns = $this->getRequestParameter('filtercolumns');
91        $start = $limit * $page - $limit;
92
93        $fieldsMap = [
94            'login' => GenerisRdf::PROPERTY_USER_LOGIN,
95            'firstname' => GenerisRdf::PROPERTY_USER_FIRSTNAME,
96            'lastname' => GenerisRdf::PROPERTY_USER_LASTNAME,
97            'email' => GenerisRdf::PROPERTY_USER_MAIL,
98            'guiLg' => GenerisRdf::PROPERTY_USER_UILG,
99            'roles' => GenerisRdf::PROPERTY_USER_ROLES
100        ];
101        if ($userLangService->isDataLanguageEnabled()) {
102            $fieldsMap['dataLg'] = GenerisRdf::PROPERTY_USER_DEFLG;
103        }
104
105        // sorting
106        $order = array_key_exists($sortBy, $fieldsMap) ? $fieldsMap[$sortBy] : $fieldsMap['login'];
107
108        // filtering
109        $filters = [
110            GenerisRdf::PROPERTY_USER_LOGIN => '*',
111        ];
112
113        if ($filterQuery) {
114            if (!$filterColumns) {
115                // if filter columns not set, search by all columns
116                $filterColumns = array_keys($fieldsMap);
117            }
118            $filters = array_flip(array_intersect_key($fieldsMap, array_flip($filterColumns)));
119            array_walk($filters, function (&$row, $key) use ($filterQuery) {
120                $row = $filterQuery;
121            });
122        }
123
124        $options = [
125            'recursive' => true,
126            'like' => true,
127            'chaining' => count($filters) > 1 ? 'or' : 'and',
128            'order' => $order,
129            'orderdir' => strtoupper($sortOrder),
130        ];
131
132        // get total user count...
133        $total = $userService->getCountUsers($options, $filters);
134
135        // get the users using requested paging...
136        $users = $userService->getAllUsers(array_merge($options, [
137            'offset' => $start,
138            'limit' => $limit
139        ]), $filters);
140
141        $rolesProperty = $this->getProperty(GenerisRdf::PROPERTY_USER_ROLES);
142
143        $response = new stdClass();
144        $readonly = [];
145        $index = 0;
146
147        /** @var core_kernel_classes_Resource $user */
148        foreach ($users as $user) {
149            $propValues = $user->getPropertiesValues(array_values($fieldsMap));
150
151            $roles = $user->getPropertyValues($rolesProperty);
152            $labels = [];
153
154            foreach ($roles as $uri) {
155                $labels[] = $this->getResource($uri)->getLabel();
156            }
157
158            $id = tao_helpers_Uri::encode($user->getUri());
159            $login = (string)current($propValues[GenerisRdf::PROPERTY_USER_LOGIN]);
160            $firstName = empty($propValues[GenerisRdf::PROPERTY_USER_FIRSTNAME])
161                ? ''
162                : (string)current($propValues[GenerisRdf::PROPERTY_USER_FIRSTNAME]);
163            $lastName = empty($propValues[GenerisRdf::PROPERTY_USER_LASTNAME])
164                ? ''
165                : (string)current($propValues[GenerisRdf::PROPERTY_USER_LASTNAME]);
166            $uiRes = empty($propValues[GenerisRdf::PROPERTY_USER_UILG])
167                ? null
168                : current($propValues[GenerisRdf::PROPERTY_USER_UILG]);
169            if ($userLangService->isDataLanguageEnabled()) {
170                $dataRes = empty($propValues[GenerisRdf::PROPERTY_USER_DEFLG])
171                    ? null
172                    : current($propValues[GenerisRdf::PROPERTY_USER_DEFLG]);
173                $response->data[$index]['dataLg'] = is_null($dataRes) ? '' : $dataRes->getLabel();
174            }
175
176            $email = (string)current($propValues[GenerisRdf::PROPERTY_USER_MAIL]);
177            $response->data[$index]['id'] = $id;
178            $response->data[$index]['login'] = DisplayHelper::htmlEscape($login);
179            $response->data[$index]['firstname'] = DisplayHelper::htmlEscape($firstName);
180            $response->data[$index]['lastname'] = DisplayHelper::htmlEscape($lastName);
181            $response->data[$index]['email'] = DisplayHelper::htmlEscape($email);
182            $response->data[$index]['roles'] = implode(', ', $labels);
183            $response->data[$index]['guiLg'] = is_null($uiRes) ? '' : $uiRes->getLabel();
184
185            $statusInfo = $this->getUserLocksService()->getStatusDetails($login);
186            $response->data[$index]['lockable'] = $statusInfo['lockable'];
187            $response->data[$index]['locked'] = $statusInfo['locked'];
188            $response->data[$index]['status'] = $statusInfo['status'];
189
190            if ($user->getUri() == LOCAL_NAMESPACE . TaoOntology::DEFAULT_USER_URI_SUFFIX) {
191                $readonly[$id] = true;
192            }
193
194            $index++;
195        }
196
197        $response->page = floor($start / $limit) + 1;
198        $response->total = ceil($total / $limit);
199        $response->records = count($users);
200        $response->readonly = $readonly;
201
202        $this->returnJson($response, 200);
203    }
204
205    /**
206     * Remove a user
207     * The request must contains the user's login to remove
208     * @return void
209     * @throws Exception
210     * @throws common_exception_Error
211     */
212    public function delete()
213    {
214        try {
215            $this->validateCsrf();
216        } catch (common_exception_Unauthorized $e) {
217            $this->response = $this->getPsrResponse()->withStatus(403, __('Unable to process your request'));
218            return;
219        }
220
221        $userService = $this->getServiceLocator()->get(tao_models_classes_UserService::class);
222
223        $deleted = false;
224        $message = __('An error occurred during user deletion');
225        if (ApplicationHelper::isDemo()) {
226            $message = __('User deletion not permitted on a demo instance');
227        } elseif ($this->hasRequestParameter('uri')) {
228            $user = $this->getResource(tao_helpers_Uri::decode($this->getRequestParameter('uri')));
229            $this->checkUser($user->getUri());
230
231            if ($userService->removeUser($user)) {
232                $deleted = true;
233                $message = __('User deleted successfully');
234            }
235        }
236        $this->returnJson([
237            'success' => $deleted,
238            'message' => $message
239        ]);
240    }
241
242    /**
243     * form to add a user
244     * @return void
245     * @throws Exception
246     * @throws \oat\generis\model\user\PasswordConstraintsException
247     * @throws tao_models_classes_dataBinding_GenerisFormDataBindingException
248     */
249    public function add()
250    {
251        $this->defaultData();
252        $container = new tao_actions_form_Users(
253            $this->getClass(TaoOntology::CLASS_URI_TAO_USER),
254            null,
255            false,
256            [FormContainer::CSRF_PROTECTION_OPTION => true]
257        );
258        $form = $container->getForm();
259
260        if ($form->isSubmited() && $form->isValid()) {
261            $values = $form->getValues();
262            $plainPassword = $values['password1'];
263            unset($values['password1'], $values['password2']);
264
265            $values[UserRdf::PROPERTY_PASSWORD] = core_kernel_users_Service::getPasswordHash()
266                ->encrypt($plainPassword);
267            $hashForKey = UserHashForEncryption::hash($plainPassword);
268
269            /** @var tao_models_classes_UserService $userService */
270            $userService = $this->getServiceLocator()->get(tao_models_classes_UserService::SERVICE_ID);
271
272            if ($userService->triggerUpdatedEvent($container->getUser(), $values, $hashForKey)) {
273                $this->setData('message', __('User added'));
274                $this->setData('exit', true);
275            }
276        }
277
278        $this->setData('loginUri', tao_helpers_Uri::encode(GenerisRdf::PROPERTY_USER_LOGIN));
279        $this->setData('formTitle', __('Add a user'));
280        $this->setData('myForm', $form->render());
281        $this->setView('user/form.tpl');
282    }
283
284    /**
285     * @throws Exception
286     * @throws common_exception_BadRequest
287     */
288    public function addInstanceForm()
289    {
290        $this->defaultData();
291        if (!$this->isXmlHttpRequest()) {
292            throw new common_exception_BadRequest('wrong request mode');
293        }
294
295        $clazz = $this->getClass(TaoOntology::CLASS_URI_TAO_USER);
296        $formContainer = new tao_actions_form_CreateInstance([$clazz], [FormContainer::CSRF_PROTECTION_OPTION => true]);
297        $form = $formContainer->getForm();
298
299        if ($form->isSubmited() && $form->isValid()) {
300            $properties = $form->getValues();
301            $instance = $this->createInstance([$clazz], $properties);
302
303            $this->setData('message', __('%s created', $instance->getLabel()));
304            $this->setData('selectTreeNode', $instance->getUri());
305        }
306
307        $this->setData('formTitle', __('Create instance of ') . $clazz->getLabel());
308        $this->setData('myForm', $form->render());
309
310        $this->setView('form.tpl', 'tao');
311    }
312
313    /**
314     * action used to check if a login can be used
315     * @return void
316     * @throws Exception
317     * @throws common_exception_BadRequest
318     */
319    public function checkLogin()
320    {
321        $this->defaultData();
322        $userService = $this->getServiceLocator()->get(tao_models_classes_UserService::class);
323        if (!$this->isXmlHttpRequest()) {
324            throw new common_exception_BadRequest('wrong request mode');
325        }
326
327        $data = ['available' => false];
328        if ($this->hasRequestParameter('login')) {
329            $data['available'] = $userService->loginAvailable($this->getRequestParameter('login'));
330        }
331
332        $this->returnJson($data);
333    }
334
335    /**
336     * Form to edit a user
337     * User login must be set in parameter
338     * @return void
339     * @throws Exception
340     * @throws \oat\generis\model\user\PasswordConstraintsException
341     * @throws common_exception_Error
342     * @throws tao_models_classes_dataBinding_GenerisFormDataBindingException
343     */
344    public function edit()
345    {
346        $this->defaultData();
347        $user = $this->getUserResource();
348
349        $types = $user->getTypes();
350        $myFormContainer = new tao_actions_form_Users(
351            reset($types),
352            $user,
353            false,
354            [FormContainer::CSRF_PROTECTION_OPTION => true]
355        );
356        $myForm = $myFormContainer->getForm();
357
358        if ($myForm->isSubmited() && $myForm->isValid()) {
359            $values = $myForm->getValues();
360            $hashForKey = null;
361
362            if (!empty($values['password2']) && !empty($values['password3'])) {
363                $plainPassword = $values['password2'];
364                $values[UserRdf::PROPERTY_PASSWORD] = core_kernel_users_Service::getPasswordHash()
365                    ->encrypt($plainPassword);
366                $hashForKey = UserHashForEncryption::hash($plainPassword);
367            }
368
369            unset($values['password2'], $values['password3']);
370
371            if (
372                isset($values[GenerisRdf::PROPERTY_USER_UILG])
373                && !preg_match('/[A-Z]{2,4}$/', trim($values[GenerisRdf::PROPERTY_USER_UILG]))
374            ) {
375                unset($values[GenerisRdf::PROPERTY_USER_UILG]);
376            }
377
378            if (
379                isset($values[GenerisRdf::PROPERTY_USER_DEFLG])
380                && !preg_match('/[A-Z]{2,4}$/', trim($values[GenerisRdf::PROPERTY_USER_DEFLG]))
381            ) {
382                unset($values[GenerisRdf::PROPERTY_USER_DEFLG]);
383            }
384
385            /** @var tao_models_classes_UserService $userService */
386            $userService = $this->getServiceLocator()->get(tao_models_classes_UserService::SERVICE_ID);
387            $userService->checkCurrentUserAccess($values[GenerisRdf::PROPERTY_USER_ROLES]);
388
389            // leave roles which are not in the allowed list for current user
390            $oldRoles = $userService->getUserRoles($user);
391            $allowedRoles = $userService->getPermittedRoles($userService->getCurrentUser(), $oldRoles, false);
392            $staticRoles = array_diff($oldRoles, $allowedRoles);
393            $values[GenerisRdf::PROPERTY_USER_ROLES] = array_merge(
394                $values[GenerisRdf::PROPERTY_USER_ROLES],
395                $staticRoles
396            );
397
398            if ($userService->triggerUpdatedEvent($user, $values, $hashForKey)) {
399                $this->setData('message', __('User saved'));
400            }
401        }
402
403        $this->setData('formTitle', __('Edit a user'));
404        $this->setData('myForm', $myForm->render());
405        $this->setView('user/form.tpl');
406    }
407
408    /**
409     * Removes all locks from user account
410     * @throws Exception
411     */
412    public function unlock()
413    {
414        try {
415            $this->validateCsrf();
416        } catch (common_exception_Unauthorized $e) {
417            $this->response = $this->getPsrResponse()->withStatus(403, __('Unable to process your request'));
418            return;
419        }
420        $user = UserHelper::getUser($this->getUserResource());
421
422        if ($this->getUserLocksService()->unlockUser($user)) {
423            $this->returnJson([
424                'success' => true,
425                'message' => __('User %s successfully unlocked', UserHelper::getUserLogin(UserHelper::getUser($user)))
426            ]);
427        } else {
428            $this->returnJson([
429                'success' => false,
430                'message' => __('User %s can not be unlocked', UserHelper::getUserLogin(UserHelper::getUser($user)))
431            ]);
432        }
433    }
434
435    /**
436     * Locks user account, he can not login in to the system anymore
437     * @throws Exception
438     */
439    public function lock()
440    {
441        try {
442            $this->validateCsrf();
443        } catch (common_exception_Unauthorized $e) {
444            $this->response = $this->getPsrResponse()->withStatus(403, __('Unable to process your request'));
445            return;
446        }
447
448        $user = UserHelper::getUser($this->getUserResource());
449
450        if ($this->getUserLocksService()->lockUser($user)) {
451            $this->returnJson([
452                'success' => true,
453                'message' => __('User %s successfully locked', UserHelper::getUserLogin(UserHelper::getUser($user)))
454            ]);
455        } else {
456            $this->returnJson([
457                'success' => false,
458                'message' => __('User %s can not be locked', UserHelper::getUserLogin(UserHelper::getUser($user)))
459            ]);
460        }
461    }
462
463    /**
464     * @throws common_exception_MissingParameter
465     * @throws Exception
466     * @return core_kernel_classes_Resource
467     */
468    private function getUserResource()
469    {
470        if (!$this->hasRequestParameter('uri')) {
471            throw new common_exception_MissingParameter('uri');
472        }
473
474        $userUri = tao_helpers_Uri::decode($this->getRequestParameter('uri'));
475        $this->checkUser($userUri);
476
477        return $this->getResource($userUri);
478    }
479
480    /**
481     * Check whether user user data can be changed
482     * @param $uri
483     * @throws Exception
484     */
485    private function checkUser($uri)
486    {
487        if ($uri === LOCAL_NAMESPACE . TaoOntology::DEFAULT_USER_URI_SUFFIX) {
488            throw new Exception('Default user data cannot be changed');
489        }
490    }
491
492    /**
493     * @return UserLocks
494     */
495    protected function getUserLocksService()
496    {
497        return $this->getServiceLocator()->get(UserLocks::SERVICE_ID);
498    }
499}