You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
IOcornerstone/src/Framework/Trait/Security/CsrfTokenFunctions.php

146 lines
3.8 KiB

<?php
declare(strict_types=1);
/*
* @author Robert Strutts
* @copyright (c) 2026, Robert Strutts
* @license MIT
*/
namespace IOcornerstone\Framework\Trait\Security;
use IOcornerstone\Framework\Configure;
trait CsrfTokenFunctions
{
/**
* Set Session to use CSRF Token
* Useful for JSON data...
* @return string CSRF Token
*/
public static function createCsrfToken(): string
{
self::setup();
$newToken = self::csrfToken();
$_SESSION['csrf_pool'][$newToken] = time();
return $newToken;
}
/**
* Keep only last 15 tokens to prevent memory issues.
*/
public static function cleanUp(){
$keepOnly = 15;
if (count($_SESSION['csrf_pool']) > $keepOnly) {
$_SESSION['csrf_pool'] = array_slice($_SESSION['csrf_pool'], -$keepOnly, null, true);
}
}
/**
* Get CSRF Token for use with HTML Form
* @return string Hidden Form with token set
*/
public static function csrfTokenTag(): string
{
$token = self::createCsrfToken();
return "<input type=\"hidden\" id=\"csrf_token\" name=\"csrf_token\" value=\"" . $token . "\">";
}
public static function csrfTokenStillValid(string $csrfTokenKeyName = ""): bool
{
if (empty($csrfTokenKeyName)) {
$csrfTokenKeyName = $_POST['csrf_token'] ?? "";
}
$validTimeStamp = self::csrfTokenIsValid($csrfTokenKeyName);
if ($validTimeStamp === false) {
return false;
}
$recent = self::csrfTokenIsRecent($validTimeStamp);
self::destroyCsrfToken($csrfTokenKeyName); // Done, so clean up Consume token
return $recent;
}
/**
* Get an Cross-Site Request Forge - Prevention Token
* @return string
*/
private static function csrfToken(): string
{
return self::getUniqueId();
}
private static function setup(): void
{
$clean_ts = intval(Configure::get(
'security',
'token_life'
));
if ($clean_ts < 60) {
$clean_ts = 3600;
}
if (!isset($_SESSION['csrf_pool'])) {
$_SESSION['csrf_pool'] = [];
}
// Clean old tokens by useage time token_life
foreach ($_SESSION['csrf_pool'] as $key => $timestamp) {
if ($timestamp < (time() - $clean_ts)) {
unset($_SESSION['csrf_pool'][$key]);
}
}
}
private static function csrfTokenIsValid(string $csrfTokenKeyName = ""): false|int
{
if (empty($csrfTokenKeyName)) {
$csrfTokenKeyName = $_POST['csrf_token'] ?? "";
}
if (!isset($_SESSION['csrf_pool'][$csrfTokenKeyName])) {
return false;
}
$tsToken = $_SESSION['csrf_pool'][$csrfTokenKeyName];
if (is_int($tsToken)){
return $tsToken;
}
return false;
}
private static function csrfTokenIsRecent($tokenTimeStored): bool
{
$max_elapsed = intval(Configure::get(
'security',
'max_token_age'
));
if ($max_elapsed < 30) {
$max_elapsed = 60 * 60 * 24; // 1 day
}
return ($tokenTimeStored + $max_elapsed) >= time();
}
/**
* Destroy CSRF Token from Session
* @return bool success
*/
private static function destroyCsrfToken(string $csrfTokenKeyName = ""): bool
{
if (empty($csrfTokenKeyName)) {
$csrfTokenKeyName = $_POST['csrf_token'] ?? "";
}
if (!isset($_SESSION['csrf_pool'][$csrfTokenKeyName])) {
return false;
}
unset($_SESSION['csrf_pool'][$csrfTokenKeyName]); // Consume token
return true;
}
}