Ausführungssemantik, Einschränkungen und Sicherheit

Diese Seite fasst zusammen, wie sich ein Script zur Laufzeit verhält (Reihenfolge, Transaktionen, Fehler, Sperren), welchen statischen Einschränkungen es bei der Speicherung unterliegt und sein Sicherheitsmodell. Zur Syntax siehe den Statement-Katalog und die Wertausdrücke; für praktische Kombinationen siehe das Cookbook.

Ausführungsreihenfolge

  • statements werden von oben nach unten sequenziell ausgeführt. Sobald die Ausführung ein Return erreicht, endet sie an diesem Punkt.
  • Die Ausführung erfolgt inline auf dem Pfad, der die Aufrufanfrage verarbeitet. Die Antwort des Aufrufs ist unmittelbar das Ausführungsergebnis (zur Form der Antwort siehe Anfrage und Antwort in der Script-Übersicht), und die Zeit für eine einzelne Ausführung wird unten unter Zeitbudget behandelt.

Ausführungssemantik

Guard (Vorbedingungen)

Es gibt kein dediziertes Guard-Statement. Man drückt es mit If und then:[Return] aus. Bei einer Verletzung der Bedingung wird ein Ergebnis zurückgegeben und die nachfolgenden Statements werden nicht ausgeführt (ein Script ohne Guard ist selbstverständlich ebenfalls möglich).

{ "type": "If", "condition": { "<": [ "{ /wallet/fields/balance/en-US }", "{ /payload/fields/cost }" ] },
  "then": [ { "type": "Return", "value": { "ok": false, "reason": "insufficient credit" }, "statusCode": 402 } ] }

Keine Transaktionen und Best-Effort-Kompensation

Ein Script ist keine Transaktion. Bei einem Fehler versucht die Engine, die bisher ausgeführten Vorgänge zu kompensieren (Kompensation), und gibt die Fehlerursache zurück, jedoch mit den folgenden Einschränkungen (als Designkompromiss in Kauf genommen).

  • Das Rückgängigmachen einer Löschung erzeugt eine neue sys.id, wodurch Referenzen, die darauf zeigten, brechen.
  • Externe Effekte (Http) sind irreversibel (ein bereits abgesetzter Aufruf und dessen Kosten lassen sich nicht rückgängig machen).
  • Wenn die Kompensation gar nicht ausgeführt wird, kann ein nicht kompensierter Zustand zurückbleiben.

Wenn Sie echte Atomarität benötigen, kompensieren Sie im Script selbst oder platzieren Sie nicht rückgängig zu machende Vorgänge (etwa externe Aufrufe) ganz am Ende. Am gefährlichsten ist die Reihenfolge, die "sich durchkettet, aber nicht zurückrollen lässt und dennoch sicher aussieht".

Optimistisches Sperren

Update-/Patch-Konkurrenz grenzen Sie über das version-Feld von ResourceUpdate und ResourcePatch ein. Geben Sie version (ein Wertausdruck, Int) an, wird nur dann aktualisiert, wenn es mit der aktuellen sys.version des Ziels übereinstimmt; bei Nichtübereinstimmung wird mit einem Versionskonfliktfehler abgebrochen (lokal mit Try/catch behandelbar). Lassen Sie es weg, gilt last-write-wins ohne Prüfung. Üblicherweise liest man zuerst mit ResourceRead oder ResourceFind und übergibt diese sys.version (siehe Optimistisches Sperren mit CAS im Cookbook).

Schreibvorgänge gehen an origin

Schreibvorgänge landen immer in origin (draft), und die Sichtbarkeit auf der Auslieferung (CDA/ACDA) wird über publish gesteuert (das publish von ResourceCreate/ResourceUpdate/ResourcePatch oder ResourcePublish/ResourceUnpublish).

Was als Fehler zählt

  • Ein echter Fehler ist ein Statement-Laufzeitfehler: ein finaler Http-Status von 400 oder höher (4xx·5xx; kein Fehler bei ignoreStatusCode: true) oder ein Timeout, ein Antwort-Body, der 10MiB überschreitet, oder ein fehlgeschlagener Ressourcenvorgang (kein Ziel vorhanden, ein Versionskonflikt, ein nicht unterstützter Vorgang usw.). Bei solchen Fehlern bricht die Engine ab und kompensiert, und Sie können sie lokal mit Try/catch/finally behandeln.
  • Ein Return ist kein Fehler, sondern ein normaler vorzeitiger Ausstieg. Es ist kein catch-Ziel (es gibt kein Konzept des benutzerseitigen throw).
  • Innerhalb eines catch referenzieren Sie { message } über /error. Welches Statement fehlgeschlagen ist, steht nicht darin.

Serverseitige Aggregation nur für die Anzahl

Die Anzahl zählt ResourceCount auf dem Server. Da dabei keine Elemente gelesen werden, gilt die Obergrenze für die Anzahl verarbeiteter Elemente hier nicht.

Für sum und group-by gibt es keine dedizierten Servervorgänge. Solche Aggregationen müssen Sie selbst berechnen, indem Sie mit ResourceForEach iterieren und mit SetVar und JsonLogic rechnen, weshalb Sie an die Obergrenze für die Anzahl verarbeiteter Elemente gebunden sind (für das Aggregieren von Millionen von Datensätzen ungeeignet). Wenn Sie nur die Anzahl brauchen, iterieren Sie nicht, sondern verwenden ResourceCount.

Kein Warten oder Verzögern

Ein Script hat kein Delay-Statement. Ein Script wird einmal ausgeführt und ist dann fertig und wartet oder pollt intern nicht, bis ein externer Job fertig ist.

Statische Einschränkungen (bei der Speicherung validiert)

Das Folgende wird geprüft, wenn ein Script gespeichert wird (beim Erstellen/Ändern). Bei einer Verletzung wird die Speicherung abgelehnt (der Fehler tritt zum Autorenzeitpunkt auf, nicht zur Laufzeit). Welche Verletzung mit welchem Code abgelehnt wird, behandelt der Abschnitt Fehler.

EinschränkungWert
Maximale externe Aufrufe (Http, EmailSend) pro DefinitionTarifabhängig (siehe Tarife)
Maximale Gesamtzahl verarbeiteter Elemente bei ResourceForEach (ohne deklariertes limit wird bis zu diesem Wert iteriert; verbleiben dabei Treffer, schlägt es fehl)10.000
Maximale SetVar pro Definition (inklusive verschachtelter)10
Maximale Cache pro Definition (inklusive verschachtelter, unabhängig von der Operation zusammengezählt)5. Bei Überschreitung Speichern abgelehnt
Cache im Block von Loop oder ResourceForEachSpeichern abgelehnt
Cache.keyNur Literal, maximal 128 Zeichen. Bei einem Wertausdruck Speichern abgelehnt
Cache.ttlZwischen 1 und 30 Sekunden, ohne Angabe 5 Sekunden. Außerhalb dieses Bereichs Speichern abgelehnt
Maximale Gesamtzahl an Statements pro Definition (inklusive verschachtelter)Tarifabhängig (siehe Tarife)
Obergrenze für Http.retry2
Länge von Regex.pattern128 Zeichen
Ein Statement, das einen ServiceUser ändertSpeichern abgelehnt. Nur die drei Lese-Statements nehmen diese Ressource an
createdBy: ":self" in where, wenn anonymousCallEnabled true istSpeichern abgelehnt

Die festen Limits in der Tabelle oben sind von der Plattform vorgegebene Werte und daher unabhängig vom Tarif gleich. Dagegen sind die Gesamtzahl der Statements und die Anzahl externer Aufrufe pro Definition tarifabhängige Limits. Diese beiden sind keine Validierungsfehler, sondern Tariflimits: Bei Überschreitung wird das Speichern bzw. Ändern wegen Überschreitung des Tariflimits abgelehnt (dieselbe Definition ist in einem höheren Tarif erlaubt) und durch ein Upgrade freigegeben. Die tarifabhängigen Zahlen finden Sie unter Tarife.

Der Media-Datei-Ingest wird, anders als externe Aufrufe wie Http und EmailSend, nicht auf das Limit für externe Aufrufe pro Definition angerechnet.

ResourceForEach ist ein zusammengesetztes Statement mit eigenen Kindern und wird daher selbst nicht auf die Anzahl externer Aufrufe angerechnet. Gezählt werden die externen Aufruf-Statements (Http, EmailSend) in onEach (statisch als 1 gezählt, aber bei der Iteration pro Element tatsächlich ausgeführt). onEach kann externe Aufrufe oder einen Media-Datei-Ingest enthalten, und das gilt ebenso für den body von Loop. Wie oft eine Iteration tatsächlich läuft, geht nicht in diese Zählung ein, sondern kommt stattdessen unten im Zeitbudget als Multiplikation zum Tragen.

Obergrenzen für Wertlängen (Laufzeit)

Die Statements für Signatur und Textverarbeitung sowie Cache haben eine Obergrenze für die Größe des Werts, den sie behandeln. Gemeint ist nicht die Länge des Ausdrucks, sondern die Länge des Werts, zu dem der Ausdruck aufgelöst wird (die sechzehn Zeichen { /rawPayload } zeigen auf mehrere Dutzend KB), und deshalb wird sie nicht beim Speichern, sondern während der Ausführung geprüft.

ZielObergrenzeBei Überschreitung
Das value von Signature65.536 ZeichenDieses Statement schlägt fehl (status 422)
Das value von Hash128 ZeichenDieses Statement schlägt fehl (status 422)
Das value von Regex10.240 Zeichen (10KiB)Dieses Statement schlägt fehl (status 400)
Das value von Cache10.240 Byte (10KiB)Dieses Statement schlägt fehl (status 422)
  • Alle vier sind wie jeder andere Laufzeitfehlschlag und lassen sich daher mit Try/catch lokal behandeln.
  • Die Obergrenze von Signature ist an der Body-Größe ausgerichtet, die reale Anbieter senden (Zahlungsereignisse liegen bei einigen KB, Bestell-Webhooks erreichen mehrere Dutzend KB). Hash ist die Stelle, an der einige Felder aneinandergehängt werden, und daher viel enger.
  • Die 128 Zeichen von Regex.pattern sind die Prüfung beim Speichern aus den Statischen Einschränkungen oben. Diese Länge ist keine Vorrichtung gegen ein Durchdrehen des Patterns ((a+)+$ ist auch mit sechs Zeichen gefährlich). Dagegen wirken die Regel, dass ein Pattern nur als Literal geschrieben werden darf, und das Zeitbudget unten; die Länge verspricht nur eine Größe, die ein Mensch lesen und prüfen kann.

Zeitbudget (Laufzeit)

Die Zeit für eine einzelne Ausführung wird durch eine einzige Formel bestimmt: min(30 Sekunden + Summe der von den Statements deklarierten Zeiten, 180 Sekunden).

  • Das Budget wird aus dem jeweiligen Script berechnet. Zum Grundbudget wird nur die Zeit addiert, die die Definition deklariert hat. Als deklarierte Zeit gibt es allein das timeoutMs von Http und EmailSend. Http verbraucht bei jeder Wiederholung erneut sein eigenes timeoutMs und wird daher als timeoutMs × (1 + retry) gezählt; EmailSend wiederholt nicht und wird daher einmal gezählt. Fehlt timeoutMs, wird mit dem Standardwert gerechnet (Http 30 Sekunden, EmailSend 10 Sekunden).
  • Arbeiten ohne deklarierte Zeit gehen vom Grundbudget von 30 Sekunden ab. Dazu zählen das Lesen und Schreiben von Ressourcen, der Media-Datei-Ingest und das, was eine Iteration in ihrem Inneren tut. Deshalb ist das Grundbudget kein formaler Wert, sondern ein tatsächlicher Anteil.
  • Wie summiert wird, folgt der Struktur der Statements. Hintereinander stehende Statements werden addiert, bei If gilt der größere der beiden Zweige und bei Parallel der größte unter den Branches. Bei Loop wird der body mit der Anzahl der Iterationen multipliziert (maxIterations, ohne Deklaration 10.000), bei ResourceForEach das onEach mit der Anzahl der verarbeiteten Elemente (limit, ohne Deklaration 10.000).
  • Bei einer Iteration ohne externen Aufruf ist die deklarierte Zeit 0. Deshalb wird das Grundbudget von 30 Sekunden zur tatsächlichen Grenze, und genau hier stößt ein Script mit einer Iteration in der Praxis an.
  • Die Obergrenze von 180 Sekunden blockiert nicht das Speichern, sondern schneidet ab. Überschreitet das Rechenergebnis die Obergrenze, wird dieses Script dennoch gespeichert und ausgeführt und bei Erreichen von 180 Sekunden dort abgebrochen.

Anzahllimits pro Plan

Bei einem Script ist die Anzahl pro Organization je nach Plan begrenzt.

PlanScript-Anzahl
Free10
Basic30
Pro100
EnterpriseUnbegrenzt

Davon getrennt sind auch die Anzahl der Statements und der externen Aufrufe (Http, EmailSend), die eine Script-Definition enthalten kann, je nach Tarif begrenzt. Überschreitet eine Definition beim Speichern oder Ändern das Limit dieses Tarifs, wird sie abgelehnt; die konkreten Zahlen finden Sie unter Tarife.

Wenn das Limit erreicht ist, wird das Erstellen eines neuen Script abgelehnt.

Sicherheitsmodell

Secret-Header

Ein Eintrag in Http.headers mit secret:true ist nur für die CMA (Administrator) bestimmt, wird dem Endnutzer (ServiceUser) nicht offengelegt und erst unmittelbar vor dem Senden entschlüsselt. Legen Sie hier Geheimnisse wie einen LLM-API-Schlüssel ab (selbst wenn er in ein App Bundle gepackt wird, wird der Secret-Wert maskiert und verlässt niemals den ursprünglichen Space).

Das secret von Signature erhält innerhalb des Space diese Behandlung nicht. Es wird nicht verschlüsselt, sondern genau so gespeichert, wie es in der Definition steht, daher sehen Rollen, die dieses Script lesen können, den Wert. Mitglieder (ServiceUser) können eine Script-Definition nicht lesen (Abfrage und Autorenschaft sind CMA-exklusiv, und auf der ACMA gibt es keine Script-API). Bei einem Script, das einen Prüfschlüssel enthält, ist es sicherer, die Rollen, die es lesen können, eng zu halten.

Verlässt es den Space, verhält es sich anders. Wird dieses Script in ein App Bundle gepackt, wird das secret von Signature maskiert und verlässt den ursprünglichen Space nicht. Bei Http.headers werden die Einträge mit secret-Flag und der Authorization-Header verdeckt, während beim secret von Signature das Feld selbst der Signaturschlüssel ist und daher bedingungslos verdeckt wird. Auch ein Signature, das in If, Loop oder Try verschachtelt ist, wird mitverdeckt.

Ausführungsidentität und Autorisierung

  • Ausführungsidentität: Während der Ausführung wird jeder Ressourcenvorgang unter der Identität des Benutzers ausgeführt, der /execute aufgerufen hat. Der createdBy/updatedBy einer erstellten oder geänderten Ressource ist der Aufrufer, und auch ein createdBy: ":self"-Scope wird anhand des Aufrufers aufgelöst. Die Ausnahme ist der anonyme Aufruf. Eine Ausführung, die über /execute/anonymous hereinkommt, hat keinen Aufrufer, daher werden beide anhand des Autors aufgelöst (Anonymer Aufruf).
  • Es gibt zwei Autorisierungsgrenzen, und zur Laufzeit prüft die Engine die Ressourcenberechtigungen nicht pro Statement erneut.
    1. Zum Autorenzeitpunkt (Speicherung): Beim Speichern eines Script wird geprüft, ob der Autor die von seinen Statements verwendeten Ressourcen- und Aktionsberechtigungen tatsächlich besitzt. Fehlt auch nur eine, wird die Speicherung abgelehnt. Mit anderen Worten: Ein Script, das einen unautorisierten Vorgang enthält, wird von vornherein nicht gespeichert. Diese Prüfung erhält jedes Statement, das eine Ressource auswählt, ob Leaf oder blockbesitzendes ResourceForEach. Auch eine schon gespeicherte Definition wird beim Ändern erneut geprüft, sodass sich eine Definition nach dem Entzug der Berechtigung nicht mehr ändern und speichern lässt.
      • Das Mitgliederverzeichnis (ServiceUser) wird nicht über die Berechtigungs-Map, sondern über die Einstellungsachse geprüft. Um in den drei Lese-Statements resource: "ServiceUser" zu verwenden, müssen die settings der SpaceRole des Autors SETTING_SERVICE_LOGIN (oder SETTING_ALL) enthalten (siehe settings der SpaceRole). Denn das Mitgliederverzeichnis ist auch auf allen anderen Wegen eine Ressource, die von den Space-Einstellungen verwaltet wird.
      • Ein Statement, das ein Mitglied ändert, lässt sich mit keiner Rolle speichern. Da es in einem Script überhaupt keinen Weg gibt, ein Mitglied zu erstellen, zu ändern oder zu löschen, wird es nicht wegen fehlender Berechtigung (403), sondern als falsch geschriebenes Statement (400) abgelehnt. Es ist also keine Lücke, die man mit zusätzlichen Berechtigungen schließen kann.
    2. Zum Aufrufzeitpunkt (/execute): Es wird nur die Execute-Berechtigung des Aufrufers auf das Script geprüft. Ohne sie ergibt sich 403. Nach bestandener Prüfung werden die Ressourcenberechtigungen pro Statement zur Laufzeit nicht erneut überprüft, und die Ausführung läuft. Das funktioniert wie die Berechtigung zum Ausführen einer Funktion in der Programmierung. Wer die Berechtigung hat, die Funktion auszuführen, wird nach der Berechtigung für die einzelnen Vorgänge darin nicht erneut gefragt. Auf dem Weg des anonymen Aufrufs gibt es diese Prüfung nicht. Denn es gibt keinen Aufrufer, den man prüfen könnte, und deshalb ist das Öffnen dieses Wegs dasselbe wie ein Script ohne Authentifizierung zu veröffentlichen.
  • Blockieren des direkten Aufrufs (directCallEnabled): Ist das directCallEnabled eines Script false, wird bereits der direkte /execute-Aufruf selbst abgelehnt. Dieses Gate greift erst, nachdem die Prüfung der Execute-Berechtigung bestanden ist, und blockiert daher auch dann, wenn die Execute-Berechtigung vorhanden ist. Ein Aufrufer ohne Execute-Berechtigung erhält bereits 403, bevor der Aufruf dieses Gate erreicht. Dieses Gate gibt es nur an diesem Endpunkt, daher führen die Anbindungsaktion (script) eines Webhook und ein Scheduler das Script unverändert aus. Der Standardwert ist true (direkter Aufruf erlaubt).
  • Anonymer Aufruf (anonymousCallEnabled): Der Standardwert ist false. Setzen Sie ihn auf true, wird nur dieses Script auch über einen eigenen Weg ohne Authentifizierung (/execute/anonymous) ausgeführt, und die Ausführungsidentität ist dann nicht der Aufrufer, sondern der Autor. Von den beiden Grenzen oben fehlt auf diesem Weg die Prüfung zum Aufrufzeitpunkt (Execute-Berechtigung), daher übernimmt das Script die tatsächliche Authentifizierung selbst (die Signaturprüfung der empfangenen Anfrage). Die Bedingungen zum Einschalten und die Speicherregeln werden unter Anonymer Aufruf behandelt.
  • Eigentümer-Scope: createdBy: ":self" in einem where-Filter bedeutet "nur das, was der aktuelle Aufrufer erstellt hat" (z. B. nur die eigene Wallet abfragen). In einem Script, das anonyme Aufrufe erlaubt, lässt sich dieser Filter nicht verwenden. Da es keinen Aufrufer gibt und er zum Autor aufgelöst wird, trifft die ursprüngliche Bedeutung eines Eigentümer-Scope nicht mehr zu.
  • Delegierte Berechtigungen (Vorsicht für Autoren): Verbindet man die beiden obigen Grenzen, ist das Ausführen eines Script gleichbedeutend mit einem Handeln mit den an das Script delegierten Berechtigungen des Autors. Der Aufrufer braucht nur Execute, und die Statements innerhalb des Script laufen genau in dem Umfang, für den der Autor bei der Speicherung autorisiert war. Dadurch kann ein Ressourcenvorgang, den der Aufrufer selbst nicht durchführen könnte, dennoch über das Script geschehen. Da die dem Autor erteilten Berechtigungen die effektive Reichweite dieses Script bestimmen, überlegen Sie sorgfältig, welche Vorgänge Sie in ein Script aufnehmen.

Zusammenfassende Checkliste

Prüfen Sie vor dem Speichern Folgendes.

  • Haben Sie den anonymen Aufruf (anonymousCallEnabled) eingeschaltet, steht in where kein createdBy: ":self", und ein Statement, das die empfangene Anfrage prüft (etwa Signature), steht an erster Stelle.
  • Die externen Aufrufe (Http, EmailSend) und die Gesamtzahl der Statements liegen innerhalb des Tariflimits, SetVar ist höchstens 10 und Cache höchstens 5.
  • Wenn Sie Cache verwendet haben, haben Sie key als Literal geschrieben und es nicht innerhalb von Loop oder ResourceForEach platziert.
  • Wenn Sie mit ResourceForEach eine große Menge durchlaufen, haben Sie ein limit deklariert oder geprüft, dass die Menge vollständig durchlaufbar ist.
  • Haben Sie eine Iteration (Loop, ResourceForEach) aufgenommen, haben Sie geprüft, dass diese Iteration als Multiplikation in das Zeitbudget eingeht (ohne externen Aufruf ist das Grundbudget von 30 Sekunden die Grenze).
  • Secret-Werte wurden nur über secret:true in Http.headers eingetragen (Signature.secret wird nicht verschlüsselt gespeichert, daher haben Sie die Rollen geprüft, die dieses Script lesen können).
  • Die Nachricht für die Signaturprüfung haben Sie nicht über /payload, sondern über { /rawPayload } genommen.
  • Gibt es ein Statement, das ein Mitglied (ServiceUser) liest, besitzt der Autor SETTING_SERVICE_LOGIN, und ein Statement, das diese Ressource ändert, haben Sie nicht aufgenommen.
  • Nicht rückgängig zu machende Vorgänge (externe Aufrufe) wurden möglichst weit hinten platziert.
  • Wenn Sie Update-/Patch-Konkurrenz befürchten, verwenden Sie das version-Feld von ResourceUpdate oder ResourcePatch.
  • Um ein Ergebnis zurückzugeben, haben Sie Return.value angegeben.

Fehler

Dies sind die Codes, die auftreten, wenn die Form der Definition eine statische Einschränkung verletzt und das Speichern deshalb abgelehnt wird. Die Codes für Verstöße gegen die Regeln der Wertausdrücke stehen unter Fehler bei den Wertausdrücken, die Codes, die beim Aufrufen und Löschen auftreten, unter Fehler bei den Endpunkten. Codes, die allen Ressourcen gemeinsam sind, finden Sie unter Gemeinsame Fehler.

CodeBedingung
WGL400066Eine Definition enthält mehr als 5 Cache-Statements.
WGL400068Ein Cache-Statement steht im Block von Loop oder ResourceForEach.
WGL400067Im key eines Cache-Statements steht kein Literal, sondern eine { /pointer }-Referenz.
WGL400065Das ttl eines Cache-Statements liegt außerhalb des erlaubten Bereichs.
WGL400063In einem Cache-Statement steht ein Feld, das nicht zur jeweiligen action gehört (ttl bei Get, defaultValue bei Set).
WGL400060Im resource eines schreibenden Statements (ResourceCreate, ResourceUpdate, ResourcePatch, ResourceDelete sowie der Statements für Veröffentlichung und Archivierung) steht "ServiceUser".
WGL400061In einem Script, das anonyme Aufrufe erlaubt (anonymousCallEnabled), steht im where eines Lese-Statements createdBy: ":self".
WGL400023Eine Definition enthält mehr als 10 SetVar-Statements.
WGL400026Das retry eines Http-Statements überschreitet die Obergrenze 2.
WGL400036ResourceForEach überschreitet die Obergrenze für die Anzahl der Elemente, die dieses Statement verarbeiten kann.
WGL429005Die Gesamtzahl der Statements in einer Definition überschreitet das Tariflimit.
WGL429006Die Anzahl der externen Aufrufe (Http, EmailSend) in einer Definition überschreitet das Tariflimit.
WGL403015Der Autor besitzt die Berechtigungen für die Ressourcen und Aktionen nicht, die die Statements der Definition verwenden. Selbst wenn die Berechtigung vorliegt, wird das Speichern abgelehnt, sobald an dieser Erlaubnis ein Filter contentType, createdBy oder tag hängt. Es muss eine Erlaubnis ohne Bedingung sein. Die einzige Ausnahme ist Content Create: Hier wird auch eine Erlaubnis anerkannt, die einen contentType-Bereich festlegt, und dieser Bereich wird mit dem im Statement angegebenen contentType abgeglichen (für Media Create gilt diese Ausnahme nicht). Denselben Code erhalten Sie auch, wenn im resource eines Lese-Statements "ServiceUser" steht, der Autor aber SETTING_SERVICE_LOGIN nicht besitzt.