OBS/Kostenpflichtige Module/RESTServer/Scripting: Unterschied zwischen den Versionen
(Die Seite wurde neu angelegt: „{{Kostenpflichtige Module}} =Anleitung für Endpunkt-Scripte= Anfragen an einen Endpunkt werden (nach erfolgreicher Authentifizierung und Authorisierung) an das hinterlegte Script weitergegeben und müssen dort beantwortet werden. Gibt es einen Fehler oder wird die Anfrage nicht korrekt bearbeitet wird ein Fehlercode (HTML-Fehlercode) zurückgegeben. =Sicherheit= Da hier ein Zugriff von außen ermöglicht wird ist es sehr wichtig, dass Übergabeparame…“) |
Keine Bearbeitungszusammenfassung |
||
| Zeile 1: | Zeile 1: | ||
{{Kostenpflichtige Module}} | {{Kostenpflichtige Module}} | ||
=Anleitung | =Anleitung fuer Endpunkt-Skripte= | ||
Jede Anfrage an einen Endpunkt wird nach erfolgreicher Authentifizierung und Autorisierung an das hinterlegte Skript weitergegeben. Das Skript ist in Object Pascal geschrieben und greift auf die OBS-Bibliothek (DB-Zugriff, Hilfsfunktionen) zu. | |||
= | ==Sicherheitshinweise== | ||
{{Hinweis|Endpunkt-Skripte verarbeiten Daten aus dem Internet. Uebergabeparameter duerfen niemals ungeprueft in SQL-Anweisungen eingebaut werden. Werte immer ueber ''DB_SQLVal'' bzw. Parameter-Bindings absichern, Eingabewerte gegen Whitelists pruefen.}} | |||
* Eingaben gegen erwartete Werte pruefen (z.B. Pflichtfelder, erlaubte Typen, Wertebereiche). | |||
* Nur das zurueckgeben, was der Konsument wirklich braucht - keine internen IDs, keine Sys-Felder, keine Passwoerter. | |||
* Bei Fehlern keine internen Details an den Konsumenten zurueckgeben; stattdessen schreibt der Server ohnehin Detail-Eintraege in '''RESTSRV_PROTO'''. | |||
==Methoden-Signatur== | |||
function | |||
begin | Pro HTTP-Methode wird im Skript eine gleichnamige Funktion implementiert. Der Server ruft genau die Funktion auf, die zur Methode des eingehenden Requests passt. | ||
[ | |||
function Get (oParams: TStrings; oBody: TJSONObject): string; | |||
function Post (oParams: TStrings; oBody: TJSONObject): string; | |||
function Put (oParams: TStrings; oBody: TJSONObject): string; | |||
function Delete(oParams: TStrings; oBody: TJSONObject): string; | |||
function Patch (oParams: TStrings; oBody: TJSONObject): string; | |||
Nicht implementierte Methoden liefern automatisch ''405''-aehnliche Fehler ueber die generische Skript-Antwort. | |||
==Parameter== | |||
===oParams (TStrings)=== | |||
Enthaelt alle Query-Parameter, POST-Parameter (form-urlencoded) sowie die durchgereichten HTTP-Header. Werte sind immer Strings. | |||
Filterung durch den Server: | |||
* Geblockte Header werden nicht durchgereicht: ''authorization'', ''cookie'', ''proxy-authorization'', ''x-forwarded-for'', ''x-real-ip'', ''apikey'', ''api_key''. | |||
* Parameter mit Praefix '''_OBS_''' koennen von aussen nicht gesetzt werden; sie sind fuer interne Werte reserviert. | |||
* Werte werden auf max. 1024 Zeichen begrenzt. | |||
* Null-Bytes und Steuerzeichen (ausser Tab, CR, LF) werden entfernt. | |||
Zugriff im Skript: | |||
if (not Empty(oParams.Values['kundennr'])) then begin | |||
cKundenNr := oParams.Values['kundennr']; | |||
end; | end; | ||
===oBody (TJSONObject)=== | |||
begin | |||
Enthaelt den Request-Body, sofern dieser ein gueltiges JSON-Objekt ist. Bei leerem oder nicht-JSON-Body wird ''nil'' uebergeben. | |||
if (Assigned(oBody)) then begin | |||
oBody.TryGetValue<string>('email', cEmail); | |||
oBody.TryGetValue<integer>('menge', nMenge); | |||
end; | end; | ||
function | Maximale Body-Groesse: 10 MB. | ||
begin | |||
===Reservierte _OBS_-Parameter=== | |||
Bei aktiver JWT-Authentifizierung stehen die Token-Claims als Parameter zur Verfuegung: | |||
{| class="wikitable" | |||
! Parameter !! Inhalt | |||
|- | |||
| _OBS_JWT_ID || JWT-Id (''jti''-Claim) - typisch die User-Id | |||
|- | |||
| _OBS_JWT_SUBJECT || Subject (''sub''-Claim) - typisch Benutzername | |||
|- | |||
| _OBS_JWT_AUDIENCE || Audience (''aud''-Claim) - typisch Mandant / Rolle | |||
|} | |||
Diese Werte werden vom Server gesetzt und koennen vom Skript fuer Berechtigungs- und Mandantenpruefungen verwendet werden. | |||
==Rueckgabe== | |||
Die Rueckgabe ist immer ein '''JSON-String'''. Wird ein leerer String zurueckgegeben, antwortet der Server automatisch mit ''{}''. Der Server setzt Content-Type auf ''application/json; charset=utf-8'' und Status auf 200, sofern das Skript nicht selbst einen Fehler signalisiert. | |||
Einfaches Beispiel: | |||
<syntaxhighlight lang="pascal" line> | |||
function Get(oParams: TStrings; oBody: TJSONObject): string; | |||
var oRes: TJSONObject; | |||
begin | |||
oRes := TJSONObject.Create(); | |||
try | |||
oRes.AddPair('wert_string', '123'); | |||
oRes.AddPair('wert_int' , 456); | |||
result := oRes.ToJSON(); | |||
finally | |||
MyFreeAndNil(oRes); | |||
end; | |||
end; | |||
</syntaxhighlight> | |||
==Datenbank-Zugriff== | |||
Innerhalb eines Skripts steht die globale OBS-Datenbankverbindung '''oDB''' zur Verfuegung. Jeder Request bekommt eine eigene Verbindung aus dem DB-Connection-Pool, das Skript muss daher nicht selbst auf Thread-Sicherheit achten. | |||
Lese-Beispiel: | |||
<syntaxhighlight lang="pascal" line> | |||
var cSql : string; | |||
qData : TxFQuery; | |||
begin | |||
cSql := 'SELECT ku_name1 FROM kunden WHERE ku_nr = ' + DB_SQLVal(oParams.Values['kundennr']); | |||
if (DB_SOpen('Y00C71XXAA', oDB, cSql, qData)) then begin | |||
// Verarbeitung | |||
end; | |||
DB_Close(qData); | |||
end; | |||
</syntaxhighlight> | |||
==JWT-Authentifizierungs-Skript== | |||
Bei Zugaengen mit aktiver JWT-Pflicht wird ueber den JWT-Endpunkt das im Zugang hinterlegte Authentifizierungs-Skript aufgerufen (F7 in der Zugaenge-Liste). Das Skript muss eine Methode ''Authenticate'' bereitstellen: | |||
<syntaxhighlight lang="pascal" line> | |||
function Authenticate(oParams: TStrings; oBody: TJSONObject): string; | |||
var oRes : TJSONObject; | |||
cUser : string; | |||
cPass : string; | |||
begin | |||
oRes := TJSONObject.Create(); | |||
try | |||
// Eingangsdaten lesen (Body oder Query-Param) | |||
if (Assigned(oBody)) then begin | |||
oBody.TryGetValue<string>('username', cUser); | |||
oBody.TryGetValue<string>('password', cPass); | |||
end; | |||
// Pruefung gegen eigene Tabelle, Hash-Verfahren, LDAP, ... | |||
if (PasswortPasst(cUser, cPass)) then begin | |||
oRes.AddPair('status' , 1); | |||
oRes.AddPair('_OBS_JWT_ID' , cUser); | |||
oRes.AddPair('_OBS_JWT_SUBJECT', cUser); | |||
oRes.AddPair('_OBS_JWT_AUDIENCE', 'mandant1'); | |||
end else begin | |||
oRes.AddPair('status', 9); | |||
oRes.AddPair('error' , 'Login fehlgeschlagen'); | |||
end; | |||
result := oRes.ToJSON(); | |||
finally | |||
MyFreeAndNil(oRes); | |||
end; | |||
end; | |||
</syntaxhighlight> | |||
Rueckgabewerte: | |||
{| class="wikitable" | |||
! status !! Bedeutung | |||
|- | |||
| 1 || Erfolgreich, der Server erzeugt aus den ''_OBS_JWT_*''-Werten einen Token (HS256) | |||
|- | |||
| 9 || Misserfolg, der Server antwortet mit 403 und protokolliert den Wert von ''error'' | |||
|} | |||
==Fehlerbehandlung im Skript== | |||
Tritt im Skript eine Exception auf oder schlaegt die Syntax-Pruefung fehl, antwortet der Server mit 500 ''Interner Fehler'' und protokolliert die Detail-Meldung in '''RESTSRV_PROTO''' (mit Skript-Fehlertext). Der Konsument sieht keine internen Details. | |||
Will das Skript einen spezifischen Fehler an den Konsumenten zurueckgeben, ist eine eigene JSON-Struktur zu liefern: | |||
=Parameter= | <syntaxhighlight lang="pascal" line> | ||
function Post(oParams: TStrings; oBody: TJSONObject): string; | |||
var oRes: TJSONObject; | |||
begin | |||
oRes := TJSONObject.Create(); | |||
try | |||
if (Empty(oParams.Values['kundennr'])) then begin | |||
oRes.AddPair('status', 'error'); | |||
oRes.AddPair('msg' , 'Parameter ''kundennr'' fehlt'); | |||
result := oRes.ToJSON(); | |||
exit; | |||
end; | |||
// ... | |||
finally | |||
MyFreeAndNil(oRes); | |||
end; | |||
end; | |||
</syntaxhighlight> | |||
==Skript-Cache== | |||
Der Server cached das kompilierte Skript pro Endpunkt. Aenderungen am Skript ueber F7 sind sofort wirksam - der Server erkennt die Aenderung am ''sys_date'' und compiliert neu. | |||
= | ==Verfuegbare Bibliotheken== | ||
Im Skript koennen alle OBS-Standard-Bibliotheken verwendet werden. Typische Einstiegspunkte: | |||
* ''Base.Tools'' - String-, Datum-, IIF-, Empty-Helper | |||
* ''Base.DB'' / ''Base.xQuery'' - Datenbank-Operationen, ''DB_SQLVal'', ''DB_SOpen'', ''DB_LSeek'' | |||
* ''Base.qSqlReg'' - ''qSqlInit'', ''qSet'', ''SaveData'' | |||
* ''System.JSON'' - JSON-Objekte und -Arrays | |||
* ''Base.ToolsConst'' - Konstanten wie ''CRLF'', ''SINGELQUOTE'', ''EMPTY_DATE'' | |||
Konkrete Beispiele: | |||
* [[OBS/Kostenpflichtige Module/RESTServer/Beispiel1|Beispiel: Daten-Abruf mit JWT]] | |||
* [[OBS/Kostenpflichtige Module/RESTServer/Beispiel2|Beispiel: Datensatz anlegen mit JSON-Body]] | |||
Version vom 19. Mai 2026, 06:40 Uhr
- A Preise aktualisieren
- C Personen übertragen
- E Kategorien verwalten
- G Kataloge verwalten
- I Merkliste übertragen
- K Varianten übertragen
- L Artikelvarianten übertragen
- M Referenzarten übertragen
- N Lagerbestände verwalten
- U Bestellungen einlesen
- V leere Passworte füllen
- W Update-Informationen zurücksetzen
- X Konfiguration
- Z Protokoll
Anleitung fuer Endpunkt-Skripte
Jede Anfrage an einen Endpunkt wird nach erfolgreicher Authentifizierung und Autorisierung an das hinterlegte Skript weitergegeben. Das Skript ist in Object Pascal geschrieben und greift auf die OBS-Bibliothek (DB-Zugriff, Hilfsfunktionen) zu.
Sicherheitshinweise
- Eingaben gegen erwartete Werte pruefen (z.B. Pflichtfelder, erlaubte Typen, Wertebereiche).
- Nur das zurueckgeben, was der Konsument wirklich braucht - keine internen IDs, keine Sys-Felder, keine Passwoerter.
- Bei Fehlern keine internen Details an den Konsumenten zurueckgeben; stattdessen schreibt der Server ohnehin Detail-Eintraege in RESTSRV_PROTO.
Methoden-Signatur
Pro HTTP-Methode wird im Skript eine gleichnamige Funktion implementiert. Der Server ruft genau die Funktion auf, die zur Methode des eingehenden Requests passt.
function Get (oParams: TStrings; oBody: TJSONObject): string; function Post (oParams: TStrings; oBody: TJSONObject): string; function Put (oParams: TStrings; oBody: TJSONObject): string; function Delete(oParams: TStrings; oBody: TJSONObject): string; function Patch (oParams: TStrings; oBody: TJSONObject): string;
Nicht implementierte Methoden liefern automatisch 405-aehnliche Fehler ueber die generische Skript-Antwort.
Parameter
oParams (TStrings)
Enthaelt alle Query-Parameter, POST-Parameter (form-urlencoded) sowie die durchgereichten HTTP-Header. Werte sind immer Strings.
Filterung durch den Server:
- Geblockte Header werden nicht durchgereicht: authorization, cookie, proxy-authorization, x-forwarded-for, x-real-ip, apikey, api_key.
- Parameter mit Praefix _OBS_ koennen von aussen nicht gesetzt werden; sie sind fuer interne Werte reserviert.
- Werte werden auf max. 1024 Zeichen begrenzt.
- Null-Bytes und Steuerzeichen (ausser Tab, CR, LF) werden entfernt.
Zugriff im Skript:
if (not Empty(oParams.Values['kundennr'])) then begin
cKundenNr := oParams.Values['kundennr'];
end;
oBody (TJSONObject)
Enthaelt den Request-Body, sofern dieser ein gueltiges JSON-Objekt ist. Bei leerem oder nicht-JSON-Body wird nil uebergeben.
if (Assigned(oBody)) then begin
oBody.TryGetValue<string>('email', cEmail);
oBody.TryGetValue<integer>('menge', nMenge);
end;
Maximale Body-Groesse: 10 MB.
Reservierte _OBS_-Parameter
Bei aktiver JWT-Authentifizierung stehen die Token-Claims als Parameter zur Verfuegung:
| Parameter | Inhalt |
|---|---|
| _OBS_JWT_ID | JWT-Id (jti-Claim) - typisch die User-Id |
| _OBS_JWT_SUBJECT | Subject (sub-Claim) - typisch Benutzername |
| _OBS_JWT_AUDIENCE | Audience (aud-Claim) - typisch Mandant / Rolle |
Diese Werte werden vom Server gesetzt und koennen vom Skript fuer Berechtigungs- und Mandantenpruefungen verwendet werden.
Rueckgabe
Die Rueckgabe ist immer ein JSON-String. Wird ein leerer String zurueckgegeben, antwortet der Server automatisch mit {}. Der Server setzt Content-Type auf application/json; charset=utf-8 und Status auf 200, sofern das Skript nicht selbst einen Fehler signalisiert.
Einfaches Beispiel:
function Get(oParams: TStrings; oBody: TJSONObject): string;
var oRes: TJSONObject;
begin
oRes := TJSONObject.Create();
try
oRes.AddPair('wert_string', '123');
oRes.AddPair('wert_int' , 456);
result := oRes.ToJSON();
finally
MyFreeAndNil(oRes);
end;
end;
Datenbank-Zugriff
Innerhalb eines Skripts steht die globale OBS-Datenbankverbindung oDB zur Verfuegung. Jeder Request bekommt eine eigene Verbindung aus dem DB-Connection-Pool, das Skript muss daher nicht selbst auf Thread-Sicherheit achten.
Lese-Beispiel:
var cSql : string;
qData : TxFQuery;
begin
cSql := 'SELECT ku_name1 FROM kunden WHERE ku_nr = ' + DB_SQLVal(oParams.Values['kundennr']);
if (DB_SOpen('Y00C71XXAA', oDB, cSql, qData)) then begin
// Verarbeitung
end;
DB_Close(qData);
end;
JWT-Authentifizierungs-Skript
Bei Zugaengen mit aktiver JWT-Pflicht wird ueber den JWT-Endpunkt das im Zugang hinterlegte Authentifizierungs-Skript aufgerufen (F7 in der Zugaenge-Liste). Das Skript muss eine Methode Authenticate bereitstellen:
function Authenticate(oParams: TStrings; oBody: TJSONObject): string;
var oRes : TJSONObject;
cUser : string;
cPass : string;
begin
oRes := TJSONObject.Create();
try
// Eingangsdaten lesen (Body oder Query-Param)
if (Assigned(oBody)) then begin
oBody.TryGetValue<string>('username', cUser);
oBody.TryGetValue<string>('password', cPass);
end;
// Pruefung gegen eigene Tabelle, Hash-Verfahren, LDAP, ...
if (PasswortPasst(cUser, cPass)) then begin
oRes.AddPair('status' , 1);
oRes.AddPair('_OBS_JWT_ID' , cUser);
oRes.AddPair('_OBS_JWT_SUBJECT', cUser);
oRes.AddPair('_OBS_JWT_AUDIENCE', 'mandant1');
end else begin
oRes.AddPair('status', 9);
oRes.AddPair('error' , 'Login fehlgeschlagen');
end;
result := oRes.ToJSON();
finally
MyFreeAndNil(oRes);
end;
end;
Rueckgabewerte:
| status | Bedeutung |
|---|---|
| 1 | Erfolgreich, der Server erzeugt aus den _OBS_JWT_*-Werten einen Token (HS256) |
| 9 | Misserfolg, der Server antwortet mit 403 und protokolliert den Wert von error |
Fehlerbehandlung im Skript
Tritt im Skript eine Exception auf oder schlaegt die Syntax-Pruefung fehl, antwortet der Server mit 500 Interner Fehler und protokolliert die Detail-Meldung in RESTSRV_PROTO (mit Skript-Fehlertext). Der Konsument sieht keine internen Details.
Will das Skript einen spezifischen Fehler an den Konsumenten zurueckgeben, ist eine eigene JSON-Struktur zu liefern:
function Post(oParams: TStrings; oBody: TJSONObject): string;
var oRes: TJSONObject;
begin
oRes := TJSONObject.Create();
try
if (Empty(oParams.Values['kundennr'])) then begin
oRes.AddPair('status', 'error');
oRes.AddPair('msg' , 'Parameter ''kundennr'' fehlt');
result := oRes.ToJSON();
exit;
end;
// ...
finally
MyFreeAndNil(oRes);
end;
end;
Skript-Cache
Der Server cached das kompilierte Skript pro Endpunkt. Aenderungen am Skript ueber F7 sind sofort wirksam - der Server erkennt die Aenderung am sys_date und compiliert neu.
Verfuegbare Bibliotheken
Im Skript koennen alle OBS-Standard-Bibliotheken verwendet werden. Typische Einstiegspunkte:
- Base.Tools - String-, Datum-, IIF-, Empty-Helper
- Base.DB / Base.xQuery - Datenbank-Operationen, DB_SQLVal, DB_SOpen, DB_LSeek
- Base.qSqlReg - qSqlInit, qSet, SaveData
- System.JSON - JSON-Objekte und -Arrays
- Base.ToolsConst - Konstanten wie CRLF, SINGELQUOTE, EMPTY_DATE
Konkrete Beispiele: