major
#29449
QueryExecutor sichert das Ergebnis einer Skriptausführung ab
Zugriffe in TL-Script liefern referenzierte Objekte ungefiltert – konsistent zur Oberfläche, die ein referenziertes Objekt immer mit seiner Beschriftung anzeigt und nur die Navigation in das Objekt absichert. Verweigert wird lediglich der Zugriff auf die Attribute eines Objektes, das der Nutzer nicht lesen darf.
Damit muss das Endergebnis eines Skriptes vom Aufrufer selbst abgesichert werden, über SearchExpression#filterSecurity(Object). Das tun im Engine-Code sieben Stellen (ListModelByExpression, TreeModelByExpression, ModelProviderByExpression, SelectionUpdaterByExpression, TableContentProvider, ScriptComponent, ForeignObjectsTemplateProvider) – alle übrigen Aufrufer tun es stillschweigend nicht. Allein außerhalb von com.top_logic.model.search benutzen 139 Dateien einen QueryExecutor. „Der Aufrufer muss daran denken“ heißt in der Praxis: es wird vergessen, und das Ergebnis enthält Objekte, die der angemeldete Nutzer nicht sehen darf.
Lösung
Ein QueryExecutor ist die Schnittstelle, über die eine Anwendung ein Skript ausführt. Er sichert sein Ergebnis daher selbst ab: executeWith(EvalContext, Args) filtert das Ergebnis auf die Leserechte des angemeldeten Nutzers, solange die Security nicht mit disableSecurity() abgeschaltet ist. disableSecurity() schaltet den Ergebnis-Filter zusammen mit der Security des Ausdrucks ab – ein Schalter, nicht zwei.
Ausführung und Security-Schalter sind Template-Methoden (internalExecuteWith, internalDisableSecurity), damit eine Unterklasse den Filter nicht umgehen kann. Die sieben Aufrufer, die ihr Ergebnis selbst abgesichert haben, tun das nicht mehr.
Zwischenergebnisse dürfen nicht gefiltert werden
Der Filter darf dort nicht greifen, wo das Ergebnis ein Zwischenwert einer größeren Operation ist und nicht an den Nutzer geht: Ein Objekt, über das die Operation navigiert, das sie verknüpft oder nur zählt, darf nicht verschwinden, bloß weil der Nutzer es nicht lesen darf. Solche Ausführungen benutzen executeIntermediate(…):
- QueryExecutorMethod – eine konfigurierte Funktion, die aus einem anderen Skript heraus aufgerufen wird. Ihr Ergebnis ist ein Zwischenergebnis des aufrufenden Skriptes, dessen eigene Ausführung es absichert.
- Die Model-Bindings des XML-Importers – ein Import-Schritt (ein Objekt auflösen, auf das verknüpft wird).
- DeferredQueryExecutor – würde sonst doppelt filtern, weil der verzögert kompilierte Executor sein Ergebnis ebenfalls absichert.
Modellelemente sind ausgenommen
Ein Typ, ein Attribut oder ein Modul ist Metadaten, mit denen eine Berechnung arbeitet, kein Fachobjekt. Für das Meta-Modell sind keine Rechte konfiguriert, und persistente Modellelemente sind BoundObject`s – ein Skript, das den Typ eines Objektes liefert, gäbe für jeden nicht-administrativen Nutzer `null zurück:
`TestTLScriptSecurity:Project` -> null $p.type() -> null
Da der Filter jetzt jedes Skriptergebnis betrifft, würde das Berechnungen zerstören statt Daten zu schützen. SearchExpression#filterSecurity(Person, Object) lässt TLModelPart-Werte deshalb durch. Der Zugriff auf die Attributwerte eines Modellelementes bleibt davon unberührt und wird weiterhin von der Prüfung des Attributzugriffs entschieden.
TL-Script-Funktion `filterSecurity`
Die Funktion behält ihren Zweck, einen Wert innerhalb eines Skriptes abzusichern – etwa ein Teilergebnis, das das Skript selbst ausgibt oder weitergibt. Ein filterSecurity am Ende eines Skriptes ist überflüssig.
Ihr ungenutztes FilterSecurity#ensureOnlyAllowedResults(SearchExpression) entfällt: Den Filter als Ausdruck um die Wurzel eines kompilierten Queries zu legen hätte die Argumente jedes parametrisierten Queries verschluckt, weil GenericMethod#internalEval die übergebenen Args ignoriert.
Migration
1. Ergebnisse werden gefiltert. Wer einen QueryExecutor für interne Berechnungen benutzt, deren Ergebnis nicht an einen Nutzer geht, muss die Security abschalten – sonst fehlen im Ergebnis Objekte, die der angemeldete Nutzer nicht lesen darf. Das betrifft keinen Compile-Fehler, sondern das Laufzeitverhalten:
{{{#!java QueryExecutor executor = QueryExecutor.compile(expr); executor.disableSecurity(); }}}
Die Erweiterungspunkte der Engine, die mit Definer's-Rights arbeiten (berechnete Attribute, Attribut-Locator, Constraints, Lock-Strategien, Migrations- und Wartungsskripte, JMS-Consumer, Prozess-Engine), tun das bereits.
2. Überschriebene Methoden. Eine eigene QueryExecutor-Unterklasse überschreibt nicht mehr executeWith(EvalContext, Args) und disableSecurity() – beide sind final – sondern:
{{{#!java @Override protected Object internalExecuteWith(EvalContext definitions, Args args) { … }
@Override protected void internalDisableSecurity() { … } }}}