minor
#29700
TL Views: the open session of a deleted account keeps evaluating as that account; every model event logs "DeletedObjectAccess … was already deleted" from its observers
Problem
An administrator deletes an account (React admin hub, Zugriffskontrolle → Konten → Löschen) while that account is still logged in in another browser. The deleted account's session stays alive, and its React window keeps its view observers registered. From then on every model event (any commit of any user) is delivered to those observers, which evaluate their scripts with the deleted Person as the session user. The security check of the evaluation reads the deleted person:
ERROR EventBuilder - Failed to deliver change event to listener: com.top_logic.layout.view.model.RowSourceObserver@7e09d19b … Caused by: com.top_logic.knowledge.service.db2.DeletedObjectAccess: This object, the current version of ImmutableKnowledgeObject[wrapper: Account(type:tl.accounts:Person, id: Person:9030@51, name: probe2)], was already deleted in revision 52. at DBKnowledgeItem.checkAlive(DBKnowledgeItem.java:956) … at Person.isRestrictedUser(Person.java:537) at SimpleBoundCommandGroup.isAllowedCommandGroup(SimpleBoundCommandGroup.java:257) at BoundChecker.isAllowedBypass(BoundChecker.java:154) at SecurityConfigurationService.isAllowedBypass(SecurityConfigurationService.java:593) at SecurityConfigurationService.isAllowed(SecurityConfigurationService.java:552) at ModelAccessRights.isReadAllowed(ModelAccessRights.java:132) at AccessLike.lookupValue(AccessLike.java:81) …
Seen in the TL Development app (snapshot build 43): one deletion of a logged-in account, then two commits by root, gave 11 such ERRORs (RowSourceObserver, ChannelObjectObserver of a <progress>, …), one per observer of the deleted account's window and commit. The window of the deleted account keeps showing and is still operable until its session times out.
Expected
Deleting an account ends its sessions (like a logout: its windows are unregistered, its observers removed, its next request lands on the login page). No script is evaluated with a deleted person as the session user.
Lösung
- Ursache: Kein Löschpfad (React-Admin $obj.delete(), klassisches GenericDeleteCommandHandler, TL-Script, Import) beendet die Sitzungen des gelöschten Kontos. SessionService kannte die HttpSession nicht; invalidateSession(String) entfernte nur den Eintrag aus der Sitzungstabelle. Die HTTP-Sitzung – und mit ihr das React-Fenster samt Beobachtern – lebte bis zum Timeout weiter, der SSE-Heartbeat lieferte jede Änderung an die Beobachter aus.
- SessionService merkt sich zu jeder Sitzung die HttpSession. Neu sind terminateSession(String) und terminateSessions(Person), die eine Sitzung tatsächlich beenden (HttpSession.invalidate()). Das löst den vorhandenen Abbau aus: LOGGED_OUT-Ereignis (genau einmal), ReactWindowRegistry.valueUnbound (Reload des Fensters, cleanupTree, Beobachter abgemeldet).
- SessionService registriert einen UpdateListener an der Wissensbasis. Ein Commit, der Person-Objekte löscht (lokal oder aus dem Cluster), beendet deren Sitzungen – unabhängig vom Löschpfad. Das Beenden läuft als Hintergrundaufgabe im SchedulerService, weil das Protokollieren des Logouts (StoreUserEventListener) selbst committet und das während der Benachrichtigung eines UpdateListener nicht erlaubt ist.
- Die Ereignissynthese eines React-Fensters (ReactWindowRegistry.synthesizeModelEvents, alle Pfade: SSE-Heartbeat, Requests, Replay, Agent) findet nur statt, solange die Sitzung beim SessionService registriert und ihr Benutzer noch am Leben ist. Das schließt die Lücke zwischen Lösch-Commit und Beenden der Sitzung. Anonyme Sitzungen sind mit dem anonymen Konto registriert und bekommen weiterhin Ereignisse.
- Der Logout des Wartungsmodus (MaintenanceWindowManager.logoutUsers) beendet die Sitzungen ebenfalls tatsächlich, statt sie nur aus der Tabelle zu entfernen.
- Das Sitzungsende eines gelöschten Kontos greift nicht mehr auf das gelöschte Konto zu: StoreUserEventListener findet den offenen UserSession-Eintrag über die Sitzungs-ID, SessionService nimmt den Namen für die Log-Meldung aus der Sitzungsinfo, und TLContext.storePersonalConfiguration speichert für ein gelöschtes Konto keine persönliche Konfiguration (vorher scheiterte daran HttpSession.invalidate() mit "Unable to invalidate Session").
- Tests: TestSessionService (Beenden per ID, per Person, Löschen einer angemeldeten Person inkl. Negativfall), TestWindowModelEventsOfEndedSession (keine Ereignisse nach Entfernen der Sitzung bzw. Löschen des Benutzers).