<?php
/**
* Created by PhpStorm.
* User: uwethiess
* Date: 07.10.16
* Time: 12:36
*/
namespace App\Controller;
use App\Api\ProthelisApi;
use App\Entity\User;
use App\Form\LoginForm;
use App\Form\PasswordRequestForm;
use App\Form\PasswordResetForm;
use App\Security\LoginFormAuthenticator;
use App\Service\DBLogService;
use Symfony\Component\Mailer\MailerInterface;
use Symfony\Component\Mime\Address;
use Symfony\Component\Mime\Email;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Security\Guard\GuardAuthenticatorHandler;
use Symfony\Component\Security\Http\Authentication\AuthenticationUtils;
use Symfony\Contracts\Translation\TranslatorInterface;
class SecurityController extends AbstractController
{
/**
* @var TranslatorInterface
*/
private $translator;
private MailerInterface $mailer;
private DBLogService $DBLog;
private GuardAuthenticatorHandler $authenticatorHandler;
private LoginFormAuthenticator $authenticator;
/**
* SecurityController constructor.
*/
public function __construct(TranslatorInterface $translator, MailerInterface $mailer, DBLogService $DBLog, GuardAuthenticatorHandler $authenticatorHandler, LoginFormAuthenticator $authenticator) {
$this->translator = $translator;
$this->mailer = $mailer;
$this->DBLog = $DBLog;
$this->authenticatorHandler = $authenticatorHandler;
$this->authenticator = $authenticator;
}
/**
* @Route("/login", name="security_login")
*/
public function loginAction(AuthenticationUtils $authenticationUtils)
{
// get the login error if there is one
$error = $authenticationUtils->getLastAuthenticationError();
// last username entered by the user
$lastUsername = $authenticationUtils->getLastUsername();
$form = $this->createForm(
LoginForm::class,
[
'_username' => $lastUsername,
]
);
return $this->render(
'security/login.html.twig',
array(
'form' => $form->createView(),
'error' => $error,
'login' => true
)
);
}
/**
* @Route("/logout", name="security_logout")
*/
public function logoutAction()
{
throw new \Exception('this should not be reached.');
}
/**
* @Route("/resetpassword", name="reset_password")
*/
public function resetPassword(Request $request)
{
$form = $this->createForm(PasswordRequestForm::class);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$formData = $form->getData();
$em = $this->getDoctrine()
->getManager();
$user = $em->getRepository(User::class)
->findByEmail($formData['_email']);
if (empty($user)) {
$this->DBLog->debug('api', 'set password', [
'email' => $formData['_email'],
'response' => 'email not found',
]);
$this->addFlash('error', 'User not found');
} else {
$this->DBLog->debug('api', 'set password requested', [
'email' => $formData['_email']
]);
// $this->addFlash('success', 'User found');
$this->sendPasswordResetMail($user);
return $this->render(
'account/resetpassword.html.twig',
[
'step' => 1,
'formData' => $formData,
'resetForm' => $form->createView(),
]
);
}
}
return $this->render(
'account/resetpassword.html.twig',
[
'step' => 0,
'resetForm' => $form->createView(),
]
);
// return $this->redirectToRoute('user_account');
}
/**
* @Route("/resettoken/{token}", name="reset_password_token")
*/
public function resetPasswordToken($token, Request $request)
{
$max_time_sec = 60 * 60 * 24;
$error = false;
$userpassword = null;
$token = urldecode($token);
$token = base64_decode($token);
$token = $this->decryptData($token);
$token = json_decode($token);
$time_passed = strtotime('now') - strtotime($token->ts);
if ($time_passed > $max_time_sec) {
$error = 'expired';
}
$em = $this->getDoctrine()
->getManager();
/**
* Distinguish where the user requested the password reset.
* For account page, check user against
*/
switch ($token->source) {
case 'api':
$userdata = ProthelisApi::getUserByEmail($token->email);
if (!empty($userdata)) {
$userpassword = $userdata->password;
/**
* Check if API user already exists in local db
*/
$user = $em->getRepository(User::class)
->findbyUsername($userdata->username);
if (!empty($user)) {
/*
* user exists
*/
} else {
// create empty user for form
$user = new User();
$user->setUsername($userdata->username);
}
}
break;
case 'account':
default:
/** @var User $user */
$user = $em->getRepository(User::class)
->findByEmail($token->email);
if (!empty($user)) {
$userpassword = $user->getPassword();
}
break;
}
if (!$error && $userpassword !== $token->oldpw) {
$error = 'already_used';
}
//display form
$form = $this->createForm(PasswordResetForm::class, $user);
$form->handleRequest($request);
if ($error === false) {
if ($form->isSubmitted() && $form->isValid()) {
$formData = $form->getData();
// send new password to api
$response = ProthelisApi::setUserParam($user->getUsername(), 'password', $user->getPlainPassword());
// todo: check for negative response
$this->DBLog->debug('api', 'set password', [
'username' => $user->getUsername(),
'response' => $response,
]);
$this->addFlash(
'success',
$this->translator
->trans('account.resetpw.success.msgshort')
);
if ($user->getId() !== null) {
// user has local account, auto login
$em->flush();
return $this->authenticatorHandler
->authenticateUserAndHandleSuccess(
$user,
$request,
$this->authenticator,
'main'
);
} else {
// user has no local account, don't log in
return $this->redirectToRoute('security_login');
return $this->render(
'account/error_message.html.twig',
[
'message' => [
'title' => $this->translator
->trans('account.resetpw.success.title'),
'body' => $this->translator
->trans('account.resetpw.success.msgshort') . ' ' . $this->translator
->trans('account.resetpw.success.msgwebapp'),
'link' => [
'href' => 'https://my-prothelis.de',
'title' => 'Prothelis Web App',
],
],
]
);
}
}
} else {
$this->DBLog->debug('api', 'set password', [
'username' => $user ? $user->getUsername() : null,
'response' => ['status' => 'error', 'token' => $token, 'msg' => $error],
]);
return $this->redirectToRoute('security_login');
return $this->render(
'account/error_message.html.twig',
[
'message' => [
'title' => $this->translator
->trans('account.error.oopsie'),
'body' => $this->translator
->trans('account.resetpw.error.' . $error),
'link' => [
'href' => $this->get('router')
->generate('reset_password'),
'title' => $this->translator
->trans('account.resetpw.title'),
],
],
]
);
}
return $this->render(
'account/passwordform.html.twig',
[
'passwordForm' => $form->createView(),
]
);
}
private function sendPasswordResetMail(User $user)
{
$locale = $user->getLocale();
if (!$locale) {
$locale = 'en';
}
$this->translator->setLocale($locale);
/** @var Email $message */
$message = (new Email())
->subject($this->translator->trans('email.reset_password.subject'))
->from(new Address('support@prothelis.de', 'Prothelis Support'))
->to($user->getEmailAddress())
->html(
$this->renderView(
'email/reset_password.html.twig',
['resetLink' => $this->getResetLink($user)]
)
);
$this->mailer->send($message);
}
private function getResetLink(User $user)
{
$locale = $user->getLocale() ?: 'de';
$url = 'https://my-prothelis.de/account/'.$locale.'/resettoken/';
$token = [
'email' => $user->getEmailAddress(),
'oldpw' => $user->getPassword(),
'ts' => date('Y-m-d H:i:s'),
'source' => 'account',
];
$token = json_encode($token);
$token_enc = $this->encryptData($token);
$token_b64 = base64_encode($token_enc);
$token_url = urlencode(urlencode($token_b64));
return $url . $token_url;
}
function encryptData($plaintext)
{
$key = $_ENV['APP_SECRET'];
$iv = 'c4HQEm7dK6Niuq[H';
$ciphertext = openssl_encrypt($plaintext, 'AES-128-CBC', $key, OPENSSL_RAW_DATA, $iv);
return $ciphertext;
}
function decryptData($ciphertext)
{
$key = $_ENV['APP_SECRET'];
$iv = 'c4HQEm7dK6Niuq[H';
$plaintext = openssl_decrypt($ciphertext, 'AES-128-CBC', $key, OPENSSL_RAW_DATA, $iv);
return $plaintext;
}
}
?>