看板初始化提交

This commit is contained in:
zephyr
2026-06-01 21:23:12 -07:00
commit 27411ebedc
1827 changed files with 192340 additions and 0 deletions
+76
View File
@@ -0,0 +1,76 @@
<?php
namespace Kanboard\Core\Session;
use Kanboard\Core\Base;
/**
* Session Flash Message
*
* @package Kanboard\Core\Session
* @author Frederic Guillot
*/
class FlashMessage extends Base
{
/**
* Add success message
*
* @access public
* @param string $message
*/
public function success($message)
{
$this->setMessage('success', $message);
}
/**
* Add failure message
*
* @access public
* @param string $message
*/
public function failure($message)
{
$this->setMessage('failure', $message);
}
/**
* Add new flash message
*
* @access public
* @param string $key
* @param string $message
*/
public function setMessage($key, $message)
{
if (! session_exists('flash')) {
session_set('flash', []);
}
session_merge('flash', [$key => $message]);
}
/**
* Get flash message
*
* @access public
* @param string $key
* @return string
*/
public function getMessage($key)
{
$message = '';
if (session_exists('flash')) {
$messages = session_get('flash');
if (isset($messages[$key])) {
$message = $messages[$key];
unset($messages[$key]);
session_set('flash', $messages);
}
}
return $message;
}
}
+107
View File
@@ -0,0 +1,107 @@
<?php
namespace Kanboard\Core\Session;
use PicoDb\Database;
use SessionHandlerInterface;
/**
* Class SessionHandler
*
* @package Kanboard\Core\Session
*/
class SessionHandler implements SessionHandlerInterface
{
const TABLE = 'sessions';
/**
* @var Database
*/
private $db;
public function __construct(Database $db)
{
$this->db = $db;
}
#[\ReturnTypeWillChange]
public function close()
{
return true;
}
#[\ReturnTypeWillChange]
public function destroy($sessionID)
{
return $this->db->table(self::TABLE)->eq('id', $sessionID)->remove();
}
#[\ReturnTypeWillChange]
public function gc($maxlifetime)
{
return $this->db->table(self::TABLE)->lt('expire_at', time())->remove();
}
#[\ReturnTypeWillChange]
public function open($savePath, $name)
{
return true;
}
#[\ReturnTypeWillChange]
public function read($sessionID)
{
$result = $this->db->table(self::TABLE)->eq('id', $sessionID)->gt('expire_at', time())->findOneColumn('data');
// Note: Returning false display an error message and write() is never called
// preventing new sessions to be created when calling session_start()
if (empty($result)) {
return '';
}
// Sanitize session data to prevent object deserialization attacks (CWE-502).
// Using allowed_classes: false converts any serialized objects to harmless
// __PHP_Incomplete_Class instances, preventing exploitation via gadget chains.
$sanitized = @unserialize($result, ['allowed_classes' => false]);
// unserialize() returns false both on failure AND when the data legitimately
// represents boolean false (serialized as 'b:0;'). Check the raw string to
// distinguish a real deserialization error from a valid false value.
if ($sanitized === false && $result !== 'b:0;') {
// Data could not be unserialized (e.g. legacy format after handler change);
// discard it so a fresh session is created.
return '';
}
return serialize($sanitized);
}
#[\ReturnTypeWillChange]
public function write($sessionID, $data)
{
if (SESSION_DURATION > 0) {
$lifetime = time() + SESSION_DURATION;
} else {
$lifetime = time() + (ini_get('session.gc_maxlifetime') ?: 1440);
}
$this->db->startTransaction();
if ($this->db->table(self::TABLE)->eq('id', $sessionID)->exists()) {
$this->db->table(self::TABLE)->eq('id', $sessionID)->update([
'expire_at' => $lifetime,
'data' => $data,
]);
} else {
$this->db->table(self::TABLE)->insert([
'id' => $sessionID,
'expire_at' => $lifetime,
'data' => $data,
]);
}
$this->db->closeTransaction();
return true;
}
}
+117
View File
@@ -0,0 +1,117 @@
<?php
namespace Kanboard\Core\Session;
use Kanboard\Core\Base;
use Symfony\Contracts\EventDispatcher\Event;
/**
* Session Manager
*
* @package Kanboard\Core\Session
* @author Frederic Guillot
*/
class SessionManager extends Base
{
/**
* Event names
*
* @var string
*/
const EVENT_DESTROY = 'session.destroy';
/**
* Return true if the session is open
*
* @static
* @access public
* @return boolean
*/
public static function isOpen()
{
return session_id() !== '';
}
/**
* Create a new session
*
* @access public
*/
public function open()
{
if (SESSION_HANDLER === 'db') {
session_set_save_handler(new SessionHandler($this->db), true);
}
$this->configure();
if (ini_get('session.auto_start') == 1) {
session_destroy();
}
session_name('KB_SID');
session_start();
}
/**
* Destroy the session
*
* @access public
*/
public function close()
{
$this->dispatcher->dispatch(new Event(), self::EVENT_DESTROY);
// Destroy the session cookie
$params = session_get_cookie_params();
setcookie(
session_name(),
'',
time() - 42000,
$params['path'],
$params['domain'],
$params['secure'],
$params['httponly']
);
session_unset();
session_destroy();
}
/**
* Define session settings
*
* @access private
*/
private function configure()
{
// Session cookie: HttpOnly and secure flags
session_set_cookie_params(
SESSION_DURATION,
$this->helper->url->dir() ?: '/',
null,
$this->request->isHTTPS(),
true
);
// Use php_serialize handler so session data is a single serialize() call,
// which allows safe sanitization in SessionHandler::read() (CWE-502 mitigation).
ini_set('session.serialize_handler', 'php_serialize');
// Avoid session id in the URL
ini_set('session.use_only_cookies', '1');
ini_set('session.use_trans_sid', '0');
// Enable strict mode
ini_set('session.use_strict_mode', '1');
// Better session hash
ini_set('session.hash_function', '1'); // 'sha512' is not compatible with FreeBSD, only MD5 '0' and SHA-1 '1' seems to work
ini_set('session.hash_bits_per_character', 6);
// Set an additional entropy
ini_set('session.entropy_file', '/dev/urandom');
ini_set('session.entropy_length', '256');
}
}