major
#29637
Modellbasierte Berechtigungsdefinition: Zugriffselternobjekt (Delegation der Zugriffsentscheidung an Container oder Referenz, Standard für Kompositionsteile, Entscheidungs-Cache je Interaktion), Rollenelternobjekt statt Security-Parent, Vollständigkeitsprüfung und Assistent
Kontext
Mit der modellbasierten Berechtigung (SecurityConfigurationService, ModelAccessRights) braucht jeder Fachtyp eine vollständige Berechtigungsdefinition, sonst sind seine Objekte für normale Nutzer nicht zugreifbar:
- eine Rollenregel im AccessManager (<role-rules>), die auf Objekten des Typs Rollen liefert, oder
- eine Rollenelternregel (<security-parents>; bisher „Security-Parent“ genannt), die festlegt, an welchem anderen Objekt die Rollen geprüft werden,
- und eine Freigabe (<grant>) im SecurityConfigurationService, die einer dieser Rollen die Operation (mindestens Read) auf dem Typ erlaubt.
Fehlt die Rollenquelle, fällt ein Objekt höchstens auf die globale Security-Root zurück (nur wenn use-default-security-parent aktiv ist, was standardmäßig nicht der Fall ist), d.h. es ist nur noch für Inhaber von Root-Rollen sichtbar. Fehlt die Freigabe, ist das Objekt für keinen Benutzer zugreifbar. Diese Definitionen sind mühsam anzulegen, verteilen sich auf mehrere Konfigurationsabschnitte, und es ist schwer zu prüfen, ob sie für alle Typen einer Anwendung vollständig sind. Ein vergessener Typ fällt erst auf, wenn ein Nutzer ihn nicht sieht.
Die Vollständigkeitsprüfung hat zudem eine Lücke im Konzept sichtbar gemacht: In der alten Welt, in der Zugriffsrechte an Sichten definiert waren, wurden viele Objekte nie nach einem Zugriffsrecht gefragt, weil nur der Code sie handhabte. Jetzt braucht jeder Zugriff eine Freigabe – auch für die vielen Hilfstypen (Zeilen von Kompositionstabellen, Kommentare, Anhänge, …), die nie als eigenständige Objekte angezeigt werden. Ein Rollenelternobjekt reicht für sie nicht aus: Es vererbt nur die Rollen, so dass jeder dieser Typen trotzdem eigene, redundante Freigaben braucht.
Verbesserung
Neues Konzept: Zugriffselternobjekt (access-parent). Ein Typ mit Zugriffselternobjekt hat keine eigene Berechtigungsdefinition: keine Freigaben, keine Kennzeichen, keine Rollen. Ob ein Benutzer ein Objekt des Typs lesen, schreiben oder exportieren darf, ist die Frage, ob er dasselbe mit dem Zugriffselternobjekt darf; Anlegen und Löschen eines Objekts ist Schreiben des Zugriffselternobjekts. Anlegen ohne Kontext (das Muster „transient anlegen, dann persistent kopieren“ des Sichtsystems) ist nicht beschränkt: Das Objekt ist unzugreifbar, bis es in einen Container gelegt wird, und das ist ein Schreiben des Containers, das für sich geprüft wird. Das Zugriffselternobjekt kann seinerseits delegieren; die Kette wird zur Laufzeit verfolgt. Ein Objekt, dessen Beziehung ins Leere führt, ist nicht zugreifbar; eine zyklische Kette wird abgelehnt (und als Fehler protokolliert). Attributfreigaben (<part>) eines delegierenden Typs werden gegen die Rollen am Ende der Kette geprüft. Spezialisierungen erben die Einstellung, sofern sie keine eigene haben.
- Definition am <class>-Eintrag des SecurityConfigurationService: Die Art der Beziehung wird explizit angegeben (access-parent), die Referenz getrennt davon (access-reference). Die Navigationsrichtung wird nicht mehr aus der Form der Referenz geraten, so dass auch eine einwertige Komposition, die ein Typ zugleich besitzt und in der er liegt, eindeutig ist:
- access-parent="container" ohne Referenz: der Container, welche Komposition das Objekt auch hält – der Standard für Kompositionsteile, explizit angegeben.
- access-parent="container" access-reference="demo.tickets:Ticket#comments": der Container, nur wenn er das Objekt über diese Komposition hält (rückwärts navigiert).
- access-parent="target" access-reference="demo.tickets:Reminder#ticket": das Ziel einer einwertigen Referenz des Typs (vorwärts navigiert); die Referenz ist Pflicht.
- access-parent="self": keine Delegation – der Typ entscheidet über eigene Freigaben und Rollen. Schaltet den Standard für Kompositionsteile ab und hebt ein von einer Generalisierung geerbtes Zugriffselternobjekt auf.
- access-parent="auto" (Standard, entfällt im XML): die Einstellung der Generalisierungen, sonst der Standard für Kompositionsteile.
Eine Referenz, die nicht zur Art passt (keine Komposition, die den Typ hält, für container; keine einwertige Referenz des Typs für target; überhaupt eine Referenz für self/auto), ist ein Konfigurationsfehler. Ein Eintrag, der ein delegierendes Zugriffselternobjekt (container, target) mit Freigaben oder Kennzeichen kombiniert, wird beim Start und vom Editor abgelehnt (Constraint AccessParentStandsAlone); target ohne Referenz meldet der Editor über AccessReferenceRequired. Die Auswahlliste des Editors (AccessReferenceOptions) bietet je nach Art die Kompositionen, die Objekte des Typs halten, bzw. die einwertigen Referenzen des Typs, jeweils mit dem besitzenden Typ beschriftet (TLPartInOwnerResourceProvider).
- Standard für Kompositionsteile: Ein Typ, dessen Objekte in einer Komposition liegen und für den weder eine (auch geerbte) Rollenregel noch eine Rollenelternregel gilt, der nicht gekennzeichnet ist und keine eigene oder geerbte access-parent-Einstellung hat, delegiert an seinen Container – welche Komposition ihn auch hält (TLObject#tContainer()). Die meisten Hilfstypen einer Anwendung brauchen damit keinerlei Konfiguration. Jede explizite Definition schaltet den Standard für den Typ ab. access-parent="container" ist nötig für Kompositionsteile, die eine Rollenregel erben, aber trotzdem delegieren sollen (die Prüfung meldet dann die überdeckten Regeln), target für Typen, die über eine einwertige Referenz erreicht werden, und self für Kompositionsteile, die selbst entscheiden – etwa mit eigenen Freigaben und direkt an ihren Objekten vergebenen Rollen, die für den Standard nicht als Rollenquelle zählen.
- Cache: Jede Entscheidung von isAllowed wird je Interaktion (Request) im AccessDecisionCache am InteractionContext gemerkt, Schlüssel: Person, Objekt, Operation. Eine Kompositionstabelle mit 200 Zeilen, die alle an einen Container delegieren, kostet eine echte Prüfung des Containers. Der Cache wird ersetzt, sobald die Knowledge-Base eine neuere Revision hat, damit ein Kommando, das committet und dann rendert, keine Entscheidungen von vor seiner Änderung wiederverwendet. Der Cache erkennt auch Zyklen: Eine Entscheidung wird während der Berechnung markiert; erreicht eine Kette von Zugriffselternobjekten die Markierung erneut, wird abgelehnt.
- Umbenennung: Das bisherige „übergeordnete Sicherheitsobjekt“ (Security-Parent) heißt in Oberfläche, Befunden und Dokumentation jetzt Rollenelternobjekt (role parent), weil es Rollen vererbt, nicht die Zugriffsentscheidung. Konfigurations-Tag (<security-parents>) und Java-API (BoundObject#getSecurityParents()) behalten ihren Namen.
Die Anwendung prüft die Vollständigkeit ihrer Berechtigungsdefinition selbst, und die Definition lässt sich aus der Administration heraus vervollständigen. Die Definitionen bleiben Konfiguration (Rollenregeln und Rollenelternregeln in der AccessManager-Konfiguration, Freigaben, Kennzeichen und Zugriffselternobjekte im SecurityConfigurationService).
Analyse. Für jeden konkreten Typ des Anwendungsmodells wird ermittelt:
- Keine Rollenquelle: weder eine (auch geerbte) Rollenregel noch eine Rollenelternregel gilt für den Typ, und keine Komposition hält seine Objekte. Der Befund nennt, ob der Rückfall auf die Security-Root aktiv ist. (Eine direkte Rollenzuweisung an Objekten ist Daten, keine Definition, und zählt hier nicht.)
- Keine Lesefreigabe: für den Typ (bzw. sein Modul, bzw. geerbt) ist keine Read-Freigabe konfiguriert.
- Wirkungslose Freigabe: eine freigegebene Rolle kann weder auf dem Typ noch auf einem Typ, zu dem seine Rollenelternpfade führen, jemals gehalten werden – weder durch eine Rollenregel noch durch eine direkte Rollenzuweisung an einem Objekt dieses Typs (statisch über die Zieltypen der Pfade berechnet; ein statisch nicht auflösbarer Pfad erzeugt keinen Befund).
- Überdeckte Regeln: der Typ hat ein konfiguriertes Zugriffselternobjekt, aber es gelten Rollenregeln oder Rollenelternregeln für ihn; sie sind wirkungslos.
Ein Typ mit Zugriffselternobjekt (konfiguriert oder Standard) hat den Status „Delegiert“ und keine weiteren Befunde; ob das Elternobjekt zugreifbar ist, meldet dessen Typ. Die Befunde zu fehlender Rollenquelle und fehlender Lesefreigabe weisen darauf hin, dass ein nur vom Code der Anwendung verwendeter Typ als internal zu kennzeichnen ist.
Ausgenommene Typen. Ein Typ, der als without-security konfiguriert ist, und ein Typ, der als internal konfiguriert ist (Attribut an <class> und <module> im SecurityConfigurationService; vererbt sich auf Spezialisierungen), erhält keine Befunde und den Status „Ausgenommen“. internal kennzeichnet Typen, deren Objekte nur vom Code der Anwendung verwendet werden – transient oder in einem Kontext ohne Zugriffsprüfung –, so dass kein Benutzer eine Rolle auf ihnen benötigt; die Zugriffsprüfung selbst ändert sich nicht, und ein interner Typ delegiert nicht. So gekennzeichnet sind die Module tl.login und tl.admin (tl-layout-view), tl.changelog und tl.imagegallery (tl-element) sowie tl.devtools (tl-dev-tools).
Meldung beim Start. Der Dienst SecurityCoverageCheck (in tl-element konfiguriert, Erweiterung des AccessManager, startet also mit ihm) führt die Analyse beim Start aus und protokolliert je Befund eine Zeile sowie eine Zusammenfassung (n types analysed, m with findings) – auf der Stufe INFO, da die Test-Infrastruktur für Anwendungen Warnungen beim Start als Fehler wertet; die aktiven Hinweise sind der Reiter „Abdeckung“ und der wiederverwendbare Test. Konfiguration: excluded-modules (Module, die bewusst ohne Definition bleiben) und log-findings (Standard true).
Administrationssicht. Im React-Administrationsbereich hat der Abschnitt „Zugriffskontrolle“ den Reiter „Abdeckung“: eine Tabelle aller analysierten Typen, nach Modul gruppiert (die Modulspalte selbst ist zunächst ausgeblendet), mit den Spalten Typ (Objektreferenz mit Icon), Status (Icon: Haken = abgedeckt, Pfeil = delegiert, Warndreieck = unvollständig, Strich = ausgenommen), leseberechtigte Rollen (Objektreferenzen), Rollenregeln, Rollenelternobjekte (Pfad, ^ markiert einen rückwärts navigierten Schritt) und Zugriffselternobjekt („Container (Standard)“, „Container“ für den konfigurierten Container oder die navigierte Referenz); die Spalte Befunde ist über die Spaltenauswahl einblendbar; sortier- und filterbar. Rechts die Detailansicht „Befunde“: für einen delegierenden Typ ein Absatz, wohin er delegiert, sonst die Befunde als Liste von Problemen mit ihren Lösungen; darunter „Geltende Regeln“: die Rollenregeln und Rollenelternregeln, die für den Typ gelten, mit Art, Id, Beschreibung und der Angabe, ob die Anwendungskonfiguration sie definiert.
Bearbeiten der Definition. Die Kommandos der Detailansicht schreiben in die Konfigurationsdateien der Anwendung (WEB-INF/autoconf/com.top_logic.tool.boundsec.manager.AccessManager.config.xml und …SecurityConfigurationService.config.xml, geschichtet über die Basiskonfiguration, nie überschreibend): „Rollenelternobjekt…“ und „Rollenregel…“ (Dialog mit Konfigurationsformular über eine NavigationRuleConfig bzw. RoleRuleConfig, Typ vorbelegt), „Zugriffsrechte…“ (Konfigurationsformular über den <class>-Eintrag der Anwendungsdatei: Freigaben/Entzüge, die Kennzeichen, die Art des Zugriffselternobjekts und die Zugriffsreferenz mit Auswahlliste der zur Art passenden Referenzen) und „Zugriffsrechte des Moduls…“ (<module>-Eintrag), „Als intern kennzeichnen“ und „Ohne Zugriffskontrolle“ (bestätigte Umschalter; Setzen eines Kennzeichens verwirft das andere und ein delegierendes Zugriffselternobjekt), „Regel bearbeiten…“ und „Regel löschen“ in der Regelliste (eine Regel der Basiskonfiguration kann bearbeitet, d.h. per Id überlagert, aber nicht gelöscht werden). Gespeicherte Änderungen wirken nicht sofort; ein Hinweis zeigt sie an, bis „Konfiguration anwenden“ die Konfiguration neu liest und SecurityConfigurationService und AccessManager (samt abhängiger Dienste) neu startet, wonach die Analyse den neuen Stand zeigt.
Konfigurationseditor. Der Konfigurationseditor des React-Sichtsystems (<config-form>, tl-layout-configedit) bietet jetzt auch für Eigenschaften mit einer @Options-Annotation mit nicht-identischer Abbildung (Typnamen, Rollennamen, TLModelPartRef, CommandGroupReference mit @Options an der Wertklasse) eine Auswahlliste an: das Auswahlmodell übersetzt zwischen gespeichertem Wert und Option in beide Richtungen; mehrwertige und abgebildete Auswahlen nutzen das Dropdown-Steuerelement mit Filter. Außerdem:
- Der Editor folgt dem dynamischen Modus einer Eigenschaft (@DynamicMode), wie es das klassische Formular tut: eine unsichtbare Eigenschaft wird ausgeblendet (das Feld samt Beschriftung bzw. die Gruppe einer Liste oder eines Unterelements), eine deaktivierte oder unveränderliche nimmt keine Eingaben an. So erscheint die Zugriffsreferenz nur für container und target, und die Freigaben verschwinden bei without-security.
- Optionen, die aus anderen Eigenschaften berechnet werden, lädt das Dropdown neu, sobald sich diese ändern – auch wenn die Liste zuvor schon geöffnet war.
- Ein Formular ohne eigenen Bearbeitungsmodus (withEditMode="false", so die Dialoge der Abdeckung) prüft seine Felder bei jeder Änderung und zeigt Befunde am Feld, statt erst beim Speichern abzulehnen. Ein unberührtes Formular zeigt keine Befunde.
Test für Anwendungen. Der wiederverwendbare Test test.com.top_logic.element.boundsec.manager.coverage.TestSecurityCoverage schlägt fehl, sobald ein Typ einen Befund hat, und listet alle Befunde. Eine Anwendung schaltet ihn in ihrer *.test.config.xml ein: {{{#!xml <config config:interface="test.com.top_logic.element.util.ElementTestCollector$GlobalConfig" test-security-coverage="true"/> }}}
Lösung
- tl-core: TLClassAccessRights#getAccessParent() (access-parent, Enum AccessParentKind: auto, container, target, self; Constraint AccessParentStandsAlone) und TLClassAccessRights#getAccessReference() (access-reference, TLModelPartRef, Auswahl je nach Art über AccessReferenceOptions, Beschriftung TLPartInOwnerResourceProvider, nur für container/target sichtbar über AccessParentKind.ReferenceMode, Constraint AccessReferenceRequired); Wert AccessParent (Container – als Standard container() oder konfiguriert anyContainer() –, Komposition rückwärts, Referenz vorwärts; resolve(TLObject)); ModelAccessRights#getAccessParent(TLClass); SecurityConfigurationService löst die Einstellungen beim Start auf und prüft die Referenz gegen die Art (inkl. Vererbung an Spezialisierungen, die self beendet, Index der Kompositionsziele), entscheidet in isAllowed/isAllowedCreate/getAccessibleTypes per Delegation und merkt sich Entscheidungen im AccessDecisionCache (je Interaktion, revisionsgebunden, Zykluserkennung); AccessManager#hasRoleSource(TLClass) als Hook für den Standard (Basisimplementierung true, d.h. kein impliziter Standard ohne Regelwerk). TypeBasedAccessRights#isInternal() (internal) neben without-security, beide mit Settern; ModelAccessRights#isInternal(TLClass). com.top_logic.util.autoconf.InAppServiceConfigStore – Lesen/Schreiben einer Dienstkonfiguration im Autoconf-Ordner (Datei je Dienst, Override-Markierung optional), ausgelagert aus TLSaveServiceConfigHandler, der es nun verwendet; OverrideConfigurationWriter liegt im selben Paket.
- tl-element: ElementAccessManager#hasRoleSource (Rollenregel oder Rollenelternregel gilt); Paket com.top_logic.element.boundsec.manager.coverage: SecurityCoverageAnalysis (Berechnung über Modell, aufgelöste Rollenregeln ElementAccessManager#getRules(TLClass), Rollenelternregeln über ElementAccessManager#getRoleParentRules(TLClass), Freigaben ModelAccessRights#getAllowedRoles, Zugriffselternobjekt ModelAccessRights#getAccessParent und die direkten Rollenzuweisungen der Datenbank je Typ), Ergebnis TypeCoverage mit CoverageStatus (COVERED, DELEGATED, EXEMPT, INCOMPLETE) und CoverageFindings (FindingKind: NO_ROLE_SOURCE, NO_READ_GRANT, DEAD_GRANT, SHADOWED_RULES); SecurityCoverageCheck (Startdienst, @ServiceExtensionPoint(AccessManager.Module)); CoverageStatusResourceProvider (Icons); SecurityDefinitionEditor (Bearbeitung der Autoconf-Einträge: Regeln per Id, Freigabeeinträge per Name, Kennzeichen, setAccessParent(TLClass, AccessParentKind, TLModelPartRef); apply() = Konfiguration neu laden und Dienste neu starten). NavigationRule kennt seine Id, RoleProvider seine Konfigurations-Id und seinen Pfad; PathNavigation legt Referenz und Richtung offen; NavigationRuleConfig/PathElementConfig erhalten Setter.
- tl-layout-configedit: ConfigSelectFieldModel mit OptionMapping; ConfigControlService#isSelect akzeptiert abgebildete Optionen und @Options an der Wertklasse (ConfigPropertyOptions#optionsAnnotation); abgebildete und mehrwertige Auswahlen über ReactDropdownSelectControl. ConfigEditorControl folgt @DynamicMode über ConfigPropertyOptions#modeProvider (Feld über ReactFormFieldChromeControl#setVisible, Gruppe über ReactControl#setHidden, Editierbarkeit des Feldmodells); ConfigFormControl prüft auch mit Commands.NONE bei jeder Feldänderung.
- tl-layout-react: ReactDropdownSelectControl verwirft die geladene Optionsliste, wenn das Modell neue Optionen meldet (solange es angezeigt wird); TLFormGroup wertet hidden aus; ReactFormFieldChromeControl#isVisible().
- tl-layout-view: admin/access-control/coverage.view.xml mit SecurityCoverageTable (typbasierte Objektspalten über den ColumnProviderService, Gruppierung nach Modul, Spalte Zugriffselternobjekt, Kanäle für Auswahl, Typ, Befunde und geltende Regeln, die nach einem Neuaufbau der Zeilen erneut geschrieben werden), SecurityCoverageAction (Analyse) und SecurityDefinitionAction (Bearbeiten: Regeln anlegen/bearbeiten/speichern/löschen, Zugriffsrechte, Kennzeichen, Anwenden); Dialoge coverage-role-parent-rule, coverage-role-rule, coverage-access-rights; Reiter in admin.view.xml. FieldControlService#createDisplayControl(ColumnType) zeigt Werte eines Objekttyps ohne deklarierendes Attribut wie Referenzattribute; AttributeOptions#isStructuralSelect(TLType).
- Tests: TestAccessParentCheck (Delegation an Container, Kette, konfigurierte Elternobjekte rückwärts/vorwärts, Anlegen/Löschen als Schreiben, Attributprüfung, Zyklus, zugreifbare Typen), TestSecurityCoverageAnalysis (u.a. konfigurierter Container, eindeutige Richtung bei einer rekursiven Komposition, self gegen den Standard und gegen ein geerbtes Zugriffselternobjekt), TestSecurityCoverageCheck, TestSecurityCoverageDirectRoleAssignment, TestSecurityDefinitionEditor (tl-element, gegen das Testmodell TestSecurityCoverage); TestConfigControlService, TestConfigSelectFieldModel, TestConfigEditorControl (dynamischer Modus), TestConfigFormControl (Prüfung ohne Bearbeitungsmodus) (tl-layout-configedit); TestSelectValueObservation (nachgeladene Optionen, tl-layout-react).
- tl-demo-react: Rolle demo.react.ProjectReader (Gruppe users) auf ProjectScope und demo.tickets:Ticket, Read/Write-Freigaben für die Module tl.demo.projectManagement und demo.tickets; Milestone, Ticket, Note, Comment und Attachment delegieren als Kompositionsteile ohne eigene Konfiguration an ihren Container.
- FAQ docs/faq/access-configuration.md: Zugriffselternobjekt (Syntax mit access-parent und access-reference), Standard für Kompositionsteile, Cache, Rollenelternobjekt.
Hinweis: Jede Anwendung protokolliert nach dem Update beim Start je Typ mit unvollständiger Berechtigungsdefinition eine INFO-Zeile. Typen ohne Berechtigungsbedarf werden mit internal="true" gekennzeichnet, ganze Module alternativ über excluded-modules des SecurityCoverageCheck ausgenommen. Verhaltensänderung: Objekte eines Kompositionsteil-Typs ohne Rollenquelle waren bisher für jeden normalen Benutzer unzugreifbar; sie folgen jetzt der Zugriffsentscheidung ihres Containers. Ein Typ, der das nicht soll, erhält access-parent="self" (mit eigenen Freigaben), eine Rollenregel, eine Rollenelternregel oder das Kennzeichen internal.