OpenNIT

Quelltext des aktuellen Standes

Fassung 1.1.1

sync-directory.php

6 KB
<?php
// Cron-Script: Verzeichnis-Synchronisation (Entra / LDAP)
//
// Dieses Script sollte regelmaessig per Cron ausgefuehrt werden (z.B. alle 30 Minuten):
//   */30 * * * *  /usr/bin/php /pfad/zu/bin/sync-directory.php >> /pfad/zu/storage/logs/directory-sync.log 2>&1

require_once __DIR__ . '/../vendor/autoload.php';
require_once __DIR__ . '/../bootstrap.php';

use OpenNIT\Audit\AuditLogger;
use OpenNIT\Core\Database;
use OpenNIT\Directory\DirectoryConnectionManager;
use OpenNIT\Directory\DirectorySyncRunner;

$dotenv = Dotenv\Dotenv::createImmutable(dirname(__DIR__));
$dotenv->load();

$dbConfig      = require __DIR__ . '/../config/database.php';
$runtimeConfig = [];
$runtimeFile   = __DIR__ . '/../config/runtime.php';
if (file_exists($runtimeFile)) {
    $runtimeConfig = include $runtimeFile;
}
$config = $runtimeConfig;

$db = new Database($dbConfig);

// Zeitzone aus Konfiguration setzen
$timezone = $config['app']['timezone'] ?? 'Europe/Berlin';
date_default_timezone_set($timezone);

// Cron-Lauf tracken (vor dem Source-Check, damit jeder Aufruf sichtbar wird)
$runId = null;
try {
    $runId = (int)$db->insert('cronjob_runs', [
        'job_name'   => 'directory_sync',
        'started_at' => date('Y-m-d H:i:s'),
        'status'     => 'running',
    ]);
} catch (\Throwable) {}

$appKey  = (string)($config['app']['key'] ?? ($_ENV['APP_KEY'] ?? ''));
$manager = new DirectoryConnectionManager($db, $appKey);
$manager->adoptLegacyConfig($config);

$anbindungen = $manager->enabled();
if ($anbindungen === []) {
    $msg = 'Keine Verzeichnis-Anbindung eingerichtet oder eingeschaltet – abgebrochen.';
    echo '[' . date('Y-m-d H:i:s') . '] ' . $msg . PHP_EOL;
    if ($runId) {
        try {
            $db->update('cronjob_runs', [
                'finished_at'     => date('Y-m-d H:i:s'),
                'status'          => 'success',
                'message'         => $msg,
                'items_processed' => 0,
            ], 'id = ?', [$runId]);
        } catch (\Throwable) {}
    }
    exit(0);
}

echo '[' . date('Y-m-d H:i:s') . '] Starte Verzeichnis-Abgleich über '
   . count($anbindungen) . ' Anbindung(en)…' . PHP_EOL;

try {
    $lauf = (new DirectorySyncRunner($db, new AuditLogger($db), $config, $appKey))->run();

    foreach ($lauf['results'] as $einzeln) {
        echo '[' . date('Y-m-d H:i:s') . ']   ' . $einzeln['name'] . ': ' . $einzeln['message'] . PHP_EOL;
    }
    $msg = ($lauf['ok'] ? 'OK: ' : 'FEHLER: ') . $lauf['message'];
    echo '[' . date('Y-m-d H:i:s') . '] ' . $msg . PHP_EOL;

    if ($runId) {
        $db->update('cronjob_runs', [
            'finished_at'     => date('Y-m-d H:i:s'),
            'status'          => $lauf['ok'] ? 'success' : 'error',
            'message'         => mb_substr($msg, 0, 1000),
            'items_processed' => $lauf['imported'] + $lauf['updated'],
        ], 'id = ?', [$runId]);
    }
} catch (\Throwable $e) {
    $msg = 'Exception: ' . $e->getMessage();
    echo '[' . date('Y-m-d H:i:s') . '] ' . $msg . PHP_EOL;
    if ($runId) {
        $db->update('cronjob_runs', [
            'finished_at' => date('Y-m-d H:i:s'),
            'status'      => 'error',
            'message'     => $msg,
        ], 'id = ?', [$runId]);
    }
    exit(1);
}

// Der Lizenzabgleich hängt an Entra – er läuft, sobald eine Entra-Anbindung dabei ist.
$source = '';
foreach ($anbindungen as $anbindung) {
    if ($anbindung['type'] === 'entra') {
        $source = 'entra';
        break;
    }
}

// ────────────────────────────────────────────────────────────────────────────
// Optional: M365-Lizenzen (subscribedSkus) im selben Lauf abgleichen.
// Nur wenn Quelle = entra UND Toggle "licenses.entra_sync_enabled" aktiv.
// Fallback auf den alten Key "directory.sync_licenses" (Bestandsinstalls).
// (Den modules.licenses-Check haben wir bewusst rausgelassen — der Cron
// laedt nur runtime.php, das Modul-Default aus public/index.php fehlt hier
// und fuehrte dazu, dass der Sync wortlos uebersprungen wurde.)
// ────────────────────────────────────────────────────────────────────────────
$licenseSyncToggle = $config['licenses']['entra_sync_enabled']
    ?? $config['directory']['sync_licenses']
    ?? false;
$syncLicenses = !empty($licenseSyncToggle) && $source === 'entra';

if ($syncLicenses) {
    $licRunId = null;
    try {
        $licRunId = (int)$db->insert('cronjob_runs', [
            'job_name'   => 'entra_license_sync',
            'started_at' => date('Y-m-d H:i:s'),
            'status'     => 'running',
        ]);
    } catch (\Throwable) {}

    echo '[' . date('Y-m-d H:i:s') . '] Starte Entra-Lizenz-Sync…' . PHP_EOL;

    try {
        $auditLogger = new \OpenNIT\Audit\AuditLogger($db, null);
        $licSync     = new \OpenNIT\Directory\EntraLicenseSync($db, $auditLogger, $config);
        $licResult   = $licSync->run();

        $licMsg = empty($licResult['errors'])
            ? "OK: {$licResult['imported']} neu, {$licResult['updated']} aktualisiert, {$licResult['skipped']} uebersprungen."
            : 'FEHLER: ' . implode('; ', $licResult['errors']);

        echo '[' . date('Y-m-d H:i:s') . '] Lizenz-Sync ' . $licMsg . PHP_EOL;

        if ($licRunId) {
            $db->update('cronjob_runs', [
                'finished_at'     => date('Y-m-d H:i:s'),
                'status'          => empty($licResult['errors']) ? 'success' : 'error',
                'message'         => $licMsg,
                'items_processed' => $licResult['imported'] + $licResult['updated'],
            ], 'id = ?', [$licRunId]);
        }
    } catch (\Throwable $e) {
        $licMsg = 'Exception: ' . $e->getMessage();
        echo '[' . date('Y-m-d H:i:s') . '] Lizenz-Sync ' . $licMsg . PHP_EOL;
        if ($licRunId) {
            $db->update('cronjob_runs', [
                'finished_at' => date('Y-m-d H:i:s'),
                'status'      => 'error',
                'message'     => $licMsg,
            ], 'id = ?', [$licRunId]);
        }
        // Lizenz-Sync-Fehler darf den Personen-Sync-Erfolg nicht zerstoeren.
    }
}