Login & User System

Keywords: Authentication Session User Permission Role Admin Impersonate Backend Login Access Control RBAC

Übersicht

Login-System mit Session-Management, User-Authentifizierung, Rollen-basierter Zugriffskontrolle und Admin-Impersonation.

Methoden

rex_login

Methode Parameter Rückgabe Beschreibung
setLogin($login, $password) string, string void Setzt Login-Credentials
setLogout($logout) bool void Setzt Logout-Flag
setSessionDuration($duration) int void Session-Timeout in Sekunden (default 28 Tage)
checkLogin() - bool Prüft Anmeldung gegen DB, regeneriert Session
impersonate($userId) int void Admin nimmt User-Identität an
depersonate() - void Beendet Impersonation
getUser() - rex_user|null Aktueller User (impersonated)
getImpersonator() - rex_user|null Original Admin bei Impersonation
getValue($key, $default) string, mixed mixed User-Eigenschaft aus DB
setSessionVar($var, $value) string, scalar void Session-Variable setzen
getSessionVar($var, $default) string, mixed mixed Session-Variable lesen
passwordHash($password) string string Verschlüsselt Passwort (sha1+bcrypt)
passwordVerify($pw, $hash) string, string bool Prüft Passwort gegen Hash
passwordNeedsRehash($hash) string bool Hash aktualisieren nötig?
startSession() - void Startet PHP-Session
regenerateSessionId() - void Session-ID neu generieren (Fixation)

rex_user

Methode Parameter Rückgabe Beschreibung
rex_user::get($id) int rex_user|null User-Instanz aus Cache
rex_user::forLogin($login) string rex_user|null User per Login-Name laden
rex_user::require($id) int rex_user User laden oder Exception
getId() - int User-ID
getLogin() - string Login-Name
getName() - string Anzeige-Name
getEmail() - string E-Mail-Adresse
isAdmin() - bool Admin-Berechtigung?
hasPerm($perm) string bool Permission-Check inkl. Admin
hasRole() - bool Hat zugewiesene Rolle?
getComplexPerm($key) string rex_complex_perm|null Komplexes Recht-Objekt (media/clang/structure)
getLanguage() - string Backend-Sprache (z.B. ‘de_de’)
getStartPage() - string Start-Subpage nach Login
getValue($key, $default) string, mixed mixed DB-Feld-Wert

rex_user_role_interface

Methode Parameter Rückgabe Beschreibung
hasPerm($perm) string bool Rolle hat Berechtigung?
getComplexPerm($user, $key) rex_user, string rex_complex_perm|null Komplexes Recht für User
::get($id) string rex_user_role_interface|null Rolle per ID(s) laden

rex_perm

Methode Parameter Rückgabe Beschreibung
::register($perm, $name, $group) string, string, self::* void Registriert neues Recht (GENERAL/OPTIONS/EXTRAS)
::has($perm) string bool Recht existiert?
::getAll($group) self::* array Alle Rechte einer Gruppe

rex_user_session

Methode Parameter Rückgabe Beschreibung
storeCurrentSession($login, $cookieKey, $passkey) rex_backend_login, string, string void Speichert Session in DB
clearCurrentSession() - void Löscht aktuelle Session aus DB
updateLastActivity($login) rex_backend_login void Aktualisiert Aktivität (max 1x/Min)
removeSession($sessionId, $userId) string, int bool Entfernt Session per ID
removeSessionsExceptCurrent($userId) int void Löscht alle Sessions außer aktueller
::updateSessionId($old, $new) string, string void Bei Session-Regeneration
::clearExpiredSessions() - void Cleanup abgelaufener Sessions

Praxisbeispiele

User abrufen

// Aktueller Backend-User (null wenn nicht eingeloggt)
$user = rex::getUser();
if ($user) {
    echo $user->getName();
}

// User zwingend notwendig - sonst Exception
$user = rex::requireUser();
echo $user->getEmail();

// User per ID laden
$user = rex_user::get(5);
if ($user) {
    echo $user->getLogin();
}

// User per Login-Namen laden
$user = rex_user::forLogin('admin');
if ($user) {
    echo $user->getId();
}

Berechtigungen prüfen

$user = rex::requireUser();

// Admin?
if ($user->isAdmin()) {
    echo 'Super-Admin mit allen Rechten';
}

// Einzelnes Recht prüfen
if ($user->hasPerm('media[]')) {
    echo 'Darf Media verwalten';
}

// Custom Addon-Recht
if ($user->hasPerm('myAddon[settings]')) {
    // Addon-Einstellungen erlaubt
}

// Admin-Check ist in hasPerm() integriert (Admin hat immer true)
if ($user->hasPerm('structure')) {
    echo 'Zugriff auf Struktur';
}

Complex Permissions

$user = rex::requireUser();

// Sprach-Zugriff prüfen
$clangPerm = $user->getComplexPerm('clang');
if ($clangPerm && $clangPerm->hasPerm(2)) {
    echo 'Zugriff auf Sprache 2';
}

// Media-Kategorie prüfen
$mediaPerm = $user->getComplexPerm('media');
if ($mediaPerm && $mediaPerm->hasPerm(5)) {
    echo 'Zugriff auf Media-Kategorie 5';
}

// Struktur-Kategorie prüfen
$structurePerm = $user->getComplexPerm('structure');
if ($structurePerm && $structurePerm->hasPerm(10)) {
    echo 'Zugriff auf Struktur-Kategorie 10';
}

// Modul-Zugriff
$modulePerm = $user->getComplexPerm('modules');
if ($modulePerm && $modulePerm->hasPerm(3)) {
    echo 'Darf Modul 3 verwenden';
}

User-Properties

$user = rex::requireUser();

// Standard-Properties
echo $user->getId();        // 1
echo $user->getLogin();     // 'admin'
echo $user->getName();      // 'Administrator'
echo $user->getEmail();     // 'admin@example.com'
echo $user->getLanguage();  // 'de_de'
echo $user->getStartPage(); // 'structure'

// Custom DB-Felder
echo $user->getValue('phone', '-');
echo $user->getValue('department');
echo $user->getValue('createdate');

Login-Handling

$login = new rex_backend_login();

// Login-Versuch
$login->setLogin($_POST['user_login'], $_POST['user_psw']);

if ($login->checkLogin()) {
    // Erfolgreich eingeloggt
    $user = $login->getUser();
    echo 'Willkommen ' . $user->getName();
} else {
    // Fehler
    echo $login->getMessage(); // 'Fehler bei der Anmeldung.'
}

// Session-Dauer setzen (default 28 Tage = 2419200 Sekunden)
$login->setSessionDuration(3600); // 1 Stunde

// Logout
$login->setLogout(true);
$login->checkLogin(); // Setzt Message + löscht Session

Session-Management

// Session starten (automatisch vor checkLogin)
rex_login::startSession();

// Session-Variable setzen/lesen
$login->setSessionVar('last_page', '/structure');
$lastPage = $login->getSessionVar('last_page', '/profile');

// Session-ID neu generieren (gegen Fixation)
rex_login::regenerateSessionId();

// Session-Cookies konfigurieren
$params = rex_login::getCookieParams();
// ['lifetime' => 0, 'path' => '/', 'domain' => '', 'secure' => true, 'httponly' => true, 'samesite' => 'Lax']

// Session in DB speichern (rex_user_session)
$sessionMgr = rex_user_session::getInstance();
$sessionMgr->storeCurrentSession($login);

// Letzte Aktivität aktualisieren
$sessionMgr->updateLastActivity($login);

// Aktuelle Session beenden
$sessionMgr->clearCurrentSession();

Impersonation (Admin als User)

$login = rex::getProperty('login');
$admin = $login->getUser();

if ($admin && $admin->isAdmin()) {
    // Als User 42 einloggen
    $login->impersonate(42);
    
    // Jetzt ist $login->getUser() = User 42
    echo rex::getUser()->getName(); // 'John Doe'
    
    // Original Admin ist noch abrufbar
    echo rex::getImpersonator()->getName(); // 'Administrator'
    
    // Zurück zum Admin
    $login->depersonate();
}

// Check ob impersoniert
if (rex::getImpersonator()) {
    echo 'Admins sehen aktuell als User: ' . rex::getUser()->getName();
}

Passwort-Hashing

// Passwort hashen (sha1 + bcrypt)
$hash = rex_login::passwordHash('meinPasswort');
// $2y$10$...

// Passwort prüfen
if (rex_login::passwordVerify('eingabe', $hash)) {
    echo 'Passwort korrekt';
}

// Rehash nötig? (z.B. bei Bcrypt-Cost-Änderung)
if (rex_login::passwordNeedsRehash($hash)) {
    $newHash = rex_login::passwordHash('meinPasswort');
    // In DB speichern
}

// Bei checkLogin() automatisch:
// - sha1($password) wird intern gebildet
// - Mit DB-Hash verglichen
// - Bei Erfolg: Session-Regeneration

Session-DB-Verwaltung

$sessionMgr = rex_user_session::getInstance();

// Session mit Cookie-Key speichern (Stay Logged In)
$sessionMgr->storeCurrentSession($login, 'abc123xyz', 'passkey_id');

// Spezifische Session löschen
$sessionMgr->removeSession('sess_xyz789', 42);

// Alle Sessions eines Users löschen (außer aktuelle)
$sessionMgr->removeSessionsExceptCurrent(42);

// Abgelaufene Sessions bereinigen (Cronjob)
rex_user_session::clearExpiredSessions();
// Löscht:
// - Sessions ohne Cookie älter als session_duration
// - Sessions mit Cookie älter als 3 Monate

// Bei Session-Regeneration
rex_user_session::updateSessionId('old_sess_id', 'new_sess_id');

Permission-System

// In boot.php: Custom Permission registrieren
rex_perm::register('myAddon[config]', 'Einstellungen ändern', rex_perm::OPTIONS);
rex_perm::register('myAddon[export]', 'Daten exportieren', rex_perm::GENERAL);
rex_perm::register('myAddon[import]', null, rex_perm::EXTRAS); // Nutzt i18n-Key

// Prüfen ob Permission existiert
if (rex_perm::has('myAddon[config]')) {
    // Registriert
}

// Alle Permissions einer Gruppe
$generalPerms = rex_perm::getAll(rex_perm::GENERAL);
// ['structure' => 'structure :: Struktur verwalten', 'media[]' => 'media[] :: ...' ]

$optionsPerms = rex_perm::getAll(rex_perm::OPTIONS);
// ['myAddon[config]' => 'myAddon[config] :: Einstellungen ändern']

// User-Check
if (rex::requireUser()->hasPerm('myAddon[config]')) {
    // Erlaubt
}

Backend-Login-Klasse

// Verwendung über rex Property
$login = rex::getProperty('login'); // rex_backend_login Instanz

$login->setLogin($_POST['user_login'], $_POST['user_psw']);

if ($login->checkLogin()) {
    // User authentifiziert
    $user = $login->getUser(); // rex_user Instanz
    
    // Session-Variablen
    $userId = $login->getSessionVar(rex_login::SESSION_USER_ID);
    $startTime = $login->getSessionVar(rex_login::SESSION_START_TIME);
    $lastActivity = $login->getSessionVar(rex_login::SESSION_LAST_ACTIVITY);
    
    // Impersonator wenn vorhanden
    $impersonatorId = $login->getSessionVar(rex_login::SESSION_IMPERSONATOR);
} else {
    echo $login->getMessage();
}

User-Verwaltung in Extension Point

// Bei User-Login
rex_extension::register('REX_FORM_SAVED', function(rex_extension_point $ep) {
    if ($ep->getParam('form') instanceof rex_user_form) {
        $user = rex::requireUser();
        rex_logger::factory()->info('User ' . $user->getLogin() . ' hat Profil bearbeitet');
    }
});

// Bei Session-Regeneration
rex_extension::register('SESSION_REGENERATED', function(rex_extension_point $ep) {
    $previous = $ep->getParam('previous_id');
    $new = $ep->getParam('new_id');
    
    // DB-Session-ID aktualisieren
    rex_user_session::updateSessionId($previous, $new);
});
// Login mit "Angemeldet bleiben"
$login = rex::getProperty('login');
$login->setLogin($_POST['user_login'], $_POST['user_psw']);

if ($login->checkLogin()) {
    if (!empty($_POST['stay_logged_in'])) {
        // Cookie-Key generieren
        $cookieKey = bin2hex(random_bytes(32));
        
        // Session mit Cookie speichern
        rex_user_session::getInstance()->storeCurrentSession(
            $login, 
            $cookieKey,
            $_POST['passkey'] ?? null
        );
        
        // Cookie setzen (3 Monate)
        setcookie('rex_stay', $cookieKey, time() + 7776000, '/');
    }
}

Multi-Query-Setup

// Custom Login-Query (z.B. LDAP-Integration)
$login->setLoginQuery("
    SELECT * FROM rex_user 
    WHERE login = :login 
    AND status = 1
    AND ldap_sync = 1
");

// Custom User-Query (mehr Felder)
$login->setUserQuery("
    SELECT u.*, d.name as department_name
    FROM rex_user u
    LEFT JOIN rex_department d ON u.department_id = d.id
    WHERE u.id = :id
");

// Impersonate-Query (z.B. nur aktive User)
$login->setImpersonateQuery("
    SELECT * FROM rex_user 
    WHERE id = :id 
    AND status = 1
");

User-Properties im Backend

// In Backend-Template: User-Info anzeigen
$user = rex::getUser();
if ($user) {
    ?>
    <div class="user-info">
        <strong><?= $user->getName() ?></strong>
        <?= $user->getEmail() ?>
        
        <?php if ($user->isAdmin()): ?>
            <span class="badge">Admin</span>
        <?php endif ?>
        
        <?php if ($impersonator = rex::getImpersonator()): ?>
            <div class="alert">
                Sie sind eingeloggt als <strong><?= $user->getName() ?></strong>
                (Original: <?= $impersonator->getName() ?>)
                <a href="?depersonate=1">Zurück zu <?= $impersonator->getName() ?></a>
            </div>
        <?php endif ?>
    </div>
    <?php
}

Session-Check in API

// API-Endpoint mit Login-Check
function api_myEndpoint(rex_api_function $api) {
    $user = rex::getUser();
    
    if (!$user) {
        return rex_api_function::sendJson(['error' => 'Not authenticated'], 401);
    }
    
    if (!$user->hasPerm('myAddon[api]')) {
        return rex_api_function::sendJson(['error' => 'Permission denied'], 403);
    }
    
    // API-Logik
    return rex_api_function::sendJson(['status' => 'ok']);
}

Role-Implementation (Addon: users)

// rex_user_role implementiert rex_user_role_interface

// Rolle laden
$role = rex_user_role::get('1,2,3'); // Mehrere Rollen
if ($role) {
    // Permissions prüfen
    if ($role->hasPerm('structure')) {
        echo 'Rolle hat Struktur-Recht';
    }
    
    // Complex Perm
    $clangPerm = $role->getComplexPerm($user, 'clang');
}

// Bei User
$user = rex::requireUser();
if ($user->hasRole()) {
    echo 'User hat Rolle zugewiesen';
}

Session-Timeout-Handling

$login = rex::getProperty('login');

// Session-Dauer: 28 Tage = 2419200 Sekunden (default)
// Letzter Zugriff muss innerhalb dieser Zeit sein

// Max Overall Duration: 4 Wochen (SESSION_START_TIME)
// Absolute Session-Dauer unabhängig von Aktivität

// Bei checkLogin():
// - SESSION_LAST_ACTIVITY geprüft gegen sessionDuration
// - SESSION_START_TIME geprüft gegen sessionMaxOverallDuration

// Expired?
if (!$login->checkLogin()) {
    if ($login->getMessage() === rex_i18n::msg('login_session_expired')) {
        // Session abgelaufen
        header('Location: /redaxo/index.php?rex-api-call=auth');
    }
}

Passwort-Änderung

$user = rex::requireUser();

// Neues Passwort hashen
$newPassword = $_POST['new_password'];
$newHash = rex_login::passwordHash($newPassword);

// In DB speichern
$sql = rex_sql::factory();
$sql->setTable(rex::getTable('user'));
$sql->setWhere(['id' => $user->getId()]);
$sql->setValue('password', $newHash);
$sql->update();

// Session aktualisieren (sonst logout)
rex::getProperty('login')->changedPassword($newHash);

// User-Instanz neu laden
rex_user::clearInstance($user->getId());

Backend-Page-Schutz

// In page.php: Permission-Check
$user = rex::requireUser();

if (!$user->hasPerm('myAddon[settings]')) {
    echo rex_view::error('Keine Berechtigung');
    exit;
}

// Oder über rex_be_page
rex_be_controller::getCurrentPage()->checkPermission('myAddon[settings]');

// Page-Definition in package.yml
pages:
    myAddon:
        title: 'My Addon'
        perm: myAddon[]
        subpages:
            settings:
                title: 'Einstellungen'
                perm: myAddon[settings]

Passwort-Reset-Flow

// 1. Reset-Token generieren
$user = rex_user::forLogin($_POST['email']);
if ($user) {
    $token = bin2hex(random_bytes(32));
    $sql = rex_sql::factory();
    $sql->setTable(rex::getTable('user'));
    $sql->setWhere(['id' => $user->getId()]);
    $sql->setValue('reset_token', $token);
    $sql->setValue('reset_token_time', time());
    $sql->update();
    
    // E-Mail mit Link senden
    mail($user->getEmail(), 'Passwort zurücksetzen', 
         'Link: https://example.com/reset?token=' . $token);
}

// 2. Token validieren
$token = $_GET['token'];
$sql = rex_sql::factory();
$sql->setQuery('SELECT * FROM rex_user WHERE reset_token = ? AND reset_token_time > ?', 
               [$token, time() - 3600]); // 1 Stunde gültig

if ($sql->getRows()) {
    // 3. Neues Passwort setzen
    $newHash = rex_login::passwordHash($_POST['new_password']);
    $sql->setTable(rex::getTable('user'));
    $sql->setWhere(['id' => $sql->getValue('id')]);
    $sql->setValue('password', $newHash);
    $sql->setValue('reset_token', null);
    $sql->update();
}

User-Sessions auflisten

// Alle aktiven Sessions eines Users
$userId = 42;
$sessions = rex_sql::factory()
    ->setQuery('
        SELECT session_id, starttime, last_activity, ip, useragent
        FROM rex_user_session
        WHERE user_id = ?
        ORDER BY last_activity DESC
    ', [$userId])
    ->getArray();

foreach ($sessions as $session) {
    echo $session['session_id'] . ' - ' . $session['last_activity'];
    echo ' (' . $session['ip'] . ')';
    
    // Session beenden
    if ($_POST['terminate'] === $session['session_id']) {
        rex_user_session::getInstance()->removeSession($session['session_id'], $userId);
    }
}

Login-Statistik

// Letzte Logins tracken
rex_extension::register('REX_FORM_SAVED', function(rex_extension_point $ep) {
    if ($ep->getParam('form') instanceof rex_backend_login_form) {
        $login = rex::getProperty('login');
        if ($login->checkLogin()) {
            $user = $login->getUser();
            
            rex_sql::factory()
                ->setTable(rex::getTable('user'))
                ->setWhere(['id' => $user->getId()])
                ->setValue('last_login', rex_sql::datetime())
                ->setValue('login_count', 'login_count + 1', false)
                ->update();
        }
    }
});