diff --git a/runtime/reconcile-user-scope.php b/runtime/reconcile-user-scope.php new file mode 100644 index 0000000..7848f8d --- /dev/null +++ b/runtime/reconcile-user-scope.php @@ -0,0 +1,195 @@ +#!/usr/bin/env php +$mount, 'source_prefix'=>$prefix, 'root'=>$real); +} + +function user_scope_storage(array $storage, $accessUser) +{ + $mount = rtrim(trim((string)($storage['local_mount'] ?? '')), '/'); + $prefix = trim((string)($storage['source_prefix'] ?? ''), '/'); + $include = trim((string)($storage['include_prefix'] ?? ''), '/'); + $candidates = array(); + + $add = function($candidate) use (&$candidates) { + if (!$candidate) return; + $candidates[$candidate['root']] = $candidate; + }; + + $add(user_scope_candidate($mount, $prefix, $accessUser)); + if ($mount !== '') + { + $add(user_scope_candidate(dirname($mount).'/'.$accessUser, $prefix, $accessUser)); + $add(user_scope_candidate($mount.'/'.$accessUser, $prefix, $accessUser)); + } + + if ($prefix !== '') + { + $parts = explode('/', $prefix); + if ($parts && (string)$parts[0] !== (string)$accessUser) + { + array_shift($parts); + $rest = implode('/', $parts); + $add(user_scope_candidate($mount.'/'.$accessUser, $rest, $accessUser)); + } + } + + if (count($candidates) !== 1) + { + user_scope_fail('Der lokale Dateistamm für Nextcloud-Benutzer '.$accessUser.' konnte nicht eindeutig bestimmt werden. Die Verbindung wird aus Sicherheitsgründen nicht gestartet.'); + } + + $resolved = reset($candidates); + return array( + 'storage_id'=>'user:'.$accessUser, + 'source_prefix'=>$resolved['source_prefix'], + 'local_mount'=>$resolved['local_mount'], + 'include_prefix'=>$include, + ); +} + +function user_scope_write_status($piwigoRoot, $id, $message) +{ + $dir = rtrim($piwigoRoot, '/').'/_data/bratonien-tools/nc-connector-status'; + if (!is_dir($dir)) @mkdir($dir, 0755, true); + $payload = array( + 'state'=>'error', + 'message'=>'Benutzerbezogene Datenquelle konnte nicht vorbereitet werden', + 'timestamp'=>time(), + 'auth_mode'=>'failed', + 'api'=>array('state'=>'not_run','message'=>''), + 'fallback'=>array('state'=>'not_run','message'=>''), + 'error_detail'=>(string)$message, + ); + @file_put_contents($dir.'/connection-'.$id.'.json', json_encode($payload, JSON_UNESCAPED_UNICODE|JSON_UNESCAPED_SLASHES)."\n", LOCK_EX); +} + +function user_scope_shell_value($value) +{ + return escapeshellarg((string)$value); +} + +$pluginRoot = dirname(__DIR__); +$piwigoRoot = dirname($pluginRoot, 2); +$dbConfig = $piwigoRoot.'/local/config/database.inc.php'; +$configDir = '/etc/bratonien-tools/nc-connector'; + +try +{ + if (!is_readable($dbConfig)) user_scope_fail('Piwigo-Datenbankkonfiguration ist nicht lesbar.'); + $conf = array(); + $prefixeTable = 'piwigo_'; + require $dbConfig; + foreach (array('db_host','db_user','db_password','db_base') as $key) + { + if (!isset($conf[$key])) user_scope_fail('Piwigo-Datenbankkonfiguration ist unvollständig: '.$key); + } + + $db = new mysqli($conf['db_host'], $conf['db_user'], $conf['db_password'], $conf['db_base']); + if ($db->connect_errno) user_scope_fail('Piwigo-Datenbank ist nicht erreichbar: '.$db->connect_error); + $db->set_charset('utf8mb4'); + $table = $prefixeTable.'bratonien_tools_nc_connections'; + $rows = $db->query("SELECT id,adapter,config_json FROM `{$table}` ORDER BY id"); + if (!$rows) user_scope_fail('Connector-Verbindungen konnten nicht gelesen werden: '.$db->error); + + while ($row = $rows->fetch_assoc()) + { + $id = (int)$row['id']; + if ((string)$row['adapter'] !== 'local') continue; + $config = json_decode((string)$row['config_json'], true); + if (!is_array($config)) continue; + if ((string)($config['origin'] ?? '') !== 'native') continue; + + $accessUser = trim((string)($config['access_user'] ?? '')); + if ($accessUser === '') continue; + + try + { + $storages = isset($config['storages']) && is_array($config['storages']) ? $config['storages'] : array(); + if (!$storages) user_scope_fail('Keine Speicherzuordnung für Benutzer '.$accessUser.' vorhanden.'); + + $resolved = array(); + foreach ($storages as $storage) + { + $item = user_scope_storage((array)$storage, $accessUser); + $key = $item['local_mount'].'|'.$item['source_prefix'].'|'.$item['include_prefix']; + $resolved[$key] = $item; + } + $config['storages'] = array_values($resolved); + $config['source_mode'] = 'user-filesystem'; + $config['access_user'] = $accessUser; + + $json = json_encode($config, JSON_UNESCAPED_UNICODE|JSON_UNESCAPED_SLASHES); + if (!is_string($json)) user_scope_fail('Benutzerbezogene Connector-Konfiguration konnte nicht serialisiert werden.'); + $escaped = $db->real_escape_string($json); + if (!$db->query("UPDATE `{$table}` SET config_json='{$escaped}' WHERE id={$id} LIMIT 1")) + { + user_scope_fail('Benutzerbezogene Connector-Konfiguration konnte nicht gespeichert werden: '.$db->error); + } + + $base = $configDir.'/connection-'.$id; + $storagePath = $base.'.storages.tsv'; + $configPath = $base.'.conf'; + if (!is_file($configPath)) continue; + + $lines = array('# storage_idsource_prefixlocal_mountinclude_prefix'); + foreach ($config['storages'] as $storage) + { + $lines[] = (string)$storage['storage_id']."\t".(string)$storage['source_prefix']."\t".(string)$storage['local_mount']."\t".(string)$storage['include_prefix']; + } + file_put_contents($storagePath, implode("\n", $lines)."\n", LOCK_EX); + chmod($storagePath, 0600); + + $existing = file($configPath, FILE_IGNORE_NEW_LINES); + if (!is_array($existing)) user_scope_fail('Runtime-Konfiguration konnte nicht gelesen werden.'); + $filtered = array_values(array_filter($existing, function($line) { + return strpos($line, 'SOURCE_MODE=') !== 0 && strpos($line, 'ACCESS_USER=') !== 0; + })); + $filtered[] = 'SOURCE_MODE=user-filesystem'; + $filtered[] = 'ACCESS_USER='.user_scope_shell_value($accessUser); + file_put_contents($configPath, implode("\n", $filtered)."\n", LOCK_EX); + chmod($configPath, 0600); + } + catch (Throwable $e) + { + @unlink($configDir.'/connection-'.$id.'.conf'); + user_scope_write_status($piwigoRoot, $id, $e->getMessage()); + fwrite(STDERR, 'NC Connector #'.$id.': '.$e->getMessage()."\n"); + } + } + exit(0); +} +catch (Throwable $e) +{ + fwrite(STDERR, 'NC Connector User Scope: '.$e->getMessage()."\n"); + exit(1); +}