<?php
namespace Stimactiv\CoreBundle\Model;
use Stimactiv\LogBundle\Model\SiteLogManager;
use Symfony\Component\HttpFoundation\RequestStack;
class ReplayPreventerManager
{
private SiteLogManager $logger;
private RequestStack $requestStack;
public function __construct(RequestStack $requestStack, SiteLogManager $siteLogManager)
{
$this->logger = $siteLogManager;
$this->requestStack = $requestStack;
}
/**
* Initialize a unique token in session to prevent replay attacks
* @param $formName string the name of the form that will be used.
* @return bool|string Either the unique key created or FALSE otherwise.
*/
public function getKey(string $formName)
{
$request = $this->requestStack->getCurrentRequest();
if ($request === null) {
$this->logger->reportReplayTokenErrorIntoDB(
$formName . '_getKey', __FILE__, "Attempted validate a form token out of request : " . $formName
);
return false;
}
$session = $request->getSession();
$token = $this->createUniqueKey();
$uniqueString = $formName . ":" . $token;
$tokenStack = $session->get("antiReplayToken", []);
$tokenStack[] = $uniqueString;
$session->set("antiReplayToken", $tokenStack);
return $token;
}
/**
* Validates a anti attack token stored in session.
* @param $formName string the name of the form used.
* @return bool Either the request is validate or not.
*/
public function validate(string $formName)
{
$request = $this->requestStack->getCurrentRequest();
if ($request === null) {
$this->logger->reportReplayTokenErrorIntoDB(
$formName . '_submit', __FILE__, "Attempted validate a form token out of request : " . $formName
);
return false;
}
$session = $request->getSession();
$formToken = $request->query->get("formToken");
if ($formToken === null) {
$this->logger->reportReplayTokenErrorIntoDB(
$formName . '_submit', __FILE__, "Form token not in query parameters : " . $formName
);
return false;
}
if (false === $session->has("antiReplayToken")) {
$this->logger->reportReplayTokenErrorIntoDB(
$formName . '_submit', __FILE__, "Session does not have the antiReplayToken stack : " . $formName
);
return false;
}
$tokenStack = $session->get("antiReplayToken");
if ($tokenStack === null || false === is_array($tokenStack)) {
$this->logger->reportReplayTokenErrorIntoDB(
$formName . '_submit', __FILE__, "antiReplayToken is not as expected in session : " . $formName
);
$session->set("antiReplayToken", []);
return false;
}
$tokenStackAsString = json_encode($tokenStack);
$uniqueString = $formName . ":" . $formToken;
$key = array_search($uniqueString, $tokenStack);
if (false === $key) {
$this->logger->reportReplayTokenErrorIntoDB(
$formName . '_submit', __FILE__, "Token not in antiReplayToken (tokens in session : " . $tokenStackAsString . ", token provided by form : " . $uniqueString . ")"
);
return false;
}
unset($tokenStack[$key]);
$tokenStack = array_values($tokenStack);
$session->set("antiReplayToken", $tokenStack);
return true;
}
/**
* Generates a randomly generated cryptographic secure key.
* @return string the generated key.
*/
private function createUniqueKey(): string
{
$valid = false;
$key = null;
while (false === $valid) {
$key = bin2hex(openssl_random_pseudo_bytes(16, $valid));
}
return $key;
}
}