Suche Konzept: Korrektheit des Aufrufers feststellen

SimProtect

Aktives Mitglied
Hallo zusammen,
Ich wende mich heute mit einem vielleicht etwas abstrusen Problem an Euch.
Ich wurde heute mit einer Nutzeranfrage konfrontiert, die ein älteres System betrifft (das in Kürze ersetzt werden soll).

Zur Grundthematik
Es handelt sich um ein agentenbasiertes Simulationssystem, bei welchem die einzelnen aktiven Simulationsentitäten (Agenten) verschiedene Aktionen ausführen können. Hat sich ein Agent für eine Aktion (z.B. eine Bewegung) entschieden, so fragt er bei einem ActionHandler nach, ob diese Aktion ausgeführt werden kann. Dieser ActionHandler führt dann entweder diese Aktion aus oder lehnt sie ab. Der Agent wird darüber über eine Response informiert und kann bei Bedarf entsprechend darauf reagieren.

Zur Problematik
Besagter Nutzer hat nun beklagt, dass der Aufruf am ActionHandler nicht abgesichert sei. Gemäß API übernimmt die Methode die Bewegungsanfrage und eine Entität, die als Anfragesteller angenommen (!) wird (Sprich: Ein Agent übergibt seinen Bewegungswunsch und sich selbst).
Nicht abgesichert bedeuetet in diesem Falle aber, dass der Agent jedoch auch einen x-beliebigen anderen Agenten übergeben könnte, für welche andere Bewegungsregeln gelten
(Konstruiertes Beispiel: Agent A ist eine Turmfigur beim Schach; A stellt eine Bewegungsanfrage, übergibt jedoch den Agenten B, der eine Springerfigur darstellt - somit könnte sich A die Bewegungsroutine von B erschleichen.

Man möchte jetzt vielleicht fragen, warum das ein Problem ist. (das war auch meine erste Überlegung) - immerhin sagt die Dokumentation der API ganz deutlich, was erwartet wird und auch was die Konsequenzen einer Falschangabe sind. Ein Designer, der bewusst diese Regeln verletzt, muss theoretisch auch mit den Konsequenzen leben. Aber nun gut: Es könnte ja auch ein fehlerhafte Aufruf besagter Methode sein, der somit unbemerkt bliebe.

Ich weiß auch nicht, warum die entsprechende Design-Entscheidung damals so getroffen wurde.

Meine Frage
Hat Jemand eine Idee für mich, wie dieser simple Aufruf abgesichert werden könnte?

Architekturtechnisch ist es so, dass die Methoden des ActionHandler public sind - also von jeder Implementierung eines Agenten aufgerufen werden könnten. Eine reine Zusammenfassung der Methode in einer abstrakten Oberklasse ist also hier nicht zielführend.

Aufgrund von möglichen Kompatibilitätsproblemen ist eine Anpassung der API des Handlers kritisch zu betrachten (gerade in Anbetracht der Tatsache, dass das Projekt ohnehin ersetzt werden soll)

Nun ist die Frage, ob ich irgendwie die Identität des Aufrufers sicher stellen bzw. überprüfen kann? (dem besagten Nutzer reicht es, wenn in diesem Fall z.B. eine Exception fliegt)

Alternativ: Gibt es eine Möglichkeit, den entsprechenden Methodenparameter quasi automatisch zu injecten? Also, dass man die Methode nur mit einem Parameter (der Bewegungsanfrage) aufruft und dann beispielswiese das aufrufende Objekt automatisch einsetzt?

Oder hat jemand vielleicht noch eine andere Idee, die ich jetzt gar nicht im Hinterkopf habe?
 
Das was du hier forderst, ist unsinnig. HIER hast du eventuell so was ähnliches, dass du wünscht.

Ich greif jetzt dein Beispiel auf und habe ein Adapter Pattern, dass mir vorgaukelt ein Turm zu sein, aber eigentlich ein Bauer ist.
Was hindert den Adapter daran, jetzt seine Aktionen an den Bauern zu delegieren (ala Wolfs im Schafspelz). Nichts! Denn der Handler sieht den Turm, überprüft die Identität, aber Züge macht der Bauer.

Also ist es unsinnig solche Abfragen überhaupt zu wollen, selbst wenn ich eine Methode schreiben würde:
Java:
public static double pi() {
  return 3d;
}
ist es die Verantwortung des Programmierers.
Oder sehe ich die Fragestellung falsch?
 
extenden die Agenten eine bestimmte Oberklasse? Dann könnte man in dieser die Methode implementieren, die den Handler aufruft, und darin this übergeben. Der wirkliche Agent ruft dann diese Methode ohne this auf.


Allerdings: Wenn Turm A Bauer B übergibt, warum führt der Handler dann überhaupt die Methode mit Turm A aus, er kennt ja nur Bauer B?
 
Alternativ: Gibt es eine Möglichkeit, den entsprechenden Methodenparameter quasi automatisch zu injecten? Also, dass man die Methode nur mit einem Parameter (der Bewegungsanfrage) aufruft und dann beispielswiese das aufrufende Objekt automatisch einsetzt?
Du injectest den Caller schon automatisch, wenn du this mitlieferst. Selbst solche automatischen Injektionen können "getürkt" werden.
 
extenden die Agenten eine bestimmte Oberklasse? Dann könnte man in dieser die Methode implementieren, die den Handler aufruft, und darin this übergeben. Der wirkliche Agent ruft dann diese Methode ohne this auf.


Allerdings: Wenn Turm A Bauer B übergibt, warum führt der Handler dann überhaupt die Methode mit Turm A aus, er kennt ja nur Bauer B?

Ja, sie erben letztlich von der AbstractAgent-Klasse. Allerdings behebt ein hineinziehen in diese Klasse nicht das Grundproblem: Die Schnittstelle des ActionHandlers ist public. Somit kann trotzdem an der API "vorbeigeschossen" werden (der Aufruf kann immer noch manuell erfolgen.

Zum zweiten Punkt: Oh, entschuldigung, das hatte ich nicht richtig beschrieben. Es gibt pro Simulation EINEN ActionHandler - nicht pro Agent. Jeder ActionHandler ist dabei eine Implementierung des IActionHandler-Interfaces und beschreibt unterschiedliche Arten, wie die Simulation auf Aktionen reagiert. Grundgedanke war damals (so die Architekturdokumentation), dass man auch asychrone Simulationen verwalten kann. Jeder Agent kann dabei seine Berechnungen durchführen, während die übrigen Agenten dies auch tun. So kann es dazu kommen, dass ein Agent zu lange braucht und seine getroffene Entscheidung nicht mehr möglich ist (z.B. weil eine Koordinate bereits durch einen anderen Agenten blockiert ist).
Der ActionHandler war in diesen Fälle nicht nur dazu da, die Aktionen der Agenten auszuwerten und zu bewerten, sondern war auch gleichzeitig der Scheduler für die Aktionen (Reihenfolge usw.)

Du injectest den Caller schon automatisch, wenn du this mitlieferst. Selbst solche automatischen Injektionen können "getürkt" werden.
Hier geht es eher darum, dass ein Entwickler (=Nutzer des Frameworks) einen falschen Parameter einträgt (anstatt this einen Agenten in der Sichtweite des "this"-Agenten(sorry, für diese Beschreibung)).
Ursprünglich war angedacht, dass es diese Möglichkeit gibt, um die Simulationsdesigner nicht in ihrer Kreativität einzuschränken.
Nun möchte ein Nutzer jedoch, dass es diese Möglichkeit nicht mehr gibt. Problem für ihn war: Ein versehentlich falsch eingetragener Wert führt zu falschen Simulationsergebnissen (hierbei wurden Aktionen von falschen Agenten ausgeführt und andere Agenten haben Bewegungen ausgeführt, die für sie eigentlich nicht erlaubt waren) - ein solches Problem stellt aber (aktuell) keinen Fehlerfall dar. Die Logik wird normal ausgeführt, weshalb so ein Fehler eher zufällig auffällt und auch entsprechend schwer zu finden ist. Nach Meinung des Nutzers reicht ein Kommentar in der Dokumentation der API nicht aus - er hätte das gerne technisch abgesichert.
 
Ja, sie erben letztlich von der AbstractAgent-Klasse. Allerdings behebt ein hineinziehen in diese Klasse nicht das Grundproblem: Die Schnittstelle des ActionHandlers ist public. Somit kann trotzdem an der API "vorbeigeschossen" werden (der Aufruf kann immer noch manuell erfolgen.
Mit Reflection kann sowieso jede Methode aufgerufen werden, aber zumindest die Wahrscheinlichkeit von zufälligen Fehlern ist so seltener.

Zum zweiten Punkt: Oh, entschuldigung, das hatte ich nicht richtig beschrieben. Es gibt pro Simulation EINEN ActionHandler - nicht pro Agent. Jeder ActionHandler ist dabei eine Implementierung des IActionHandler-Interfaces und beschreibt unterschiedliche Arten, wie die Simulation auf Aktionen reagiert. Grundgedanke war damals (so die Architekturdokumentation), dass man auch asychrone Simulationen verwalten kann. Jeder Agent kann dabei seine Berechnungen durchführen, während die übrigen Agenten dies auch tun. So kann es dazu kommen, dass ein Agent zu lange braucht und seine getroffene Entscheidung nicht mehr möglich ist (z.B. weil eine Koordinate bereits durch einen anderen Agenten blockiert ist).
Der ActionHandler war in diesen Fälle nicht nur dazu da, die Aktionen der Agenten auszuwerten und zu bewerten, sondern war auch gleichzeitig der Scheduler für die Aktionen (Reihenfolge usw.)

Hab ich auch so verstanden, meine Frage zielte auf was anderes ab 😉

Noch mal zu dem Schachbeispiel: Turm A ruft den ActionHandler mit seiner neuen Position (zu der er nicht gehen darf) auf, und übergibt Bauer B, damit er sich trotzdem dahin bewegen darf. Was macht denn dann der Handler damit?
Gibt der Handler einfach nur true zurück, und der Turm macht den Rest (dann könnte der Turm das ja ignorieren)?
Oder setzt der Handler selbst dann den Turm auf die neue Position (kann der Handler ja nicht, er denkt ja, der Bauer bewegt sich)?

Hier geht es eher darum, dass ein Entwickler (=Nutzer des Frameworks) einen falschen Parameter einträgt (anstatt this einen Agenten in der Sichtweite des "this"-Agenten(sorry, für diese Beschreibung)).
Ursprünglich war angedacht, dass es diese Möglichkeit gibt, um die Simulationsdesigner nicht in ihrer Kreativität einzuschränken.
Nun möchte ein Nutzer jedoch, dass es diese Möglichkeit nicht mehr gibt. Problem für ihn war: Ein versehentlich falsch eingetragener Wert führt zu falschen Simulationsergebnissen (hierbei wurden Aktionen von falschen Agenten ausgeführt und andere Agenten haben Bewegungen ausgeführt, die für sie eigentlich nicht erlaubt waren) - ein solches Problem stellt aber (aktuell) keinen Fehlerfall dar. Die Logik wird normal ausgeführt, weshalb so ein Fehler eher zufällig auffällt und auch entsprechend schwer zu finden ist. Nach Meinung des Nutzers reicht ein Kommentar in der Dokumentation der API nicht aus - er hätte das gerne technisch abgesichert.
Siehe meinen ersten Punkt, da hätte man solche Fehler nicht mit drin (außer der Nutzer ignoriert den richtigen Weg, könnte man verhindern, wenn der Handler für die nicht abstrakten Agenten nicht sichtbar ist (zur Not bräuchte man ein zweites Interface)). Gegen gewolltes falsch benutzen (uU mit Reflection) kann man aber nicht wirklich was tun.
 
Es geht hier tatsächlich eher um zufällige Fehler. Via Reflection könnte ein Entwickler sowieso so gut wie alles aushebeln. Relativ einfach kann so ein Fehler entstehen, wenn zuvor vielleicht an dieser Stelle eine Methode aufgerufen wurde, die einen anderen Agenten zum Ziel hatte. Etwas falsch gemacht beim Refactoring und schon ist der Fehler da.
Wenn jemand z.B. via Reflection an vorgegebenen Constraints vorbeischießt, dann ist das in meinen Augen kein Fehler unseres Frameworks mehr, wenn's dann knallt.

Zum ActionHandler
Die eigentliche Bewegung (oder auch eine Aktion) wird durch den ActionHandler ausgeführt bzw. geplant. Die Agenten selbst dürfen - in diesem Framework - selbst keine Aktionen durchführen, sondern dürfen nur freundlichst anfragen, ob sie denn dürfen.
Der Response des ActionHandlers dient dazu, dass der Agent ggf. darauf reagieren kann, wenn der Handler sagt: "Nö, darfste nicht" oder "Nö, darfste nicht, WEIL...".

Das Problem hierbei ist, dass die vorliegenden ActionHandler alle so implementiert sind, dass sie die Constraints aus den übergebenen Parametern ableiten und danach handeln. Daher wird der jeweilige Agent auch mit übergeben.
Beispiel:
Agent A fragt eine Bewegung an, übergibt aber Agent B. Der ActionHandler würde nun die Constraints von B prüfen und die Bewegung auch für Agent B ausführen. Das führt zu zweierlei Problemen:
-> Agent A führt seinen geplanten Zug niemals aus;
-> Agent B gelangt ggf. in einen inkonsistenten Zustand. Er befindet sich beispielsweise gerade selbst noch in der Entscheidungsfindungsphase, kann jedoch keine gültige Entscheidung mehr treffen, weil er faktisch nicht mehr auf seiner Position ist (dies aber noch "glaubt"). B würde in dieser - wie gesagt älteren Architektur - auch nicht über diese Änderung informiert werden. Dies würde nur geschehen, wenn tatsächlich eine Aktion gegen ihn gerichtet worden wäre (dann wäre er sofort informiert worden und würde ggf. seine Entscheidungsfindung anpassen oder abbrechen).

Zu Deinem letzten Vorschlag:
Wir haben auch schon überlegt, die Referenz zum ActionHandler in der abstrakten Klasse private zu machen und die Aufrufe darüber laufen zu lassen. Aus irgendeinem Grunde haben wir das heute früh verworfen - allerdings weiß ich gerade peinlicherweise nicht mehr warum... oh man. Vielleicht ist es heute einfach zu spät.
 
Den Aufruf des Handers in Code verlagern, der dann vom Nutzer nicht noch mal angefasst wird, dürfte imho die einfachste Lösung sein.

Alle anderen dürften ziemlich große Änderungen am Code erfordern, die Kapselung des Aufrufs kann man wenn nötig auch ohne jegliche Änderung der API erreichen (zur Not hat der abstrakte Agent zwei Handler-Instanzen, die echte und eine, die der implementierte Agent zu sehen bekommt. In der sichtbaren wird dann einfach nur der passende Agent gesetzt und der sichtbare aufgerufen, und der übergebene Agent weggeschmissen)
 

Zurück
Oben