secondFactorAuthentication = new SecondFactorAuthentication(RequestContext::getSession()); return $result; } protected function loginUser(User $user): void { RequestContext::getYiiUser()->login($user, $this->getParams()->autoLoginDuration); $user->dateLastLogin = date('Y-m-d H:i:s'); $user->save(); $authParts = explode(':', $user->auth); if (count($authParts) === 2) { FailedLoginAttempt::resetAfterSuccessfulLogin($authParts[1]); } } public function actionLogin(string $backUrl = ''): ResponseInterface { $this->layout = 'column2'; if ($backUrl === '') { $backUrl = '/'; } $usernamePasswordForm = new LoginUsernamePasswordForm(RequestContext::getSession(), User::getExternalAuthenticator()); $conPwdConsultation = $this->consultation; if ($this->getHttpRequest()->get('passConId')) { foreach ($this->site->consultations as $consultation) { if ($consultation->urlPath === $this->getHttpRequest()->get('passConId')) { $conPwdConsultation = $consultation; } } } if ($this->isPostSet('loginusernamepassword')) { $usernamePasswordForm->setAttributes($this->getPostValues()); try { $user = $usernamePasswordForm->getOrCreateUser($this->site); if ($user->status === User::STATUS_UNCONFIRMED && $this->getParams()->confirmEmailAddresses) { // Needs to confirm e-mail-address before actually being logged in $usernamePasswordForm->setLoggedInAwaitingEmailConfirmation($user); $backUrl = UrlHelper::createUrl([ '/user/confirmregistration', 'backUrl' => $backUrl, 'email' => $user->email, ]); } elseif ($user->getSettingsObj()->forcePasswordChange) { // Needs to change password before actually being logged in $usernamePasswordForm->setLoggedInAwaitingPasswordChange($user); $backUrl = UrlHelper::createUrl([ '/user/login-force-pwd-change', 'backUrl' => $backUrl, ]); } elseif ($return = $this->secondFactorAuthentication->onUsernamePwdLoginSuccess($user, $backUrl)) { // Needs to perform 2FA before actually being logged in return $return; } else { $this->loginUser($user); $this->getHttpSession()->setFlash('success', \Yii::t('user', 'welcome')); } /* 307 breaks user/NoEmailConfirmationCept $this->redirect($backUrl, 307); Yii::$app->end(307); */ return new RedirectResponse($backUrl); } catch (Login $e) { $usernamePasswordForm->error = $e->getMessage(); } } $conPwdErr = null; if ($this->isPostSet('loginconpwd') && $conPwdConsultation) { $conPwd = new ConsultationAccessPassword($conPwdConsultation); if ($conPwd->checkPassword($this->getHttpRequest()->post('password'))) { $conPwd->setCorrectCookie(); return new RedirectResponse($backUrl); } else { $conPwdErr = \Yii::t('user', 'login_err_password'); } } if ($this->getHttpSession()->isActive && $this->getHttpSession()->getFlash('error')) { $usernamePasswordForm->error = $this->getHttpSession()->getFlash('error'); $this->getHttpSession()->removeFlash('error'); } return new HtmlResponse($this->render( 'login', [ 'backUrl' => $backUrl, 'usernamePasswordForm' => $usernamePasswordForm, 'conPwdConsultation' => $conPwdConsultation, 'conPwdErr' => $conPwdErr, ] )); } public function actionLogin2fa(string $backUrl = ''): ResponseInterface { $loggingInUser = $this->secondFactorAuthentication->getOngoingSessionUser(); if (!$loggingInUser) { $minutes = SecondFactorAuthentication::TIMEOUT_2FA_SESSION / 60; $msg = str_replace('%minutes%', (string) $minutes, \Yii::t('user', 'err_2fa_timeout')); $this->getHttpSession()->setFlash('error', $msg); return new RedirectResponse(UrlHelper::createUrl('/user/login')); } if ($backUrl === '') { $backUrl = '/'; } $error = null; if ($this->isPostSet('2fa') && trim($this->getPostValue('2fa'))) { if (Captcha::needsCaptcha($loggingInUser->email) && !Captcha::checkEnteredCaptcha($this->getRequestValue('captcha'))) { $error = \Yii::t('user', 'login_err_captcha'); goto loginForm; } $successUser = $this->secondFactorAuthentication->confirmLoginWithSecondFactor($this->getPostValue('2fa')); if (!$successUser) { FailedLoginAttempt::logAttempt($loggingInUser->email); $error = \Yii::t('user', 'err_2fa_incorrect'); goto loginForm; } $this->loginUser($successUser); $this->getHttpSession()->setFlash('success', \Yii::t('user', 'welcome')); return new RedirectResponse($backUrl); } $this->secondFactorAuthentication->getOngoingSessionUser(); loginForm: $resp = new HtmlResponse($this->render('login-2fa', [ 'captchaUsername' => $loggingInUser->email, 'error' => $error, ])); $this->secondFactorAuthentication->getOngoingSessionUser(); return $resp; } public function actionLogin2faForceRegistration(string $backUrl = ''): ResponseInterface { try { $loggingInUser = $this->secondFactorAuthentication->getForcedRegistrationUser(); } catch (\Exception $e) { $this->getHttpSession()->setFlash('error', $e->getMessage()); return new RedirectResponse(UrlHelper::createUrl('/user/login')); } if ($backUrl === '') { $backUrl = '/'; } $error = null; if ($this->isPostSet('set2fa') && trim($this->getPostValue('set2fa'))) { if (Captcha::needsCaptcha($loggingInUser->email) && !Captcha::checkEnteredCaptcha($this->getRequestValue('captcha'))) { $error = \Yii::t('user', 'login_err_captcha'); goto loginForm; } try { $successUser = $this->secondFactorAuthentication->attemptForcedRegisteringSecondFactor(trim($this->getPostValue('set2fa'))); $this->loginUser($successUser); $this->getHttpSession()->setFlash('success', \Yii::t('user', 'welcome')); return new RedirectResponse($backUrl); } catch (\RuntimeException $e) { $error = $e->getMessage(); FailedLoginAttempt::logAttempt($loggingInUser->email); } } loginForm: return new HtmlResponse($this->render('login-2fa-force-registration', [ 'error' => $error, 'captchaUsername' => $loggingInUser->email, 'addSecondFactorKey' => $this->secondFactorAuthentication->createForcedRegistrationSecondFactor(), ])); } public function actionLoginForcePwdChange(string $backUrl = ''): ResponseInterface { if ($backUrl === '') { $backUrl = '/'; } $usernamePasswordForm = new LoginUsernamePasswordForm(RequestContext::getSession(), User::getExternalAuthenticator()); $sessionUser = $usernamePasswordForm->getOngoingPwdChangeSession(); if (!$sessionUser) { return new RedirectResponse($backUrl); } $pwMinLen = LoginUsernamePasswordForm::PASSWORD_MIN_LEN; $error = null; if ($this->isPostSet('change') && $this->getPostValue('pwd')) { if ($this->getPostValue('pwd') !== $this->getPostValue('pwd2')) { $error = \Yii::t('user', 'err_pwd_different'); } elseif (grapheme_strlen($this->getPostValue('pwd')) < $pwMinLen) { $msg = \Yii::t('user', 'err_pwd_length'); $error = str_replace('%MINLEN%', (string)$pwMinLen, $msg); } else { $sessionUser->changePassword($this->getPostValue('pwd')); $settings = $sessionUser->getSettingsObj(); $settings->forcePasswordChange = false; $sessionUser->setSettingsObj($settings); $this->loginUser($sessionUser); $this->getHttpSession()->setFlash('success', \Yii::t('user', 'welcome')); return new RedirectResponse($backUrl); } } return new HtmlResponse($this->render('login-force-pwd-change', [ 'pwMinLen' => $pwMinLen, 'user' => $sessionUser, 'error' => $error, ])); } public function actionToken(): JsonResponse { return new JsonResponse(JwtCreator::getJwtConfigForCurrUser($this->consultation)); } public function actionConfirmregistration(string $backUrl = '', string $email = ''): ResponseInterface { $msgError = ''; $prefillCode = ''; $usernamePasswordForm = new LoginUsernamePasswordForm(RequestContext::getSession(), User::getExternalAuthenticator()); $ongoingSessionUser = $usernamePasswordForm->getOngoingEmailConfirmationSessionUser(); if ($this->isRequestSet('resend') && $ongoingSessionUser && !$ongoingSessionUser->hasTooRecentRecoveryEmail()) { $usernamePasswordForm->sendConfirmationEmail($ongoingSessionUser); } elseif ($this->isRequestSet('email') && $this->isRequestSet('code')) { /** @var User|null $user */ $user = User::findOne(['auth' => 'email:' . $this->getRequestValue('email')]); if (!$user) { $msgError = \Yii::t('user', 'err_email_acc_notfound'); } elseif ($user->emailConfirmed === 1) { $msgError = \Yii::t('user', 'err_email_acc_confirmed'); } elseif (Captcha::needsCaptcha($user->email) && !Captcha::checkEnteredCaptcha($this->getRequestValue('captcha'))) { $msgError = \Yii::t('user', 'login_err_captcha'); $prefillCode = trim($this->getRequestValue('code', '')); // When coming from an e-mail, only ask for captcha } elseif ($user->checkEmailConfirmationCode(trim($this->getRequestValue('code')))) { $user->emailConfirmed = 1; $user->status = User::STATUS_CONFIRMED; if ($user->save()) { $user->trigger(User::EVENT_ACCOUNT_CONFIRMED, new UserEvent($user)); if ($usernamePasswordForm->hasOngoingEmailConfirmationSession($user)) { $this->loginUser($user); } if ($this->consultation && $this->consultation->getSettings()->managedUserAccounts) { if ($this->consultation->getSettings()->allowRequestingAccess) { UserConsultationScreening::askForConsultationPermission($user, $this->consultation); $needsAdminScreening = true; } else { return new RedirectResponse(UrlHelper::createUrl('/user/consultationaccesserror', $this->consultation)); } } else { $needsAdminScreening = false; } return new HtmlResponse($this->render('registration_confirmed', ['needsAdminScreening' => $needsAdminScreening])); } } else { $msgError = \Yii::t('user', 'err_code_wrong'); FailedLoginAttempt::logAttempt($user->email); } } return new HtmlResponse($this->render( 'confirm_registration', [ 'email' => $email, 'prefillCode' => $prefillCode, 'errors' => $msgError, 'backUrl' => $backUrl, 'allowResend' => ($ongoingSessionUser && !$ongoingSessionUser->hasTooRecentRecoveryEmail()), ] )); } public function actionLogout(string $backUrl = ''): ResponseInterface { if ($backUrl === '') { $backUrl = $this->getHttpRequest()->post('backUrl', UrlHelper::homeUrl()); } foreach (AntragsgruenApp::getActivePlugins() as $plugin) { if ($loginProvider = $plugin::getDedicatedLoginProvider()) { try { $pluginBackUrl = $loginProvider->logoutCurrentUserIfRelevant($backUrl); if ($pluginBackUrl) { return new RedirectResponse($pluginBackUrl, RedirectResponse::REDIRECT_TEMPORARY); } } catch (\Exception $e) { $msg = \Yii::t('user', 'err_unknown') . ':
"' . Html::encode($e->getMessage()) . '"'; return new HtmlErrorResponse(500, $msg); } } } RequestContext::getYiiUser()->logout(); return new RedirectResponse($backUrl, RedirectResponse::REDIRECT_TEMPORARY); } /** * Sets the language the user browses this site in and returns to the page they came from. * Invalid or unsupported languages are silently ignored. */ public function actionSetlanguage(string $language = '', string $backUrl = ''): ResponseInterface { LanguageTools::setCurrentLanguage($language); if ($backUrl === '') { $backUrl = UrlHelper::homeUrl() ?? '/'; } return new RedirectResponse($backUrl, RedirectResponse::REDIRECT_FOUND); } public function actionRecovery(string $email = '', string $code = ''): ResponseInterface { if ($this->isPostSet('send')) { $email = $this->getRequestValue('email'); /** @var User|null $user */ $user = User::findOne(['auth' => 'email:' . $email]); if (Captcha::needsCaptcha($email) && !Captcha::checkEnteredCaptcha($this->getRequestValue('captcha'))) { $msg = \Yii::t('user', 'login_err_captcha'); $this->getHttpSession()->setFlash('error', $msg); } elseif (!$user) { $msg = str_replace('%USER%', $email, \Yii::t('user', 'err_user_notfound')); $this->getHttpSession()->setFlash('error', $msg); } elseif ($user->getSettingsObj()->preventPasswordChange) { $this->getHttpSession()->setFlash('error', \Yii::t('user', 'err_pwd_fixed')); } elseif ($user->getSettingsObj()->preventAccountPage) { return new RedirectResponse(UrlHelper::homeUrl()); } else { try { $user->sendRecoveryMail(); $msg = \Yii::t('user', 'pwd_recovery_sent'); $this->getHttpSession()->setFlash('success', $msg); } catch (MailNotSent | ServerConfiguration | FormError $e) { $errMsg = \Yii::t('base', 'err_email_not_sent') . ': ' . $e->getMessage(); $this->getHttpSession()->setFlash('error', $errMsg); } } } if ($this->isPostSet('recover')) { $email = $this->getRequestValue('email'); /** @var User|null $user */ $user = User::findOne(['auth' => 'email:' . $email]); $pwMinLen = LoginUsernamePasswordForm::PASSWORD_MIN_LEN; if (Captcha::needsCaptcha($email) && !Captcha::checkEnteredCaptcha($this->getRequestValue('captcha'))) { $msg = \Yii::t('user', 'login_err_captcha'); $this->getHttpSession()->setFlash('error', $msg); } elseif (!$user) { $msg = str_replace('%USER%', $email, \Yii::t('user', 'err_user_notfound')); $this->getHttpSession()->setFlash('error', $msg); } elseif (grapheme_strlen($this->getRequestValue('newPassword')) < $pwMinLen) { $msg = str_replace('%MINLEN%', (string)$pwMinLen, \Yii::t('user', 'err_pwd_length')); $this->getHttpSession()->setFlash('error', $msg); } else { try { if ($user->checkRecoveryToken($this->getRequestValue('recoveryCode'))) { $user->changePassword($this->getRequestValue('newPassword')); return new HtmlResponse($this->render('recovery_confirmed')); } } catch (ExceptionBase $e) { $this->getHttpSession()->setFlash('error', $e->getMessage()); } } } return new HtmlResponse($this->render('recovery', ['preEmail' => $email, 'preCode' => $code])); } public function actionEmailchange(string $email, string $code): RedirectResponse { $this->forceLogin(); $user = User::getCurrentUser(); if ($user->getSettingsObj()->preventAccountPage) { return new RedirectResponse(UrlHelper::homeUrl()); } try { $user->changeEmailAddress($email, $code); $this->getHttpSession()->setFlash('success', \Yii::t('user', 'emailchange_done')); } catch (FormError $e) { $this->getHttpSession()->setFlash('error', $e->getMessage()); } return new RedirectResponse(UrlHelper::createUrl('user/myaccount')); } public function actionMyaccount(): ResponseInterface { $this->forceLogin(); $user = User::getCurrentUser(); $pwMinLen = LoginUsernamePasswordForm::PASSWORD_MIN_LEN; if ($user->getSettingsObj()->preventAccountPage) { return new RedirectResponse(UrlHelper::homeUrl()); } $params = AntragsgruenApp::getInstance(); if ($this->isPostSet('resendEmailChange')) { $changeRequested = $user->getChangeRequestedEmailAddress(); if ($changeRequested) { $lastRequest = time() - Tools::dateSql2timestamp($user->emailChangeAt); if ($lastRequest < 5 * 60) { $this->getHttpSession()->setFlash('error', \Yii::t('user', 'err_emailchange_flood')); } else { try { $user->sendEmailChangeMail($changeRequested); $this->getHttpSession()->setFlash('success', \Yii::t('user', 'emailchange_sent')); } catch (MailNotSent | ServerConfiguration $e) { $errMsg = \Yii::t('base', 'err_email_not_sent') . ': ' . $e->getMessage(); $this->getHttpSession()->setFlash('error', $errMsg); } } } } if ($this->isPostSet('save')) { $post = $this->getPostValues(); if (($user->fixedData & User::FIXED_NAME) === 0) { $user->nameGiven = $post['name_given'] ?? ''; $user->nameFamily = $post['name_family'] ?? ''; $user->name = trim($user->nameGiven . ' ' . $user->nameFamily); } $selectableOrganisations = $user->getSelectableUserOrganizations(); if (isset($post['orgaPrimary']) && $selectableOrganisations) { foreach ($selectableOrganisations as $userGroup) { if ($userGroup->id === intval($post['orgaPrimary'])) { $user->organization = $userGroup->title; } } } if (!$user->getSettingsObj()->preventPasswordChange && ($post['pwd'] !== '' || $post['pwd2'] !== '')) { if ($post['pwd'] !== $post['pwd2']) { $this->getHttpSession()->setFlash('error', \Yii::t('user', 'err_pwd_different')); } elseif (grapheme_strlen($post['pwd']) < $pwMinLen) { $msg = \Yii::t('user', 'err_pwd_length'); $this->getHttpSession()->setFlash('error', str_replace('%MINLEN%', (string)$pwMinLen, $msg)); } else { $user->changePassword($post['pwd']); // Changing the password logs out all other sessions, but this one should stay logged in RequestContext::getYiiUser()->switchIdentity($user, $this->getParams()->autoLoginDuration); } } if ($user->supportsSecondFactorAuth() && isset($post['set2fa']) && trim($post['set2fa'])) { try { $this->secondFactorAuthentication->attemptRegisteringSecondFactor($user, $post['set2fa']); } catch (\RuntimeException $e) { $this->getHttpSession()->setFlash('error', $e->getMessage()); } } if ($user->supportsSecondFactorAuth() && isset($post['remove2fa']) && trim($post['remove2fa'])) { $error = $this->secondFactorAuthentication->attemptRemovingSecondFactor($user, $post['remove2fa']); if ($error) { $this->getHttpSession()->setFlash('error', $error); } } $user->save(); if ($user->email && $user->emailConfirmed) { if (isset($post['emailBlocklist'])) { EMailBlocklist::addToBlocklist($user->email); } else { EMailBlocklist::removeFromBlocklist($user->email); } } if ($post['email'] != '' && $post['email'] != $user->email) { if ($params->confirmEmailAddresses) { $changeRequested = $user->getChangeRequestedEmailAddress(); if ($changeRequested && $changeRequested == $post['email']) { $this->getHttpSession()->setFlash('error', \Yii::t('user', 'err_emailchange_mail_sent')); } elseif (filter_var($post['email'], FILTER_VALIDATE_EMAIL)) { try { $user->sendEmailChangeMail($post['email']); $this->getHttpSession()->setFlash('success', \Yii::t('user', 'emailchange_sent')); } catch (MailNotSent | ServerConfiguration $e) { $errMsg = \Yii::t('base', 'err_email_not_sent') . ': ' . $e->getMessage(); $this->getHttpSession()->setFlash('error', $errMsg); } } else { $this->getHttpSession()->setFlash('error', \Yii::t('user', 'err_invalid_email')); } } else { $user->changeEmailAddress($post['email'], ''); $this->getHttpSession()->setFlash('success', \Yii::t('base', 'saved')); } } else { $this->getHttpSession()->setFlash('success', \Yii::t('base', 'saved')); } } if ($this->isPostSet('accountDeleteConfirm') && $this->isPostSet('accountDelete') && $params->allowAccountDeletion) { $user->deleteAccount(); RequestContext::getYiiUser()->logout(true); return new HtmlResponse($this->render('account_deleted')); } if ($user->email != '' && $user->emailConfirmed) { $emailBlocked = EMailBlocklist::isBlocked($user->email); } else { $emailBlocked = false; } if ($this->secondFactorAuthentication->userHasSecondFactorSetUp($user)) { $hasSecondFactor = true; $canRemoveSecondFactor = !$this->secondFactorAuthentication->isForcedToSetupSecondFactor($user); $addSecondFactorKey = null; } else { $hasSecondFactor = false; $canRemoveSecondFactor = false; $addSecondFactorKey = $this->secondFactorAuthentication->createSecondFactorKey($user); } return new HtmlResponse($this->render('my_account', [ 'user' => $user, 'emailBlocked' => $emailBlocked, 'pwMinLen' => $pwMinLen, 'hasSecondFactor' => $hasSecondFactor, 'canRemoveSecondFactor' => $canRemoveSecondFactor, 'addSecondFactorKey' => $addSecondFactorKey, ])); } public function actionEmailblocklist(string $code): ResponseInterface { $user = User::getUserByUnsubscribeCode($code); if (!$user) { return new HtmlErrorResponse(403, \Yii::t('user', 'err_user_acode_notfound')); } if ($this->isPostSet('save')) { $post = $this->getPostValues(); if (isset($post['unsubscribeOption']) && $post['unsubscribeOption'] === 'consultation') { $notis = UserNotification::getUserConsultationNotis($user, $this->consultation); foreach ($notis as $noti) { $noti->delete(); } } if (isset($post['unsubscribeOption']) && $post['unsubscribeOption'] === 'all') { foreach ($user->notifications as $noti) { /** @noinspection PhpUnhandledExceptionInspection */ $noti->delete(); } } if (isset($post['emailBlocklist'])) { EMailBlocklist::addToBlocklist($user->email); } else { EMailBlocklist::removeFromBlocklist($user->email); } $this->getHttpSession()->setFlash('success', \Yii::t('base', 'saved')); } return new HtmlResponse($this->render('email_blocklist', [ 'isBlocked' => EMailBlocklist::isBlocked($user->email), ])); } public function actionDataExport(): ResponseInterface { $this->forceLogin(); $user = User::getCurrentUser(); if ($user->getSettingsObj()->preventAccountPage) { return new RedirectResponse(UrlHelper::homeUrl()); } $this->getHttpResponse()->format = Response::FORMAT_RAW; $this->getHttpResponse()->headers->add('Content-Type', 'application/json'); $data = [ 'user' => $user->getUserdataExportObject(), 'motions' => [], 'amendments' => [], 'supported_motions' => [], 'supported_amendments' => [], 'comments' => [], 'sent_emails' => [], ]; foreach ($user->motionSupports as $motionSupport) { if (!$motionSupport->motion || $motionSupport->motion->isDeleted()) { continue; } if ($motionSupport->role === MotionSupporter::ROLE_INITIATOR) { $data['motions'][] = $motionSupport->motion->getUserdataExportObject(); } else { $data['supported_motions'][] = [ 'type' => $motionSupport->role, 'url' => $motionSupport->motion->getLink(true), 'title' => $motionSupport->motion->title, 'support_name' => $motionSupport->name, 'support_organization' => $motionSupport->organization, 'contact_name' => $motionSupport->contactName, 'contact_email' => $motionSupport->contactEmail, 'contact_phone' => $motionSupport->contactPhone, ]; } } foreach ($user->amendmentSupports as $amendmentSupport) { if (!$amendmentSupport->amendment || $amendmentSupport->amendment->isDeleted()) { continue; } if ($amendmentSupport->role === AmendmentSupporter::ROLE_INITIATOR) { $data['amendments'][] = $amendmentSupport->amendment->getUserdataExportObject(); } else { $data['supported_amendments'][] = [ 'type' => $amendmentSupport->role, 'url' => $amendmentSupport->amendment->getLink(true), 'title' => $amendmentSupport->amendment->getTitle(), 'support_name' => $amendmentSupport->name, 'support_organization' => $amendmentSupport->organization, 'contact_name' => $amendmentSupport->contactName, 'contact_email' => $amendmentSupport->contactEmail, 'contact_phone' => $amendmentSupport->contactPhone, ]; } } foreach ($user->motionComments as $comment) { if (!$comment->getIMotion() || $comment->getIMotion()->isDeleted()) { continue; } $data['comments'][] = $comment->getUserdataExportObject(); } foreach ($user->amendmentComments as $comment) { if (!$comment->getIMotion() || $comment->getIMotion()->isDeleted()) { continue; } $data['comments'][] = $comment->getUserdataExportObject(); } return new JsonResponse($data); } public function actionConsultationaccesserror(): HtmlResponse { $user = User::getCurrentUser(); if ($this->isPostSet('askPermission') && $this->consultation->getSettings()->allowRequestingAccess) { UserConsultationScreening::askForConsultationPermission($user, $this->consultation); $this->consultation->refresh(); } if ($user) { $askForPermission = $this->consultation->getSettings()->allowRequestingAccess; $askedForPermission = false; foreach ($this->consultation->screeningUsers as $screening) { if ($screening->userId === $user->id) { $askForPermission = false; $askedForPermission = true; } } } else { $askForPermission = false; $askedForPermission = false; } return new HtmlResponse($this->render('@app/views/errors/consultation_access', [ 'askForPermission' => $askForPermission, 'askedForPermission' => $askedForPermission, ])); } }