Files
einundzwanzig-app/app/Mcp/Support/SuperAdminModels.php
T
HolgerHatGarKeineNode 8c68b19138 Add Super-Admin tools for managing any model
- 🛠️ Introduced generic Super-Admin MCP tools, including `list-models`, `describe-model`, `list-records`, `show-record`, `create-record`, and `update-record`.
- 🛡️ Restricted modification of critical fields (e.g., passwords, roles, tokens) to enhance security.
-  Added extensive feature tests for Super-Admin functionality and access control.
- 📜 Increased pagination length to accommodate new tools on a single page.
- 🔗 Registered Super-Admin tools in `EinundzwanzigServer`.
2026-06-08 13:39:04 +02:00

163 lines
4.5 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
namespace App\Mcp\Support;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Str;
use ReflectionClass;
/**
* Registry aller bearbeitbaren Eloquent-Models unter App\Models. Dient den generischen
* Super-Admin-MCP-Tools, damit ein Super-Admin jede Entität per Name ansprechen kann
* ohne pro Model ein eigenes Tool zu pflegen.
*/
class SuperAdminModels
{
/**
* Attribute, die niemals über die generischen Super-Admin-Tools geschrieben werden
* dürfen: Passwörter & Rollen, Auth-Credentials/-Tokens (remember_token, two_factor_*,
* OAuth token/refresh_token/secret, lnurl-auth k1), die E-Mail-Verifizierung
* (email_verified_at) sowie der Nostr-Pubkey (Identitäts-Spoofing). lnurl und
* reputation bleiben bewusst editierbar.
*
* @var list<string>
*/
public const PROTECTED_ATTRIBUTES = [
'password',
'remember_token',
'two_factor_secret',
'two_factor_recovery_codes',
'two_factor_confirmed_at',
'role',
'roles',
'token',
'refresh_token',
'secret',
'k1',
'email_verified_at',
'nostr',
];
/**
* Kanonische Map Modelklasse → Tabellenname. Einmal pro Prozess ermittelt.
*
* @var array<class-string<Model>, string>|null
*/
private static ?array $models = null;
/**
* Normalisierter Name (Kurzname ODER Tabelle) → Modelklasse. Lazy aus $models gebaut.
*
* @var array<string, class-string<Model>>|null
*/
private static ?array $index = null;
/**
* Liste aller bearbeitbaren Models.
*
* @return array<int, array{key: string, class: class-string<Model>, table: string}>
*/
public static function list(): array
{
$models = [];
foreach (self::models() as $class => $table) {
$models[] = [
'key' => Str::kebab(class_basename($class)),
'class' => $class,
'table' => $table,
];
}
usort($models, fn (array $a, array $b): int => $a['key'] <=> $b['key']);
return $models;
}
/**
* Löst einen Model-Namen (Kurzname, FQCN, kebab-case oder Tabellenname) zur Klasse auf.
*
* @return class-string<Model>|null
*/
public static function resolve(?string $name): ?string
{
if (! is_string($name) || trim($name) === '') {
return null;
}
return self::index()[self::normalize(Str::of($name)->afterLast('\\')->value())] ?? null;
}
/**
* Verfügbare Model-Keys (kebab-case) als Auswahlhilfe.
*
* @return array<int, string>
*/
public static function keys(): array
{
return array_map(fn (array $m): string => $m['key'], self::list());
}
/**
* Ermittelt einmalig alle ladbaren, konkreten Models unter App\Models samt Tabellenname.
*
* @return array<class-string<Model>, string>
*/
private static function models(): array
{
if (self::$models !== null) {
return self::$models;
}
$models = [];
foreach (glob(app_path('Models').'/*.php') ?: [] as $file) {
$class = 'App\\Models\\'.basename($file, '.php');
try {
// Manche Model-Stubs erben von nicht (mehr) installierten Vendor-Klassen
// (z. B. Jetstream) und lassen sich nicht laden diese überspringen wir.
if (! class_exists($class) || ! is_subclass_of($class, Model::class)) {
continue;
}
if ((new ReflectionClass($class))->isAbstract()) {
continue;
}
$models[$class] = (new $class)->getTable();
} catch (\Throwable) {
continue;
}
}
return self::$models = $models;
}
/**
* Baut den Auflösungs-Index: Kurzname UND Tabellenname (jeweils normalisiert) → Klasse.
*
* @return array<string, class-string<Model>>
*/
private static function index(): array
{
if (self::$index !== null) {
return self::$index;
}
$index = [];
foreach (self::models() as $class => $table) {
$index[self::normalize(class_basename($class))] = $class;
$index[self::normalize($table)] = $class;
}
return self::$index = $index;
}
private static function normalize(string $value): string
{
return Str::of($value)->lower()->replace(['-', '_', ' '], '')->value();
}
}