luyadev/luya-module-admin

View on GitHub
src/apis/UserController.php

Summary

Maintainability
A
1 hr
Test Coverage
F
57%
<?php

namespace luya\admin\apis;

use luya\admin\models\User;
use luya\admin\models\UserChangePassword;
use luya\admin\models\UserDevice;
use luya\admin\Module;
use luya\admin\ngrest\base\Api;
use luya\base\PackageInstaller;
use luya\validators\StrengthValidator;
use RobThree\Auth\TwoFactorAuth;
use Yii;
use yii\web\NotFoundHttpException;

/**
 * User API, provides ability to manager and list all administration users.
 *
 * @author Basil Suter <basil@nadar.io>
 * @since 1.0.0
 */
class UserController extends Api
{
    /**
     * @var string Path to the user model class.
     */
    public $modelClass = 'luya\admin\models\User';

    /**
     * Profile
     *
     * Return informations about the current logged in user
     *
     * @return array
     */
    public function actionSession()
    {
        $user = Yii::$app->adminuser->identity;

        $qrcode = null;
        $secret = null;
        if (!$user->login_2fa_enabled) {
            $tfa = new TwoFactorAuth(Yii::$app->siteTitle);
            $secret = $tfa->createSecret();
            $qrcode = $tfa->getQRCodeImageAsDataUri(Yii::$app->siteTitle, $secret);
        }

        $session = [
            'packages' => [],
            'user' => $user->toArray(['title', 'firstname', 'lastname', 'email', 'id', 'email_verification_token_timestamp']),
            'activities' => ['open_email_validation' => $this->hasOpenEmailValidation($user)],
            'settings' => Yii::$app->adminuser->identity->setting->getArray([
                User::USER_SETTING_ISDEVELOPER, User::USER_SETTING_UILANGUAGE, User::USER_SETTING_NEWUSEREMAIL
            ], [
                User::USER_SETTING_UILANGUAGE => $this->module->interfaceLanguage,
            ]),
            'vendor_install_timestamp' => Yii::$app->getPackageInstaller()->getTimestamp(),
            'devices' => $user->devices,
            'twoFa' => [
                'enabled' => $user->login_2fa_enabled,
                'qrcode' => $qrcode,
                'secret' => $secret,
            ],
        ];

        // if developer option is enabled provide package infos
        if ($session['settings'][User::USER_SETTING_ISDEVELOPER]) {
            $session['packages'] = $this->packagesToArray(Yii::$app->getPackageInstaller());
        }

        return $session;
    }

    /**
     * Generate an array with package infos
     *
     * @param PackageInstaller $installer
     * @return array
     * @since 2.2.0
     */
    private function packagesToArray(PackageInstaller $installer)
    {
        $packages = [];
        foreach ($installer->getConfigs() as $config) {
            $packages[] = [
                'package' => $config->package,
                'bootstrap' => $config->bootstrap,
                'blocks' => $config->blocks,
            ];
        }
        return $packages;
    }

    /**
     * Disable 2FA
     * Action to disable the two fa auth for this user.
     *
     * @return array
     * @since 3.0.0
     */
    public function actionDisableTwofa()
    {
        $user = Yii::$app->adminuser->identity;
        $user->login_2fa_enabled = 0;
        $user->login_2fa_secret = '';
        $user->login_2fa_backup_key = '';

        if ($user->update(true, ['login_2fa_enabled', 'login_2fa_secret', 'login_2fa_backup_key'])) {
            return [];
        }

        return $this->sendModelError($user);
    }

    /**
     * Register 2FA Device
     *
     * Action to register new OTP device
     *
     * @return array
     * @since 3.0.0
     */
    public function actionRegisterTwofa()
    {
        $user = Yii::$app->adminuser->identity;

        $verification = Yii::$app->request->getBodyParam('verification');
        $secret = Yii::$app->request->getBodyParam('secret');

        $tfa = new TwoFactorAuth(Yii::$app->siteTitle);

        if (!$tfa->verifyCode($secret, $verification)) {
            return $this->sendArrayError(['verificaton' => Module::t('user_register_2fa_verification_error')]);
        }

        $code = random_int(1_000_000, 9_999_999);
        $backupKey = Yii::$app->security->generatePasswordHash($code);

        $user->login_2fa_enabled = 1;
        $user->login_2fa_secret = $secret;
        $user->login_2fa_backup_key = $backupKey;

        if ($user->update(true, ['login_2fa_enabled', 'login_2fa_secret', 'login_2fa_backup_key'])) {
            return [
                'backupCode' => $code,
            ];
        }

        return $this->sendModelError($user);
    }

    /**
     * Remove Device
     *
     * @return array
     * @since 3.0.0
     */
    public function actionRemoveDevice()
    {
        $deviceId = Yii::$app->request->getBodyParam('deviceId');

        $device = UserDevice::find()->where(['id' => $deviceId, 'user_id' => Yii::$app->adminuser->id])->one();

        if ($device) {
            return $device->delete();
        }

        throw new NotFoundHttpException();
    }

    /**
     * Change Password
     *
     * A request including body params `newpass`, `oldpass`, `newpassrepeat`.
     *
     * @uses UserChangePassword
     * @return \luya\admin\models\UserChangePassword
     */
    public function actionChangePassword()
    {
        $model = new UserChangePassword();
        $model->setUser(Yii::$app->adminuser->identity);
        $model->attributes = Yii::$app->request->bodyParams;

        if ($this->module->strongPasswordPolicy) {
            $model->validators->append(StrengthValidator::createValidator(StrengthValidator::class, $model, ['newpass']));
        }

        if ($model->validate()) {
            $model->checkAndStore();
        }

        return $model;
    }

    /**
     * Change E-Mail
     *
     * Action to change the email based on token input.
     *
     * @return boolean
     * @since 1.2.0
     */
    public function actionChangeEmail()
    {
        $token = Yii::$app->request->getBodyParam('token');
        $user = Yii::$app->adminuser->identity;

        if (!empty($token) && sha1($token) == $user->email_verification_token && $this->hasOpenEmailValidation($user)) {
            $newEmail = $user->setting->get(User::USER_SETTING_NEWUSEREMAIL);

            $user->email = $newEmail;
            if ($user->update(true, ['email'])) {
                $user->resetEmailVerification();
                $newEmail = $user->setting->remove(User::USER_SETTING_NEWUSEREMAIL);
                return ['success' => true];
            } else {
                return $this->sendModelError($user);
            }
        }

        return $this->sendArrayError(['email' => Module::t('account_changeemail_wrongtokenorempty')]);
    }

    /**
     * Update Profile
     *
     * @return array
     */
    public function actionSessionUpdate()
    {
        $identity = Yii::$app->adminuser->identity;
        $user = clone Yii::$app->adminuser->identity;
        $user->attributes = Yii::$app->request->bodyParams;
        $verify = ['title', 'firstname', 'lastname', 'id'];

        // check if email has changed, if yes send secure token and temp store new value in user settings table.
        if ($user->validate(['email']) && $user->email !== $identity->email && $this->module->emailVerification) {
            $token = $user->getAndStoreEmailVerificationToken();
            $mailer = Yii::$app->mail;
            $mailer->layout = false; // disable layout as mail template contains layout
            $mail = $mailer->compose(Module::t('account_changeemail_subject'), User::generateTokenEmail($token, Module::t('account_changeemail_subject'), Module::t('account_changeemail_body')))
                ->address($identity->email, $identity->firstname . ' '. $identity->lastname)
                ->send();

            if ($mail) {
                $identity->setting->set(User::USER_SETTING_NEWUSEREMAIL, $user->email);
            } else {
                $user->addError('email', Module::t('account_changeemail_tokensenterror', ['email' => $identity->email]));
                $identity->resetEmailVerification();
            }
        }

        if (!$this->module->emailVerification) {
            $verify[] = 'email';
        }

        if (!$user->hasErrors() && $user->update(true, $verify) !== false) {
            return $user;
        }

        return $this->sendModelError($user);
    }

    /**
     * Change Settings
     *
     * @return boolean
     */
    public function actionChangeSettings()
    {
        $params = Yii::$app->request->bodyParams;

        foreach ($params as $param => $value) {
            Yii::$app->adminuser->identity->setting->set($param, $value);
        }

        return true;
    }

    /**
     * Ensure whether the current user has an active email verification token or not.
     *
     * @param User $user The user object to evaluate.
     * @return boolean
     */
    private function hasOpenEmailValidation(User $user)
    {
        $ts = $user->email_verification_token_timestamp;
        if (!empty($ts) && (time() - $this->module->emailVerificationTokenExpirationTime) <= $ts) {
            return true;
        }

        return false;
    }
}