HEX
Server: Apache
System: Linux WWW 6.1.0-40-amd64 #1 SMP PREEMPT_DYNAMIC Debian 6.1.153-1 (2025-09-20) x86_64
User: web11 (1011)
PHP: 8.2.29
Disabled: NONE
Upload Files
File: /var/www/apklausos/application/models/services/PasswordManagement.php
<?php

namespace LimeSurvey\Models\Services;

use DateTime;

/**
 * This class contains all functions for the process of password reset and creating new administration users
 * and sending email to those with a link to set the password.
 *
 * All this functions were implemented in UserManagementController before.
 */
class PasswordManagement
{
    // NB: PHP 7.0 does not support class constant visibility
    const MIN_PASSWORD_LENGTH = 8;
    const EMAIL_TYPE_REGISTRATION = 'registration';
    const EMAIL_TYPE_RESET_PW = 'resetPassword';
    const MIN_TIME_NEXT_FORGOT_PW_EMAIL = 5; //forgot pw email is send again, only after 5 min delay

    /** @var \User */
    private $user;

    /**
     * PasswordManagement constructor.
     * @param $user \User
     */
    public function __construct(\User $user)
    {
        $this->user = $user;
    }

    /**
     * This function prepare the email template to send to the new created user
     *
     *
     * @return mixed $aAdminEmail array with subject and email body
     */
    public function generateAdminCreationEmail()
    {
        $adminEmail = [];
        $siteName = \Yii::app()->getConfig("sitename");
        /* Usage of Yii::app()->createAbsoluteUrl, disable publicurl, See mantis #19619 */
        $loginUrl = \Yii::app()->createAbsoluteUrl(
            'admin/authentication/sa/newPassword',
            ['param' => $this->user->validation_key]
        );
        $siteAdminEmail = \Yii::app()->getConfig("siteadminemail");
        $emailSubject = \Yii::app()->getConfig("admincreationemailsubject");
        $emailTemplate = \Yii::app()->getConfig("admincreationemailtemplate");

        //Replace placeholder in Email subject
        $emailSubject = str_replace("{SITENAME}", $siteName, (string) $emailSubject);
        $emailSubject = str_replace("{SITEADMINEMAIL}", $siteAdminEmail, $emailSubject);

        //Replace placeholder in Email body
        $emailTemplate = str_replace("{SITENAME}", $siteName, (string) $emailTemplate);
        $emailTemplate = str_replace("{SITEADMINEMAIL}", $siteAdminEmail, $emailTemplate);
        $emailTemplate = str_replace("{FULLNAME}", $this->user->full_name, $emailTemplate);
        $emailTemplate = str_replace("{USERNAME}", $this->user->users_name, $emailTemplate);
        $emailTemplate = str_replace("{LOGINURL}", $loginUrl, $emailTemplate);

        $adminEmail['subject'] = $emailSubject;
        $adminEmail['body'] = $emailTemplate;

        return $adminEmail;
    }


    /**
     * Sets the validationKey and the validationKey expiration and
     * sends email to the user, containing the link to set/reset password.
     *
     * @param string $emailType this could be 'registration' or 'resetPassword' (see const in this class)
     *
     * @return array message if sending email to user was successful
     *
     * @throws \PHPMailer\PHPMailer\Exception
     */
    public function sendPasswordLinkViaEmail(string $emailType): array
    {
        $success = true;
        $this->user->setValidationKey();
        $this->user->setValidationExpiration();
        $mailer = $this->sendAdminMail($emailType);

        if ($mailer->getError()) {
            $sReturnMessage = \CHtml::tag("h4", array(), gT("Error"));
            $sReturnMessage .= \CHtml::tag(
                "p",
                array(),
                sprintf(
                    gT("Email to %s (%s) failed."),
                    "<strong>" . $this->user->users_name . "</strong>",
                    $this->user->email
                )
            );
            $sReturnMessage .= \CHtml::tag("p", array(), $mailer->getError());
            $success = false;
        } else {
            // has to be sent again or no other way
            $sReturnMessage = \CHtml::tag("h4", array(), gT("Success"));
            $sReturnMessage .= \CHtml::tag(
                "p",
                array(),
                sprintf(
                    gT("Username : %s - Email : %s."),
                    $this->user->users_name,
                    $this->user->email
                )
            );
            $sReturnMessage .= \CHtml::tag("p", array(), gT("An email with a generated link was sent to the user."));
        }

        return [
            'success' => $success,
            'sReturnMessage' => $sReturnMessage
        ];
    }

    /**
     * Checks if user is allowed to do the next sending of email for forgotten password.
     * This should only be the case all 5min (see self::MIN_TIME_NEXT_FORGOT_PW_EMAIL)
     *
     * @return bool true if user can send another email for forgotten pw, false otherwise
     * @throws \Exception
     */
    public function isAllowedToSendForgotPwEmail(\User $user): bool
    {
        $sendForgotPwIsAllowed = true;
        if ($user->last_forgot_email_password !== null) { //null means user has never clicked "Forget pw" before
            $now = new DateTime();
            $lastForgotPwEmail = new DateTime($user->last_forgot_email_password);
            $dateDiff = $now->diff($lastForgotPwEmail);
            //if time difference is greater then self::MIN_TIME_NEXT_FORGOT_PW_EMAIL, user can send new email
            $differenceDays = $dateDiff->format('%d');
            $differenceHours = $dateDiff->format('%h');
            $differenceMin = $dateDiff->format('%i');
            //calculate time difference in minutes
            $differenceInMinutes = ((int)$differenceDays * 24 * 60) + ((int)$differenceHours * 60) + (int)$differenceMin;
            $sendForgotPwIsAllowed = $differenceInMinutes > self::MIN_TIME_NEXT_FORGOT_PW_EMAIL;
        }

        return $sendForgotPwIsAllowed;
    }

    /**
     * Send a link to email of the user to set a new password (forgot password functionality)
     *
     * @return string message for user
     */
    public function sendForgotPasswordEmailLink(): string
    {
        $mailer = new \LimeMailer();
        $mailer->emailType = 'passwordreminderadminuser';
        $mailer->addAddress($this->user->email, $this->user->full_name);
        $mailer->Subject = gT('User data');

        /* Body construct */
        //before setting new validationKey and date,check when was the last attempt
        if ($this->isAllowedToSendForgotPwEmail($this->user)) {
            $this->user->setValidationKey();
            $this->user->setValidationExpiration();
            $now = new DateTime();
            $this->user->last_forgot_email_password = $now->format('Y-m-d H:i:s');
            $this->user->save();
            $username = sprintf(gT('Username: %s'), $this->user->users_name);
            /* Usage of Yii::app()->createAbsoluteUrl, disable publicurl, See mantis #19619 */
            $linkToResetPage = \Yii::app()->createAbsoluteUrl(
                'admin/authentication/sa/newPassword/',
                ['param' => $this->user->validation_key]
            );
            $linkText = gT("Click here to set your password: ") . $linkToResetPage;
            $body = array();
            $body[] = sprintf(gT('Your link to reset password %s'), \Yii::app()->getConfig('sitename'));
            $body[] = $username;
            $body[] = $linkText;
            $body = implode("\n", $body);
            $mailer->Body = $body;
            /* Go to send email and set password*/
            if ($mailer->sendMessage()) {
                // For security reasons, we don't show a successful message
                $sMessage = sprintf(gT('If the username and email address is valid a password reminder email has been sent to you. This email can only be requested once in %d minutes.'), self::MIN_TIME_NEXT_FORGOT_PW_EMAIL);
            } else {
                $sMessage = gT('Email failed');
            }
        } else {
            $sMessage = sprintf(gT('If the username and email address is valid a password reminder email has been sent to you. This email can only be requested once in %d minutes.'), self::MIN_TIME_NEXT_FORGOT_PW_EMAIL);
        }

        return $sMessage;
    }

    /**
     * Creates a random password through the core plugin
     *
     * @todo it's fine to use static functions, until it is used only in controllers ...
     *
     * @param int $length Length of the password
     * @return string|null
     */
    public static function getRandomPassword($length = self::MIN_PASSWORD_LENGTH)
    {
        $oGetPasswordEvent = new \PluginEvent('createRandomPassword');
        $oGetPasswordEvent->set('targetSize', $length);
        \Yii::app()->getPluginManager()->dispatchEvent($oGetPasswordEvent);

        return $oGetPasswordEvent->get('password');
    }

    /**
     * Send the registration email to a new survey administrator
     *
     * @param string $type two types are available 'resetPassword' or 'registration', default is 'registration'
     *
     * @return \LimeMailer if send is successful
     *
     * @throws \PHPMailer\PHPMailer\Exception
     */
    private function sendAdminMail($type = self::EMAIL_TYPE_REGISTRATION): \LimeMailer
    {
        switch ($type) {
            case self::EMAIL_TYPE_RESET_PW:
                $renderArray = $this->getRenderArray();
                $subject = "[" . \Yii::app()->getConfig("sitename") . "] " . gT(
                    "Your login credentials have been reset"
                );
                $body = \Yii::app()->getController()->renderPartial(
                    'partial/usernotificationemail',
                    $renderArray,
                    true
                );
                break;
            case self::EMAIL_TYPE_REGISTRATION:
            default:
                //Get email template from globalSettings
                $aAdminEmail = $this->generateAdminCreationEmail();
                $subject = $aAdminEmail["subject"];
                $body = $aAdminEmail["body"];
                break;
        }

        $emailType = "addadminuser";

        $mailer = new \LimeMailer();
        $mailer->addAddress($this->user->email, $this->user->full_name);
        $mailer->Subject = $subject;
        $mailer->setFrom(\Yii::app()->getConfig("siteadminemail"), \Yii::app()->getConfig("siteadminname"));
        $mailer->Body = $body;
        $mailer->isHtml(true);
        $mailer->emailType = $emailType;
        $mailer->sendMessage();
        return $mailer;
    }

    /**
     * @return array
     */
    public function getRenderArray()
    {
        /* Usage of Yii::app()->createAbsoluteUrl, disable publicurl, See mantis #19619 */
        $absoluteUrl = \Yii::app()->createAbsoluteUrl("/admin");
        $passwordResetUrl = \Yii::app()->createAbsoluteUrl(
            'admin/authentication/sa/newPassword',
            ['param' => $this->user->validation_key]
        );
        return [
            'surveyapplicationname' => \Yii::app()->getConfig("sitename"),
            'emailMessage' => sprintf(gT("Hello %s,"), $this->user->full_name) . "<br />"
            . sprintf(
                gT(
                    "This is an automated email to notify you that your login credentials for '%s' have been reset."
                ),
                \Yii::app()->getConfig("sitename")
            ),
            'credentialsText' => gT("Here are your new credentials."),
            'siteadminemail' => \Yii::app()->getConfig("siteadminemail"),
            'linkToAdminpanel' => $absoluteUrl,
            'username' => $this->user->users_name,
            'password' => $passwordResetUrl,
            'mainLogoFile' => \Yii::app()->createAbsoluteUrl(LOGO_URL),
            'showPasswordSection' => \Yii::app()->getConfig("auth_webserver") === false
            && \Permission::model() ->hasGlobalPermission('auth_db', 'read', $this->user->uid),
            'showPassword' => \Yii::app()->getConfig("display_user_password_in_email") === true,
        ];
    }
}