OBS/Kostenpflichtige Module/RESTServer/Scripting: Unterschied zwischen den Versionen

Aus OBS Wiki
Zur Navigation springen Zur Suche springen
(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 für Endpunkt-Scripte=
=Anleitung fuer Endpunkt-Skripte=


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.
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.


=Sicherheit=
==Sicherheitshinweise==


Da hier ein Zugriff von außen ermöglicht wird ist es sehr wichtig, dass Übergabeparameter niemals zur direkten Datenmanipulation genutzt werden. Bei der Entwicklung von Endpunkt-Scripten muss stehts dieser Aspekt bedacht werden!
{{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.}}


=Methoden=
* 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'''.


Die folgenden Einsprungs-Methoden sind vorgegeben und müssen nach Bedarf implementiert werden:
==Methoden-Signatur==
  function Get(oParams: TStrings): string;
 
  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;


function Post(oParams: TStrings): string;
===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 Delete(oParams: TStrings): string;
Maximale Body-Groesse: 10 MB.
  begin
 
    [...]
===Reservierte _OBS_-Parameter===
end;
 
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>


Alle übergebenen Parameter werden in der Liste "oParams" übergbeben und sind immer im Format "string".
==Skript-Cache==


Beispiel für den Zugriff:
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.
if (not empty(oParams.Values['parameter_name'])) then begin
    cWert := oParams.Values['parameter_name'];
end;


=Rückgabe=
==Verfuegbare Bibliotheken==


Die Rückgabe erfolgt ausschließlich im JSON-Format.
Im Skript koennen alle OBS-Standard-Bibliotheken verwendet werden. Typische Einstiegspunkte:


Hier ein simples Beispiel für eine Antwort:
* ''Base.Tools'' - String-, Datum-, IIF-, Empty-Helper
{
* ''Base.DB'' / ''Base.xQuery'' - Datenbank-Operationen, ''DB_SQLVal'', ''DB_SOpen'', ''DB_LSeek''
    "Wert_string": "123",
* ''Base.qSqlReg'' - ''qSqlInit'', ''qSet'', ''SaveData''
    "Wert_int": 456
* ''System.JSON'' - JSON-Objekte und -Arrays
}
* ''Base.ToolsConst'' - Konstanten wie ''CRLF'', ''SINGELQUOTE'', ''EMPTY_DATE''


Hier der dafür nötige Code:
Konkrete Beispiele:
function Get(oParams: TStrings): string;
var oRes: TJSONObject;
begin
    oRes := TJSONObject.Create();
    oRes.AddPair('Wert_string', '123');
    oRes.AddPair('Wert_int', 456);
    result := oRes.ToJSON();
end;


Wird durch das Script keine Antwort erzeugt, wird automatisch eine leere JSON-Antwort zurückgegben:
* [[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

Kostenpflichtige Module

Internet-Shop
UPS
IMS Professional
SMS
Mehrlager-Verwaltung
Mehrsprachen Modul
Multilanguage Modul
EVA Marketing Tool
Termin-Projekte
Edifact-Schnittstelle
Backup Überwachung Email
OBS Geo Daten
DeliSprint / DPD
Filialen
Cashback
Moebelschnittstelle
Dokumenten Manager
DocuWare-Schnittstelle
OFML-Kalkulation
Versicherungsschaden
Gutschriftsanzeigen
Kameraverwaltung
DataInOut
OpenMasterData / IDS
Sammelpositionen


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

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: