Eigene Datenfilter
Kurzantwort: Wer die Sichtbarkeit von Zeilen über den Owner-Filter hinaus
einschränken will, implementiert CdmsFilterInterface<T> als Spring-Bean für
das DTO des Modells. Der System-Layer sammelt alle passenden Beans ein und hängt
ihre Filter mit AND an jede Abfrage des Modells – auch an die
Sichtbarkeitsprüfung vor jedem Schreibzugriff.
Das Interface
Abschnitt betitelt „Das Interface“public interface CdmsFilterInterface<T> { ListSearchFilter get();}Der Rückgabewert:
| Wert | Bedeutung |
|---|---|
ein ListSearchFilter | wird als Pflichtfilter an die Abfrage gehängt |
null | kein Filter – der Aufrufer sieht alles |
| Exception | die Anfrage scheitert |
Registrierung
Abschnitt betitelt „Registrierung“Der System-Layer sucht Beans über den generischen Typ:
this.appContext.getBeanNamesForType( ResolvableType.forClassWithGenerics(CdmsFilterInterface.class, <ModelDto>.class));Es genügt also, eine Bean mit dem passenden Typparameter bereitzustellen:
@Servicepublic class CustomerRegionFilter implements CdmsFilterInterface<CustomerDto> {
@Override public ListSearchFilter get() { String[] regions = RequestContextHolder.get() .getEffectiveUserAttributes().get("regions"); if (regions == null || regions.length == 0) throw new AttributeValidationException("missing-attribute-on-profile|regions", null); if (Arrays.asList(regions).contains("*")) return null; // keine Einschränkung return new ListSearchFilter("region", String.join(",", regions), SearchLogicOperations.IN); }}Für den häufigsten Fall – ein Profilattribut gegen ein Datenfeld – gibt es
AbstractAttributeFilter<T> bereits fertig; dann sind nur zwei Methoden zu
überschreiben:
@Servicepublic class CustomerProjectFilter extends AbstractAttributeFilter<CustomerDto> { @Override protected String getProfileAttribute() { return "projects"; } @Override protected String getDataAttribute() { return "projectId"; }}Siehe Attributbasierte Filter.
Wo die Filter greifen
Abschnitt betitelt „Wo die Filter greifen“flowchart TB
Q["Abfrage / Read / Sichtbarkeitsprüfung"] --> B["buildSearchRoot"]
B --> C["Client-Query"]
B --> D["Owner-Filter (_userId)"]
B --> E["alle CdmsFilterInterface-Beans des Modells"]
C --> R["AND-Wurzel"]
D --> R
E --> R
R --> P[Persistenz]
Betroffen sind:
queryObjects(Listen)recursiveRead(Einzelobjekt per ID)count(Trefferzahl)assertVisibleForWrite– auch Update, Patch, Delete und Rollback
Damit gilt: Was nicht lesbar ist, ist auch nicht schreibbar.
Pflichtfilter
Abschnitt betitelt „Pflichtfilter“Alle vom System-Layer ergänzten Filter werden als mandatory markiert. Lässt
sich ein solcher Filter nicht in ein Datenbank-Prädikat übersetzen, scheitert
die Abfrage, statt den Filter wegzulassen. Ein weggelassener Sicherheitsfilter
würde alle Zeilen liefern statt der erlaubten Teilmenge.
Hinweise
Abschnitt betitelt „Hinweise“- Der Filter läuft pro Abfrage, nicht pro Zeile – er muss sich als Prädikat ausdrücken lassen.
- Feldpfade über Beziehungen sind erlaubt (
company.id); jede Ebene wird alsLEFT JOINangebunden. - Der Filter sollte fail-closed sein: fehlende Grundlage → Fehler, nicht „alles sichtbar”.
- Ein
nullals Rückgabe ist ausdrücklich „keine Einschränkung” – dieser Fall gehört bewusst entschieden, nicht als Nebenwirkung.