Zum Inhalt springen

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.


public interface CdmsFilterInterface<T> {
ListSearchFilter get();
}

Der Rückgabewert:

WertBedeutung
ein ListSearchFilterwird als Pflichtfilter an die Abfrage gehängt
nullkein Filter – der Aufrufer sieht alles
Exceptiondie Anfrage scheitert

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:

@Service
public 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:

@Service
public class CustomerProjectFilter extends AbstractAttributeFilter<CustomerDto> {
@Override protected String getProfileAttribute() { return "projects"; }
@Override protected String getDataAttribute() { return "projectId"; }
}

Siehe Attributbasierte Filter.

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)
  • assertVisibleForWriteauch Update, Patch, Delete und Rollback

Damit gilt: Was nicht lesbar ist, ist auch nicht schreibbar.

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.

  • 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 als LEFT JOIN angebunden.
  • Der Filter sollte fail-closed sein: fehlende Grundlage → Fehler, nicht „alles sichtbar”.
  • Ein null als Rückgabe ist ausdrücklich „keine Einschränkung” – dieser Fall gehört bewusst entschieden, nicht als Nebenwirkung.