Sessionverwaltung
Diese Klasse speichert eine beliebig große Session in Cookies. Die SIDs sind SHA-1-Hashes.
<?php
/*
******************************************************************************
* FaraFIN-Webseite SoPra SS 2008 Uni Magdeburg
* ---------------- ---------------------------
*
* Dieser Code ist im Rahmen eines Softwareprojekts an der Uni Magdeburg
* entstanden. Er baut auf PHP 5.2 auf und darf unter den Bedingungen der
* BSD-Lizenz verwendet werden.
*
* Christoph Mewes <christoph@ymail.com>
******************************************************************************
*/
if ( !defined('FaraFIN') ) die('Fremdaufrufe sind verboten.');
/**
* Sitzungsverwaltung
*
* Diese Klasse dient zur Verwaltung der Benutzer-Sitzungen.
* Sessions werden dabei in der Datenbank abgelegt und
* auf der Clientseite in Cookies abgelegt. Die
* Identifikation erfolgt über SHA1-Hashes von möglichst
* zufälligen Werten.
*
* Eine Session hat dabei eine bestimmte Lebensdauer sowie
* beliebig viele weitere Daten, die der Klasse mittels
* setValue() und getValue() übermittelt werden können.
*/
class Session extends Object
{
private $data = array(); ///< array die Daten der Session
private $sid = ""; ///< string die aktuellen Session-ID
private $changes = false; ///< bool true, wenn Änderungen an den Daten vorgenommen wurden
private $cookieDeleted = false; ///< bool true, wenn das Cookie dieser Session gelöscht wurde
// ===========================================================================
// SCHNITTSTELLE
// ===========================================================================
/**
* Konstruktor
*
* Der Konstruktor versucht, eine vorhandene Session
* fortzuführen, sofern er eine in der Datenbank findet.
* Vorher räumt er diese jedoch noch auf, um alte,
* verwaiste Sessions nicht länger als Datenmüll
* rumliegen zu lassen.
*
* Sessions werden ausschließlich in Cookies gespeichert
* und haben eine bestimmte Lebensdauer.
*
* Sollte eine Session gefunden werden, wird die
* Ablaufzeit in der Datenbank erhöht und das Cookie
* erneuert. Wird keine Session gefunden, wird eine neue
* angelegt und ebenfalls ein Cookie beim Besucher
* erzeugt.
*
* @param int $lifetime die Dauer in Sekunden, die ein Cookie gültig sein soll
* @param string $cookiename der Name des Cookies
*/
public function __construct($lifetime, $cookiename)
{
if(DEBUG) Debug::checkArguments(__METHOD__, "is", $lifetime, $cookiename);
$sql = Registry::get("sql");
// Erstmal räumen wir die Tabelle auf.
$this->cleanupSessionTable();
// Wir suchen uns eine Session-ID...
$this->sid = self::getSIDFromCookie($cookiename);
// ... und rufen für die gefundene SID die Daten ab.
$this->data = $sql->fetch('*', PREFIX.'sessions', "`session_id` = \"".$this->sid."\"");
// Wenn wir keine Daten haben, erzeugen wir eine leere Session.
if ( $this->data == false )
{
$this->sid = $this->createFreshSession($this->sid, $lifetime);
}
else
{
// Wenn wir eine gefunden haben, erhöhen wir die Ablaufzeit.
$expire = time()+$lifetime;
$sql->query("UPDATE ".PREFIX."sessions SET `expiry` = $expire WHERE `session_id` = \"".$this->sid."\"");
// Außerdem entfernen wir die Ablaufzeit und SID aus dem $data-Array.
// Diese Daten sind auch über das Interface NICHT änderbar.
unset($this->data['session_id']);
unset($this->data['expiry']);
$this->dropCookie($lifetime, $cookiename);
}
}
/**
* Cookie ablegen
*
* Diese Methode legt mittels setcookie() ein Cookie
* beim Besucher ab.
*
* @param int $lifetime die Dauer in Sekunden, die ein Cookie gültig sein soll
* @param string $name der Name des Cookies
*/
public function dropCookie($lifetime, $name)
{
if(DEBUG) Debug::checkArguments(__METHOD__, "is", $lifetime, $name);
if ( !$this->cookieDeleted )
{
setcookie($name, $this->sid, time()+$lifetime, '/');
}
}
/**
* Cookie löschen
*
* Diese Methode setzt das Ende der Gültigkeit eines
* Cookies auf jetzt - 1000 Sekunden und weist den
* Browser damit an, das Cookie zu löschen.
*
* @param string $name der Name des Cookies
*/
public function removeCookie($name)
{
if(DEBUG) Debug::checkArguments(__METHOD__, "s", $name);
$this->cookieDeleted = true;
setcookie($name, '', time()-1000, '/');
}
/**
* Session löschen
*
* Diese Methode löscht die Session aus der Datenbank.
*/
public function remove()
{
if(DEBUG) Debug::checkArguments(__METHOD__);
$sql = Registry::get("sql");
$sql->query("DELETE FROM ".PREFIX."sessions WHERE `session_id` = \"".$this->sid."\"");
}
/**
* Session speichern
*
* Diese Methode speichert alle Änderungen, die
* zwischenzeitlich an den Sessiondaten vorgenommen
* wurden, in der Datenbank ab.
*
* Die Methode ist unabhängig vom SQL-Layout, nur die
* Attribute expiry und session_id müssen vorhanden
* sein, alle anderen Attribute erzeugt es je nach dem,
* welche sie im Konstruktor vorgefunden hat und welche
* dem Objekt über setValue() mitgeteilt wurden.
*
* @return bool true, wenn das Aktualisierung erfolgreich verlief, sonst false
*/
public function update()
{
if(DEBUG) Debug::checkArguments(__METHOD__);
if ( $this->changes == false )
{
return false;
}
$sql = Registry::get("sql");
// Wir speichern die Informationen wieder in der Datenbank.
$query = "UPDATE ".PREFIX."sessions SET `expiry` = `expiry`";
foreach ( $this->data as $key => $value )
{
if ( strlen($key) >= 2 && substr($key, 0, 2) == "__" )
{
continue;
}
$query .= ", `$key` = \"$value\"";
}
$query .= " WHERE `session_id` = \"".$this->sid."\"";
return $sql->query($query);
}
/**
* Wert setzen
*
* Diese Methode setzt einen Wert für die Session, der
* dann später beim Aufruf von update() dauerhaft in der
* Datenbank gespeichert wird. Aus diesem Grund dürfen
* nur solche Schlüssel verwendet werden, die im
* SQL-Layout der Session-Tabelle vorkommen, da sonst
* die Abfrage fehlschlagen und die Session nie
* gespeichert werden wird.
*
* Außerdem dürfen die Schlüssel "session_id" und
* "expiry" nicht verwendet werden, da diese intern von
* der Klasse verwaltet werden und niemand von außen an
* ihnen rumwerkeln sollte.
*
* @param string $key der Schlüssel, der gesetzt werden soll
* @param string $value der Wert zum Schlüssel
* @return bool true, wenn der Wert gesetzt wurde, sonst false
*/
public function set($key, $value)
{
if(DEBUG) Debug::checkArguments(__METHOD__, "sm", $key, $value);
if ( $key == "session_id" || $key == "expiry" )
{
return false;
}
$this->data[$key] = $value;
$this->changes = true;
return true;
}
/**
* Einen Wert auslesen
*
* Diese Methode liefert ein Attribut der Session
* zurück. Wenn der Name nicht gefunden wurde, wird null
* zurückgegegen.
*
* @param string $key der Attributsname, dessen Wert zurückgegeben werden soll
* @return mixed null, wenn der Wert nicht gefunden wurde, sonst mixed (i.d.R. string)
*/
public function get($key)
{
if(DEBUG) Debug::checkArguments(__METHOD__, "s", $key);
if ( $key == "sid" ) return $this->sid;
return isset($this->data[$key]) ? $this->data[$key] : null;
}
/**
* Die Session-ID auslesen
*
* Diese Methode liefert die aktuelle Sitzungskennung
* zurück. Man könnte sie auch mit
*
* @code
* $session->getValue('session_id')
* @endcode
*
* holen.
*
* @return string die Session-ID
*/
public function getSID()
{
if(DEBUG) Debug::checkArguments(__METHOD__);
return $this->sid;
}
/**
* Sessions aufräumen
*
* Diese Methode löscht alle Session, die sich bereits
* über ihrem Verfallsdatum (expiry) befinden. Sie wird
* bei jedem Konstruktor-Aufruf sowie von der
* Maintenance-Klasse aufgerufen.
*
* @return int die Anzahl der gelöschten Sessions
*/
public function cleanupSessionTable()
{
if(DEBUG) Debug::checkArguments(__METHOD__);
$sql = Registry::get("sql");
$sql->query("DELETE FROM ".PREFIX."sessions WHERE `expiry` <= ".time());
return $sql->affectedRows();
}
public static function isValid($lifetime, $cookiename)
{
if(DEBUG) Debug::checkArguments(__METHOD__, "is", $lifetime, $cookiename);
$sql = Registry::get("sql");
// Wir suchen uns eine Session-ID...
$sid = self::getSIDFromCookie($cookiename);
// ... und rufen für die gefundene SID die Daten ab.
return $sql->count(PREFIX.'sessions', "`session_id` = \"$sid\"") == 1;
}
// ===========================================================================
// PRIVATE METHODEN
// ===========================================================================
/**
* SID aus Cookie auslesen
*
* Diese Methode versucht, aus einem Cookie die
* Session-ID auszulesen. Wird ein Cookie gefunden, dass
* dem angegebenen Namen entspricht, wird der inhalt
* geprüft. Ist es keine 40 Zeichen lange Zeichenkette,
* die nur aus [a-z0-9] besteht, so wird von dieser
* Methode eine neu genrierte SID zuückgegeben.
*
* Wird kein Cookie gefunden, wird ebenfalls eine neue
* SID generiert.
*
* @param string $cookiename der Name der zu untersuchenden Cookies
* @return string die gefundene oder neu generierte SID
*/
private static function getSIDFromCookie($cookiename)
{
if(DEBUG) Debug::checkArguments(__METHOD__, "s", $cookiename);
if ( isset($_COOKIE[$cookiename]) )
{
$cleaned = self::cleanSID($_COOKIE[$cookiename]);
return $cleaned ? $cleaned : self::createFreshSID();
}
else
{
return self::createFreshSID();
}
}
/**
* SID säubern
*
* Diese Methode überprüft eine mögliche SID auf formale
* Gültigkeit. Dazu darf sie nur aus alphanumerischen
* Zeichen bestehen und muss 40 Zeichen lang sein. Ist
* die mögliche SID wohlgeformt, so wird sie
* zurückgegeben, ansonsten wird ein leerer String
* zurückgegeben.
*
* @param string $sid die mögliche Session-ID
* @return string die gereinigte Session-ID
*/
private static function cleanSID($sid)
{
if(DEBUG) Debug::checkArguments(__METHOD__, "s", $sid);
if ( !preg_match("/^[a-z0-9]+$/i", $sid) || strlen($sid) != 40 )
{
return '';
}
else
{
return $sid;
}
}
/**
* Neue Session-ID generieren
*
* Diese Methode generiert eine neue Session-ID. Dazu
* verwendet sie einen Zufallswert und die Zeit und
* bildet davon mittels uniqid und sha1 eine 40 Zeichen
* lange Zeichenkette.
*
* @return string eine neue, zufällige Session-ID
*/
private static function createFreshSID()
{
if(DEBUG) Debug::checkArguments(__METHOD__);
return sha1(uniqid(rand()).time());
}
/**
* Neue Session erzeugen
*
* Diese Methode erzeugt einen neuen Session-Datensatz
* in der Datenbank. Dabei versucht sie so lange, den
* Datensatz einzufügen, bis eine noch nicht verwendete
* Session-ID gefunden wurde. Nach jedem Misserfolg
* wird mittels createFreshSID() ein neuer SID-Kandidat
* erzeugt.
*
* @param string $sid die einzufügende SID
* @param int $lifetime der Zeitpunkt, zu dem die Session ablaufen soll
* @return string die (ggf. erneuerte) SID
*/
private function createFreshSession($sid, $lifetime)
{
if(DEBUG) Debug::checkArguments(__METHOD__, "si", $sid, $lifetime);
$sql = Registry::get("sql");
$expire = time()+$lifetime;
// Wir schauen, ob wir die neue SID einfügen können. Solange das
// nicht geht, erzeugen wir eine neue. Da sich die SID also im
// Verlauf der Methode ändern kann, geben wir sie am Ende auch
// zurück an den Konstruktor.
$query = "INSERT INTO ".PREFIX."sessions (`session_id`,`expiry`) VALUES ('%SID%',$expire)";
while ( !$sql->query(str_replace("%SID%", $sid, $query)) )
{
$sid = $this->createFreshSID();
}
$this->data = $sql->fetch('*', PREFIX.'sessions', "`session_id` = \"".$this->sid."\"");
unset($this->data['session_id']);
unset($this->data['expiry']);
return $sid;
}
// ===========================================================================
// SIGNATUR
// ===========================================================================
///@cond INC_SIGNATURE
public function signature()
{
if(DEBUG) Debug::checkArguments(__METHOD__);
return sprintf("Object<Session> (%s)", $this->sid);
}
///@endcond
}
?>
Snippetdetails
- hinzugefügt: 27.09.2008
- aktualisiert: 27.09.2008
- Snippet herunterladen
Kommentar verfassen
Fehler gefunden? Doofer Code? Ein kleines "Danke!"? Hinterlasse einfach einen Kommentar.
Dein Kommentar wird erst nach einer manuellen Prüfung angezeigt.