Skip to content
Snippets Groups Projects
UserController.php 28.2 KiB
Newer Older
<?php
/**
 * Copyright (C) 2019 Leipzig University Library
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 as
 * published by the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with this program; if not, write to the Free Software Foundation, Inc.,
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * @author   Gregor Gawol <gawol@ub.uni-leipzig.de>
 * @author   Sebastian Kehr <kehr@ub.uni-leipzig.de>
 * @license  http://opensource.org/licenses/gpl-2.0.php GNU GPLv2
 */

namespace fid\Controller;

use fid\FormModel\PasswordChangeModel;
use fid\FormModel\PasswordResetModel;
use fid\FormModel\UserCreateModel;
use fid\FormModel\UserInitModel;
use fid\FormModel\UserUpdateModel;
use fid\FormModel\UsernameChangeModel;
use fid\Service\Client;
use fid\Service\ClientException;
use fid\Service\DataTransferObject\Address;
use fid\Service\DataTransferObject\Library;
use fid\Service\DataTransferObject\User;
use fid\Service\UserNotAuthorizedException;
use Symfony\Component\Serializer\SerializerAwareTrait;
use VuFind\Auth\Manager as AuthManager;
use VuFind\Controller\AbstractBase;
use VuFind\Exception\Auth as AuthException;
use Zend\Form\Element\MultiCheckbox;
use Zend\Form\Element\Radio;
use Zend\Form\Element\Select;
use Zend\Form\Form;
use Zend\Http\PhpEnvironment\Request;
use Zend\Http\Response;
use Zend\Mvc\Plugin\FlashMessenger\FlashMessenger;
use Zend\ServiceManager\ServiceLocatorInterface;
use Zend\View\Model\ViewModel;

class UserController extends AbstractBase
{
    use SerializerAwareTrait;

    /**

    /**
     * @var Client
     */
    protected $client;

    /**
     * @var array
     */
    protected $config;

    /**
     * RegistrationController constructor.
     *
     * @param ServiceLocatorInterface $serviceLocator
     * @param AuthManager             $authManager
     * @param Client                  $client
     * @param array                   $config
     */
    public function __construct(
        ServiceLocatorInterface $serviceLocator,
        Client $client,
        array $config
    ) {
        parent::__construct($serviceLocator);
        $this->authManager = $authManager;
        $this->client = $client;
        $this->config = $config;
     * @noinspection PhpUnused
     * @return ViewModel
     */
    public function initAction()
    {
        /** @var Request $request */
        $request = $this->getRequest();
        $form = $this->serviceLocator->get(UserInitModel::class);
        $forwarded = $this->params()->fromRoute('forwarded', false);

        if ($submitted = $this->formWasSubmitted()) {
            $form->setData($request->getPost());
            if (!$forwarded && $form->isValid()) {
                return $this->init($form);
            }
        }

        $view = $this->createViewModel();
        $view->setVariables(compact('form'));
        $view->setTemplate('fid/user/init');

        return $view;
    }

    /**
     * @noinspection PhpUnused
     * @return Response|ViewModel
     */
    public function createAction()
    {
        /** @var Request $request */
        $request = $this->getRequest();
        $query = $request->getQuery();
        $messenger = $this->getMessenger();

        if ($credentials = $query->get('logon')) {
            try {
                $this->client->logon($credentials);
            } catch (ClientException $exception) {
                $message = $exception->getCode() === 401
                    ? 'fid::user_create_error_expired'
                    : 'fid::user_create_error';
                $messenger->addErrorMessage($this->translate($message));
                return $this->redirect()->toRoute('fid/user/init');
            }

            $query->offsetUnset('logon');
            return $this->redirect()->toRoute('fid/user/create', [], [
                'query' => $query->toArray()
            ]);
        }

        try {
            $libraries = array_map(function (Library $libary) {
                return $libary->getLabel();
            }, $this->client->requestLibraryList());
        } catch (ClientException $exception) {
            $message = 'fid::user_create_error';
            $messenger->addErrorMessage($this->translate($message));
            return $this->redirect()->toRoute('fid/user/init');
        }

        /** @var Form $form */
        $form = $this->serviceLocator->get(UserCreateModel::class);

        /** @var Select $homeLibraryElement */
        $homeLibraryElement = $form->get('homeLibrary');
        $homeLibraryElement->setValueOptions($libraries);

        /** @var Radio $accessLevelElement */
        $accessLevelElement = $form->get('accessLevel');
        $accessLevels = $this->config['Security']['access_levels'];
        $accessLevelValueOptions = array_map(function ($accessLevel) {
            return "label_access_level_$accessLevel";
        }, array_combine($accessLevels, $accessLevels));
        $accessLevelElement->setValueOptions($accessLevelValueOptions);

        if ($this->formWasSubmitted()) {
            $form->setData($request->getPost());
            if ($form->isValid()) {
                return $this->create($form);
            }
        } else {
            $form->setData($query);
        }

        $view = $this->createViewModel();
        $view->setVariables(compact('form'));
        $view->setTemplate('fid/user/create');

        return $view;
    }


    /**
     * @noinspection PhpUnused
     * @return Response|ViewModel
     */
    public function updateAction()
    {
        /** @var Request $request */
        $request = $this->getRequest();

        try {
            $user = $this->client->requestUserDetails();
            $libraries = array_map(function (Library $libary) {
                return $libary->getLabel();
            }, $this->client->requestLibraryList());
        } catch (ClientException $exception) {
            $this->setFollowupUrlToReferer();
            $message = $exception->getCode() === 401
                ? 'fid::user_update_error_expired'
                : 'fid::user_update_error';
            $this->getMessenger()->addErrorMessage($this->translate($message));
            return $this->redirect()->toRoute('myresearch-home');
        }

        /** @var Form $form */
        $form = $this->serviceLocator->get(UserUpdateModel::class);

        /** @var Select $homeLibraryElement */
        $homeLibraryElement = $form->get('homeLibrary');
        $homeLibraryElement->setValueOptions($libraries);

        if ($this->formWasSubmitted()) {
            $form->setData($request->getPost());
            if ($form->isValid()) {
                return $this->update($form);
            }
        }

        $viewModel = $this->createViewModel();
        $viewModel->setVariables(compact('form', 'user'));
        $viewModel->setTemplate('fid/user/update');

        return $viewModel;
    }

    /**
     * @noinspection PhpUnused
     * @return ViewModel
     */
    public function changeUsernameAction()
    {
        /** @var Request $request */
        $request = $this->getRequest();
        $form = $this->serviceLocator->get(UsernameChangeModel::class);
        $forwarded = $this->params()->fromRoute('forwarded', false);

        if ($submitted = $this->formWasSubmitted()) {
            $form->setData($request->getPost());
            if (!$forwarded && $form->isValid()) {
                return $this->changeUsername($form);
            }
        }

        $view = $this->createViewModel();
        $view->setVariables(compact('form'));
        $view->setTemplate('fid/user/username-change');

        return $view;
    }

    /**
     * @noinspection PhpUnused
     * @return Response
     */
    public function updateUsernameAction()
    {
        /** @var Request $request */
        $request = $this->getRequest();
        $query = $request->getQuery();
        $messenger = $this->getMessenger();

        if ($credentials = $query->get('logon')) {
            try {
                $this->client->logon($credentials);
            } catch (ClientException $exception) {
                $message = $exception->getCode() === 401
                    ? 'fid::username_update_error_expired'
                    : 'fid::username_update_error';
                $messenger->addErrorMessage($this->translate($message));
                return $this->redirect()->toRoute('fid/user/change-username');
            }

            $query->offsetUnset('logon');
            return $this->redirect()->toRoute('fid/user/update-username', [], [
                'query' => $query->toArray()
            ]);
        }

        try {
            $user = $this->client->requestUserDetails();
            $user->setUsername($query->get('username'));
            $this->client->requestUserUsernameUpdate($user);
            $message = $this->translate('fid::username_update_success');
            $this->getMessenger()->addSuccessMessage($message);
        } catch (ClientException $exception) {
            $message = $this->translate('fid::username_update_error');
            $this->getMessenger()->addErrorMessage($message);
        }

        return $this->redirect()->toRoute('myresearch-home');
    }

    public function policyAction()
    {
        $viewModel = $this->createViewModel();
        $viewModel->setTemplate('fid/user/policy');

        if (isset($_REQUEST['lbreferer'])) {
            $viewModel->setVariable('backUrl', $_REQUEST['lbreferer']);
        }

        return $viewModel;
    }

    public function termsAction()
    {
        $viewModel = $this->createViewModel();
        $viewModel->setTemplate('fid/user/terms');

        if (isset($_REQUEST['lbreferer'])) {
            $viewModel->setVariable('backUrl', $_REQUEST['lbreferer']);
        }

    /**
     * Reset password action - Allows the reset password form to appear.
     *
     * @return ViewModel
     */
    public function resetPasswordAction()
    {
        /** @var Request $request */
        $request = $this->getRequest();
        $form = $this->serviceLocator->get(PasswordResetModel::class);
        $forwarded = $this->params()->fromRoute('forwarded', false);

        if ($submitted = $this->formWasSubmitted()) {
            $form->setData($request->getPost());
            if (!$forwarded && $form->isValid()) {
                return $this->sendResetPassword($form);
            }
        }

        $view = $this->createViewModel();
        $view->setVariables(compact('form'));
        $view->setTemplate('fid/user/password-reset');

        return $view;
    }

    /**
     * Reset password action - Allows the change password form to appear.
     *
     * @return Response|ViewModel
     */
    public function changePasswordAction()
    {
        /** @var Request $request */
        $request = $this->getRequest();
        $query = $request->getQuery();
        $messenger = $this->getMessenger();

        if ($credentials = $query->get('logon')) {
            try {
                $this->client->logon($credentials);
            } catch (ClientException $exception) {
                $message = $exception->getCode() === 401
                    ? 'fid::password_change_error_expired'
                    : 'fid::password_change_error';
                $messenger->addErrorMessage($this->translate($message));
                return $this->redirect()->toRoute('fid/user/reset-password');
            }

            $query->offsetUnset('logon');
            return $this->redirect()->toRoute('fid/user/change-password', [], [
                'query' => $query->toArray()
            ]);
        }

        $form = $this->serviceLocator->get(PasswordChangeModel::class);

        if ($this->formWasSubmitted()) {
            $form->setData($request->getPost());
            if ($form->isValid()) {
                return $this->changePassword($form);
            }
        } else {
            $form->setData($query);
        }

        $view = $this->createViewModel();
        $view->setVariables(compact('form'));
        $view->setTemplate('fid/user/password-change');

        return $view;
    }

    /**
     * @param Form $form
     *
     * @return mixed|Response
     */
    protected function init(Form $form)
    {
        /** @var UserInitModel $model */
        $messenger = $this->getMessenger();
        $model = $form->getHydrator()
            ->hydrate($form->getData(), new UserInitModel());

        /** @noinspection PhpUndefinedFieldInspection */
        $query['lng'] = $this->layout()->userLang;
        $username = $query['username'] = $model->getUsername();
        $firstname = $query['firstname'] = $model->getFirstname();
        $lastname = $query['lastname'] = $model->getLastname();
        $baseUrl = $this->url()->fromRoute('fid/user/create',
            [], ['query' => $query, 'force_canonical' => true]);

        try {
            $this->client->requestRegistrationLink($baseUrl, $username,
                $firstname, $lastname);
        } catch (ClientException $exception) {
            $message = $exception->getCode() === 400
                ? 'fid::user_init_error_username'
                : 'fid::user_init_error';
            $messenger->addErrorMessage($this->translate($message));
            return $this->forward()->dispatch(self::class, [
                'action'    => 'init',
                'forwarded' => true
            ]);
        }

        $message = $this->translate('fid::user_init_success');
        $messenger->addSuccessMessage(sprintf($message, $username));
        return $this->redirect()->toRoute('myresearch-home');
    }

    protected function create(Form $form)
    {
        $messenger = $this->getMessenger();
        /** @var UserCreateModel $model */
        $model = $form->getHydrator()
            ->hydrate($form->getData(), new UserCreateModel());

        $user = new User();
        $user->setUsername($username = $model->getUsername());
        $user->setPassword($password = $model->getPassword());
        $user->setHomeLibrary($model->getHomeLibrary());
        $user->setPermissions([$model->getAccessLevel() => 'requested']);
        $user->setSalutation($model->getSalutation());
        $user->setAcademicTitle($model->getAcademicTitle());
        $user->setFirstname($model->getFirstname());
        $user->setLastname($model->getLastname());
        $user->setYearOfBirth($model->getYearOfBirth());
        $user->setCollege($model->getCollege());
        $user->setJobTitle($model->getJobTitle());

        if ($model->getAddressLine1()) {
            $address = new Address();
            $address->setLine1($model->getAddressLine1());
            $address->setLine2($model->getAddressLine2());
            $address->setZip($model->getAddressZip());
            $address->setCity($model->getAddressCity());
            $address->setCountry($model->getAddressCountry());
            $user->setAddresses([$address]);
        }

        try {
            $this->client->requestUserCreation($user);
            $message = $this->translate('fid::user_create_success');
            $messenger->addSuccessMessage($message);
            /** @noinspection PhpParamsInspection */
            $this->authManager->create($this->getRequest());
        } catch (ClientException $exception) {
            $message = $this->translate('fid::user_create_error');
            $messenger->addErrorMessage($message);
        } catch (AuthException $e) {
            $message = $this->translate('fid::user_create_error_autologon');
            $messenger->addWarningMessage($message);
        }

        return $this->redirect()->toRoute('myresearch-home', [], [
            'query' => ['redirect' => false]
        ]);
    }

    /**
     * @param Form $form
     *
     * @return Response
     */
    protected function update(Form $form,string $redirect = 'myresearch-home')
    {
        $messenger = $this->getMessenger();
        /** @var UserUpdateModel $model */
        $model = $form->getHydrator()->hydrate($form->getData(),
            new UserUpdateModel());

        try {
            $userId = $model->getUserId();
            $user = $this->client->requestUserDetails($userId);
            $user->setHomeLibrary($model->getHomeLibrary());
            $user->setSalutation($model->getSalutation());
            $user->setAcademicTitle($model->getAcademicTitle());
            $user->setFirstname($model->getFirstname());
            $user->setLastname($model->getLastname());
            $user->setYearOfBirth($model->getYearOfBirth());
            $user->setCollege($model->getCollege());
            $user->setJobTitle($model->getJobTitle());
            $user->setPermissions($model->getPermissions());
            $addresses = $user->getAddresses();
            foreach ($model->getAddresses() as $index => $address) {
                $addr = $addresses[$index] ?? new Address();
                $addr->setLine1($address['line1']);
                $addr->setLine2($address['line2']);
                $addr->setZip($address['zip']);
                $addr->setCity($address['city']);
                $addr->setCountry($address['country']);
                $addresses[$index] = $addr;
            }
            $user->setAddresses($addresses);
            $this->client->requestUserUpdate($user);
            $message = $this->translate('fid::user_update_success');
            $messenger->addSuccessMessage($message);
        } catch (ClientException $exception) {
            if (in_array($exception->getCode(), [403])) {
                $message = $this->translate('fid::user_update_error_'
                    . $exception->getCode());
            } else {
                $message = $this->translate('fid::user_update_error');
            }

            $messenger->addErrorMessage($message);
        }

        $this->client->flushUserList();
        return $this->redirect()->toRoute($redirect, [], [
            'query' => ['redirect' => false]
        ]);
    }

    protected function sendResetPassword(Form $form)
    {
        /** @var PasswordResetModel $model */
        $messenger = $this->getMessenger();
        $model = $form->getHydrator()->hydrate(
            $form->getData(), new PasswordResetModel());
        $username = $model->getUsername();

        try {
            /** @noinspection PhpUndefinedFieldInspection */
            $query['lng'] = $this->layout()->userLang;
            $baseUrl = $this->url()->fromRoute('fid/user/change-password',
                [], ['query' => $query, 'force_canonical' => true]);
            $this->client->requestPasswordLink($baseUrl, $username);
            $message = $this->translate('fid::password_reset_success');
            $messenger->addSuccessMessage(sprintf($message, $username));
        } catch (ClientException $exception) {
            $message = $exception->getCode() === 400
                ? $this->translate('fid::password_reset_error_username')
                : $this->translate('fid::password_reset_error');
            $messenger->addErrorMessage(sprintf($message, $username));
            return $this->redirect()->toRoute('fid/user/reset-password');
        }

        return $this->redirect()->toRoute('myresearch-home');
    }

    protected function changePassword(Form $form)
    {
        /** @var PasswordChangeModel $model */
        $messenger = $this->getMessenger();
        $model = $form->getHydrator()->hydrate(
            $form->getData(), new PasswordChangeModel());

        try {
            $user = $this->client->requestUserDetails();
            $user->setPassword($password = $model->getPassword());
            $this->client->requestUserPasswordUpdate($user);
            $message = $this->translate('fid::password_change_success');
            $message = sprintf($message, $username = $user->getUsername());
            $messenger->addSuccessMessage($message);
            /** @var Request $request */
            $request = clone $this->getRequest();
            $params = clone $request->getPost();
            $params->set('username', $username);
            $params->set('password', $password);
            $request->setPost($params);
            $this->authManager->create($request);
        } catch (ClientException $exception) {
            $message = $this->translate('fid::password_change_error');
            $messenger->addErrorMessage($message);
        } catch (AuthException $e) {
            $message = $this->translate('fid::password_change_error_autologon');
            $messenger->addErrorMessage($message);
        }

        return $this->redirect()->toRoute('myresearch-home', [], [
            'query' => ['redirect' => false]
        ]);
    }

    protected function changeUsername(Form $form)
    {
        $messenger = $this->getMessenger();
        $model = $form->getHydrator()->hydrate(
            $form->getData(), new UsernameChangeModel());

        /** @noinspection PhpUndefinedFieldInspection */
        $query['lng'] = $this->layout()->userLang;
        $username = $query['username'] = $model->getUsername();
        $baseUrl = $this->url()->fromRoute('fid/user/update-username',
            [], ['query' => $query, 'force_canonical' => true]);

        try {
            $this->client->requestUsernameLink($baseUrl, $username);
        } catch (ClientException $exception) {
            $message = $exception->getCode() === 400
                ? 'fid::username_change_error_username'
                : 'fid::username_change_error';
            $messenger->addErrorMessage($this->translate($message));
            return $this->forward()->dispatch(self::class, [
                'action'    => 'changeUsername',
                'forwarded' => true
            ]);
        }

        $message = $this->translate('fid::username_change_success');
        $messenger->addSuccessMessage(sprintf($message, $username));
        return $this->redirect()->toRoute('myresearch-home');
    }
    protected function getMessenger(): FlashMessenger
    {
        /** @noinspection PhpUndefinedMethodInspection */
        /** @var FlashMessenger $messenger */
        return $this->flashMessenger();
    }

    public function editAction()
    {
        // Not logged in?  Force user to log in:
        if (!($user = $this->getUser())) {
            return $this->forceLogin();
        }

        /** @var Request $request */
        $request = $this->getRequest();
        $userId = $this->params()->fromRoute('userid');
        if (empty($userId)) {
            // if no user ID is set the call is not valid
            // unless we are in submission state
            return $this->redirect()->toRoute('fid/admin/list');
        }

        $editableFields = $this->config['Admin']['editable_user_fields'] ?? [];

        try {
            try {
                $user = $this->client->requestUserDetails($userId);
            } catch (UserNotAuthorizedException $ex) {
                // either user does not exist, or we are not allowed to edit it
                $this->getMessenger()->addErrorMessage($this->translate('fid::user_edit_not_allowed', ['%%userid%%' => $userId]));
                return $this->redirect()->toRoute('fid/admin/list');
            } catch (\Exception $ex) {
                $this->getMessenger()->addErrorMessage($this->translate('fid::user_read_error',['%%userid%%' => $userId]));
                return $this->redirect()->toRoute('fid/admin/list');
            }
            $libraries = $this->client->requestLibraryList();
        } catch (ClientException $exception) {
            $this->setFollowupUrlToReferer();
            $message = $exception->getCode() === 401
                ? 'fid::user_update_error_expired'
                : 'fid::user_update_error';
            $this->getMessenger()->addErrorMessage($this->translate($message));
            return $this->redirect()->toRoute('fid/admin/list');
        }

        /** @var Form $form */
        $form = $this->serviceLocator->get(UserUpdateModel::class);

        if (!empty($libraries)) {
            $userLibrary = $user->getHomeLibrary();
            $libraries = array_map(function (Library $libary) use ($editableFields,$userLibrary) {
                $option = [
                    'label' => $libary->getLabel(),
                    'value' => $id = $libary->getId(),
                    'selected' => $id == $userLibrary
                ];
                if ($id != $userLibrary && !in_array('HomeLibrary', $editableFields)) {
                    $option['attributes']['disabled'] = 'disabled';
                }
                return $option;
            }, $libraries);

            /** @var Select $homeLibraryElement */
            $homeLibraryElement = $form->get('homeLibrary');
            $homeLibraryElement->setValueOptions($libraries);
        }

        if ($permissions = $this->config['Admin']['permission_options'] ?? []) {

            $userPermissions = $user->getPermissions();
            if (!isset(self::$currentPermissions)) {
                $valid = [];
                foreach ($permissions as $permission) {
                    $valid[$permission] = $userPermissions[$permission] ?? 'denied';
                }
                self::$currentPermissions = $valid;
            }
            /** @var MultiCheckbox $permissionsElement */
            $permissionsElement = $form->get('permissions');
            $permissions = array_map(function ($permission) use ($editableFields,$userPermissions) {
                $option = [
                    'label' => $this->translate('fid::permission_' . $permission),
                    'name' => $permission,
                    'label_attributes' => ['class' => 'permission-label col-sm-11'],
                    'attributes' => ['class' => 'permission-option col-sm-1'],
                    'selected' => isset($userPermissions[$permission]) && $userPermissions[$permission] == 'granted',
                    'value' => $permission . '::' . ($userPermissions[$permission] ?? 'denied'),
                ];
                if (!in_array('Permissions', $editableFields)) {
                    $option['disabled'] = '1';
                }
                return $option;
            }, $permissions);
            $permissionsElement->setDisableInArrayValidator(true);
            $permissionsElement->setValueOptions($permissions);
        }

        if ($this->formWasSubmitted()) {
            $form->setData($request->getPost());
            if ($form->isValid()) {
                return $this->update($form,'fid/admin/list');
            }
        }

        $viewModel = $this->createViewModel();
        $viewModel->setVariables(compact('form', 'user','editableFields'));
        $viewModel->setTemplate('fid/admin/edit');

        return $viewModel;
    }

    public static $currentPermissions;

    public static function combinePermissionsArray($input) {
        $output = [];
        foreach ($input as $line) {
            list($key,) = explode('::',$line);
            if (isset(self::$currentPermissions[$key])) {
                $output[$key] = 'granted';
            }
        }
        foreach (self::$currentPermissions as $key => $state) {
            if ($state === 'granted' && !isset($output[$key])) {
                $output[$key] = 'denied';
            }
        }
        return array_intersect_key($output,['full_access'=>'0','limited_access'=>1]);
    }

    public function listAction() {

        // Not logged in?  Force user to log in:
        if (!($user = $this->getUser())) {
            return $this->forceLogin();
        }

        $viewModel = $this->createViewModel();
        try {
            $list = $this->client->requestUserList();
            $viewModel->setVariable('list',$list);
        } catch (UserNotAuthorizedException $exception) {
            $this->getMessenger()->addErrorMessage('fid::read_user_list_not_allowed');
        } catch (ClientException $exception) {
            $this->getMessenger()->addErrorMessage('fid::read_user_list_error');
        }

        if ($fields = $this->config['Admin']['overview_fields']) {
            $viewModel->setVariable('fields',$fields);
        }

        $viewModel->setTemplate('fid/admin/list');
        return $viewModel;
    }