<?php
namespace Platform\SecurityBundle\Controller;
use Cms\BulletinBundle\Model\Bulletins\PasswordResetFinBulletin;
use Cms\BulletinBundle\Service\BulletinService;
use Cms\CoreBundle\Model\Scenes\DashboardScenes\DocumentScene;
use Cms\CoreBundle\Util\Controller;
use Platform\SecurityBundle\Entity\Identity\Account;
use Platform\SecurityBundle\Entity\Login\ResetToken;
use Platform\SecurityBundle\Form\Type\LoginType;
use Platform\SecurityBundle\Form\Type\ResetPasswordRequestType;
use Platform\SecurityBundle\Form\Type\ResetPasswordType;
use Platform\SecurityBundle\Model\OAuth\AuthenticationTypesBitwise;
use Platform\SecurityBundle\Service\Login\LoginSystem;
use Platform\SecurityBundle\Service\OAuth\OAuthProviderService;
use Platform\SecurityBundle\Service\PasswordService;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\Form\FormError;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
use Symfony\Component\Security\Core\Exception\BadCredentialsException;
use Symfony\Component\Security\Http\Authentication\AuthenticationUtils;
/**
* Class LoginController
* @package Platform\SecurityBundle\Controller
*/
final class LoginController extends Controller
{
public const ROUTES__SELECT = 'platform.security.login.default.select';
public const ROUTES__RESET_PASSWORD = 'platform.security.login.standard.reset.password';
public const ROUTES__CHANGE_PASSWORD = 'platform.security.login.standard.change.password';
/**
* Handles reset password requests
*
* @param Request $request
* @return DocumentScene|RedirectResponse
*
* @Route(
* "/reset-password",
* name = LoginController::ROUTES__RESET_PASSWORD
* )
*/
public function resetPasswordAction(
PasswordService $passwordService,
Request $request
)
{
// make sure passwords are supported
if ( ! $this->getGlobalContext()->getTenant()->getAuthenticationTypes()->hasFlag(AuthenticationTypesBitwise::INTERNAL__PASSWORD)) {
throw new \Exception();
}
$form = $this->createForm(ResetPasswordRequestType::class, [], []);
if ($this->handleForm($form)) {
$account = $this->getEntityManager()->getRepository(Account::class)->findOneByEmail($form->get('email')->getData());
if ( ! empty($account) && ! empty($account->getPassword())) {
$passwordService->sendResetPasswordInitEmail(
$request,
$account
);
}
return $this->view(
'@PlatformSecurity/Login/message.html.twig',
array(
'message' => sprintf(
'A reset confirmation will be sent shortly if the email address<br /><br /><strong>%s</strong><br /><br />is linked to a valid account.',
$form->get('email')->getData()
),
'routes' => array(
'login' => LoginController::ROUTES__SELECT,
),
)
);
}
return $this->view(array(
'form' => $form->createView(),
'routes' => array(
'login' => LoginController::ROUTES__SELECT,
)
));
}
/**
* Handles change password requests
*
* @param Request $request
* @param string $token
* @return DocumentScene|RedirectResponse
* @throws \Exception
*
* @Route(
* "/change-password/{token}",
* name = LoginController::ROUTES__CHANGE_PASSWORD,
* requirements = {
* "token" = "[0-9a-f]+"
* }
* )
*/
public function changePasswordAction(
BulletinService $bulletinService,
Request $request,
string $token
)
{
// make sure passwords are supported
if ( ! $this->getGlobalContext()->getTenant()->getAuthenticationTypes()->hasFlag(AuthenticationTypesBitwise::INTERNAL__PASSWORD)) {
throw new \Exception();
}
// find the reset request
/** @var array|ResetToken[] $resets */
$resets = $this->getEntityManager()->getRepository(ResetToken::class)->findBy(array(
'token' => $token,
));
// should only be one
if (count($resets) > 1) {
throw new \Exception();
} else if (count($resets) < 1) {
throw new \Exception();
}
$reset = $resets[0];
// make sure it has not been used
if ($reset->getState() !== ResetToken::STATES__UNUSED) {
throw new NotFoundHttpException();
}
// make sure it is not expired
if ($reset->isExpired()) {
throw new NotFoundHttpException();
}
// get the account tied to this
$account = $reset->getAccount();
// form stuff
$form = $this->createForm(ResetPasswordType::class);
// try to handle form submission
if ($this->handleForm($form, $request)) {
// get the input password
$password = $form->getData()['password'];
// update with the new password
$this->getEntityManager()->save($account->setPasswordRaw($password));
// also flag the reset token as being used
$reset->setState(ResetToken::STATES__USED);
$this->getEntityManager()->save($reset);
// send out confirmation email
$bulletinService->send((new PasswordResetFinBulletin())
->setTo($account->getEmail())
->setAccount($account)
->setDashboard($this->getGlobalContext()->getDashboardUrl()));
// record log
$this->getActivityLogger()->createLog($account, $account->getId());
// back to the login screen
return $this->redirectToRoute(
LoginController::ROUTES__SELECT
);
}
return $this->view(array(
'email' => $account->getEmail(),
'form' => $form->createView(),
'routes' => array('login' => LoginController::ROUTES__SELECT)
));
}
/**
* Shows the visitor which login types are supported.
* If only one type is supported, the visitor is instead automatically redirected to the page for that type.
*
* @param Request $request
* @return DocumentScene|RedirectResponse
*
* @Route(
* "",
* name = LoginController::ROUTES__SELECT
* )
*/
public function selectAction(
LoginSystem $loginSystem,
OAuthProviderService $oAuthProviderService,
Request $request
)
{
// determine if we should allow logins
// need a way for csadmin to bypass
// TODO: do we need to move this over into the new login logic?
$allow = $this->getGlobalContext()->getTenant()->getAuthenticationTypes()->hasFlag(AuthenticationTypesBitwise::INTERNAL__PASSWORD);
if ( ! $allow) {
$allow = $request->query->getBoolean('csadmin', false);
}
// create form for standard login
$autofill = $request->query->get('autofill', null);
try {
$autofill = openssl_decrypt(
$autofill,
'aes128',
$this->getParameter('kernel.secret')
);
} catch (\Exception $e) {
$autofill = null;
}
$form = $this->createForm(
LoginType::class,
[
'username' => $autofill,
],
);
// pass through login errors
if ($error = $this->getAuthenticationUtils()->getLastAuthenticationError()) {
$form->addError(new FormError(
($error instanceof BadCredentialsException)
? 'Bad login credentials.'
: $error->getMessage(),
));
}
return $this->view(
[
'form' => ($allow) ? $form->createView() : null,
'redirect' => $request->query->get('redirect'),
'providers' => $oAuthProviderService->getProviders($this->getGlobalContext()->getTenant()),
'errors' => $this->getSession()->getFlashBag()->get(SingleSignOnController::ROUTES__FINISH.'__errors'),
'routes' => array(
'oauth__start' => SingleSignOnController::ROUTES__START,
'reset_password' => LoginController::ROUTES__RESET_PASSWORD
),
'tenant' => $this->getGlobalContext()->getTenant(),
]
);
}
/**
* @return AuthenticationUtils
*/
private function getAuthenticationUtils(): AuthenticationUtils
{
return $this->get(__METHOD__);
}
}