Vous voulez protéger vos sessions 5250 de la possibilité de faire un Appel systéme
Vous devez mettre en place un programme d’exit (8 possibles)
QIBM_QWT_SYSREQPGMS
Vous devez ensuite indiquer sur chaque profil les programmes à utiliser
Schéma ci dessous
L’utilisateur quand il appuiera sur APP SYST le programme PGM1 sera appelé
Programme Exit ici le 1 , nom du programme APPSYS
**free
// programme QIBM_QWT_SYSREQPGMS contrôle d'accès à la touche
// ATTN REQUEST
// l'utilisateur à ce programme de contrôle son profil il s'exécute
// et il n'a pas le droit
ctl-opt
DFTACTGRP(*NO) ;
Dcl-Pi *N;
Reponse int(10);
// 1 ok
// 0 ko
data Char(128);
End-Pi;
//
Reponse = 0;
*inlr = *on ;
GDATA_QRPGLESRC_APPSYS.TXT
Affichage de GDATA_QRPGLESRC_APPSYS.TXT en cours...
Ce programme est simple , il interdit s’il est appelé
Pour ajouter ce programme :
ADDEXITPGM EXITPNT(QIBM_QWT_SYSREQPGMS)
FORMAT(SREQ0100)
PGMNBR(1)
PGM(Votrelib/APPSYS)
REPLACE(NO)
Dans ==>WRKREGINF pour contrôle
Programme de mise à jour des profils qui devront être concernés par le contrôle
**free
// Programme exit pour protéger appel système
// exit PGM QIBM_QWT_SYSREQPGMS
// Pour que ce programme ce déclenche il faut que vous
// l'indiquiez au niveau du profil
// 8 programmes possibles ici le 1 correspond au PGMNBR(1)
// vous devez utiliser l'API QWTSETPX
ctl-opt
DFTACTGRP(*NO) ;
// paramètre recu le profil à protéger
Dcl-Pi *N;
P_user char(10);
End-Pi;
// prototypage de l'API de mise à jour
Dcl-PR QWTSETPX ExtPgm( 'QWTSETPX');
nbrent int(10) ;
flags char(32) ;
format char(8) ;
user char(10) ;
erreur char(32) ;
End-PR;
// Variables de travail
dcl-s wnbrent int(10) ;
dcl-s wflags char(32) ;
dcl-s wformat char(8) ;
dcl-s werreur char(32) ;
// constantes figuratives
dcl-s inact char(04) inz(x'00000000') ;
dcl-s actif char(04) inz(x'00000001') ;
// Appel du programme
wformat = 'SREQ0100' ;
wnbrent = x'00000004' ;
%subst(wflags :1 : 4) = actif ; <<<<< ici
%subst(wflags :5 : 4) = inact ;
%subst(wflags :9 : 4) = inact ;
%subst(wflags :13 : 4) = inact ;
%subst(wflags :17 : 4) = inact ;
%subst(wflags :21 : 4) = inact ;
%subst(wflags :25 : 4) = inact ;
%subst(wflags :29 : 4) = inact ;
QWTSETPX(wnbrent:wflags:wformat:p_user:werreur) ;
*inlr = *on ;
Programme pour voir les programmes du profil
**free
//
// Lecture des informations sur les profils pour appel système
// sur exit pgm QIBM_QWT_SYSREQPGMS
// Rappel 8 possibilités qui correspondent au PGMNBR de l'exit PGM
//
ctl-opt
DFTACTGRP(*NO) ;
// paramétre le profil
Dcl-Pi *N;
P_user char(10);
End-Pi;
// API de lecture des postes
Dcl-PR QWTRTVPX ExtPgm( 'QWTRTVPX');
rcvvar char(40) ;
rcvlen char(4) ;
format char(8) ;
user char(10) ;
erreur char(32) ;
End-PR;
// déclaration des variables de travail
dcl-s wrcvlen char(4) inz(x'00000028') ;
dcl-s wrcvvar char(40) inz(' ') ;
dcl-s wformat char(8) ;
dcl-s werreur char(32) ;
dcl-s wflags char(32) ;
dcl-s wnbpos int(10) ;
// constantes figuratives
dcl-s inact char(04) inz(x'00000000') ;
dcl-s actif char(04) inz(x'00000001') ;
dcl-s msg char(50) inz(' ') ;
dcl-s i int(10) inz(0) ;
// Appel de l'API
wformat = 'SREQ0100' ;
QWTRTVPX(wrcvvar:wrcvlen:wformat:p_user:werreur) ;
wnbpos = 32; // 8 * 4
// extraction des informations pour les 8 programmes
wflags = %subst(wrcvvar : 9 : 32) ;
for i = 1 by 4 to wnbpos ;
if %subst(wflags : i : 4) = actif ;
msg = %trim(msg) + '*ON' ;
else ;
msg = %trim(msg) + '*OFF' ;
endif;
endfor ;
// affichage du résulat
dsply msg ;
*inlr = *on ;
Remarque :
L’utilisateur ne reçoit aucun message , mais rien ne se passe
Attention, il ne faut pas le mettre sur tous les profils, mais uniquement ceux qui le nécessitent.
Par exemple une fenêtre bloquante de ressaisie de mot de passe pour une option sensible.
Vous pouvez faire la même chose pour le programme ATTN …
Vous voulez savoir si une commande est utilisée, il y a plusieurs solutions en voici une basée sur les
programme d’exit qui est assez simple
Il existe un programme d’exit QIBM_QCA_CHG_COMMAND
Pour ajouter votre programme vous avez une commande ADDEXITPGM
ADDEXITPGM EXITPNT(QIBM_QCA_CHG_COMMAND) +
FORMAT(CHGC0100) +
PGMNBR(1) +
PGM(VOTREBIB/HSTCMD) +
TEXT('Tracer une commande') +
PGMDTA('RSTLIB QSYS')
Vous devrez lui indiquer dans la paramètre PGMDTA la commande qualifiée à tracer
exemple :
‘STRDBG QSYS’
Les données reçues sont sur le format CHGC0100 qui vous donne le découpage du buffer reçu par votre programme
Vous pouvez faire un programme générique qui va loguer l’utilisation d’une commande, vous pouvez en mettre plusieurs et la prise en compte est immédiate
Voici le source en clle du programme HSTCMD
PGM PARM(&ExitInfo &NewString &Newlength)
DCL &Class *CHAR 10
DCL &OffsetDec *DEC (7 0)
DCL &CmdLenDec *DEC (7 0)
DCL &ExitInfo *CHAR 2000 /* CHGC0100 interface data */
/* Input parameters */
DCL &ExitPoint *CHAR 20 /* Exit Point name */
DCL &ExitFormat *CHAR 8 /* Exit Point Format */
DCL &CmdName *CHAR 10 /* Command name being executed */
DCL &CmdLib *CHAR 10 /* Command Library */
DCL &Change *CHAR 1 /* Change allowed? 1=yes 0=no */
DCL &Prompt *CHAR 1 /* Prompt requested? 1=yes 0=no */
DCL &Filler *CHAR 2 /* Reserved by IBM */
DCL &Offset *CHAR 4 /* Offset to command string */
DCL &CmdLength *CHAR 4 /* Command string length */
DCL &CmdString *CHAR 2000 /* Command String */
/* Output Parameters */
DCL &NewString *CHAR 2000 /* Replace with this command */
DCL &NewLength *CHAR 4 /* Length of new command */
/* 0 = no new command */
DCL &JOB *CHAR 10
DCL &USR *CHAR 10
DCL &NBR *CHAR 6
MONMSG CPF0000 EXEC(GOTO ERROR)
/* découpage du paramètre reçu */
CHGVAR &ExitPoint %SST(&ExitInfo 1 20)
CHGVAR &ExitFormat %SST(&ExitInfo 21 8)
CHGVAR &CmdName %SST(&ExitInfo 29 10)
CHGVAR &CmdLib %SST(&ExitInfo 39 10)
CHGVAR &Change %SST(&ExitInfo 49 1)
CHGVAR &Prompt %SST(&ExitInfo 50 1)
CHGVAR &Filler %SST(&ExitInfo 51 2)
CHGVAR &Offset %SST(&ExitInfo 53 4)
CHGVAR &CmdLength %SST(&ExitInfo 57 4)
CHGVAR &CmdLenDec %BIN(&Cmdlength)
CHGVAR &OffsetDec (%BIN(&Offset) + 1) /* Set offset */
CHGVAR &CmdString %SST(&ExitInfo &OffsetDec &CmdLenDec)
/* Extraction du travail */
RTVJOBA JOB(&JOB) USER(&USR) NBR(&NBR)
/* envoi message à qsysopr */
SNDUSRMSG MSG(&job *tcat '/' *tcat &usr *tcat +
'/' *tcat &nbr *bcat %sst(&CmdString 1 +
100) ) MSGTYPE(*INFO) TOUSR(*SYSOPR)
/* passage de la commande sans transformation */
chgvar &NewString &CmdString
chgvar &NewLength &CmdLength
ERROR:
return
ENDPGM
Remarque :
Vous n’avez pas besoin de mettre en place des audits et tout est dynamique
Vous pouvez transformer une commande ou la remplacer par une autre
Pour voir les commandes que vous tracez
==> WRKREGINF QIBM_QCA_CHG_COMMAND
Exemple :
Les commandes qui commencent par DLT, je remplace par un message suppression refusée pour certain utilisateurs
Rappel :
Un programme d’exit doit être simple et ne pas planter !
Vous avez des application 5250 que vous avez décidé d’améliorer en les passant de 80 colonnes à 132 colonnes
c’est au niveau de votre écran que vous devez indiquer cette taille par le mot clé DSPSIZ(27 132 *DS4) .
Quand vous affichez votre écran sur une unité écran de type 3477 tout va bien mais quand vous l’affichez sur une unité écran de type 3179, vous avez une erreur d’entrée sortie.
Comment faire pour éviter ce plantage ?
Bien sur définir toutes les unités écrans en 132, mais on ne maitrise par forcément toujours cette démarche, beaucoup de systèmes étant en auto-configuration.
Voici une première solution minimaliste qui évitera le plantage, et qui enverra un message dans la log
Exemple.
dcl-f
VOTREECRAN WORKSTN
usropn ;
open(e) VOTREECRAN ;
if %error ;
// si erreur on considère que c’est la taille
dsply ‘Vous devez être en 132*27’ ;
// votre traitement ici
endif ;
c’est simple et efficace
Mais vous pouvez faire quelque chose d’un peu plus propre
En effet on peut avoir un mélange dans un dspf que vous allez créer
En indiquant les 2 mots clés dans votre source
Vous devez impérativement indiquer *DS4 en premier
A DSPSIZ(27 132 *DS4 –
A 24 80 *DS3)
La problématique est de trouver la taille de votre unité écran, pour l’instant il n’existe pas de vue SQL qui fasse un DSPDEVD, vous devrez utiliser les API fournies par IBM :
ici QDCRDEVD elle a un format DEVD0600 qui contient cette information.
le source de l’écran avec ses 2 formats
A*%%TS SD 20240308 152030 PLB REL-V7R4M0 5770-WDS
A* attention 132 doit être en premier
A* le premier fixe l'affichage maxi
A DSPSIZ(27 132 *DS4 -
A 24 80 *DS3)
A* Format à afficher en cas de taille 80
A R FMT80
A*%%TS SD 20240308 152030 PLB REL-V7R4M0 5770-WDS
A CA12(12)
A 5 11'Format de l''écran'
A DSPATR(UL)
A 6 11'Ecran 80'
A 7 2'F12=Retour'
A COLOR(BLU)
A* Format à afficher en cas de taille 132
A R FMT132
A*%%TS SD 20240308 152030 PLB REL-V7R4M0 5770-WDS
A CA12(12)
A 5 70'Format de l''écran'
A DSPATR(UL)
A 6 70'Ecran 132'
A 8 2'F12=Retour'
A COLOR(BLU)
Le source du programme
On a créé une procédure interne que vous pouvez facilement externaliser dans un programme de service par exemple, (dans notre exemple, il y a une variable globale liée à la SDS … )
**free
Ctl-Opt DFTACTGRP(*NO) ;
// Test d'une procédure pour déterminer la taille de l'écran DEVD
Dcl-f RTVDEVTYPE WORKSTN ;
// Contient 2 formats
// fmt132 de taille 132
// fmt80 de taille 80
// Information du Job
dcl-ds *N PSDS;
JOB Char(10) Pos(244);
end-ds;
// Variables globales
Dcl-S w_type Char(10);
// 3477 taille 132
// 3179 taille 80
// en interactif, le travail = nom de l'écran
//
w_type = getdevtyp('*') ;
If w_type = '3477' ;
exfmt fmt132 ;
Else;
exfmt fmt80 ;
EndIf;
*inlr = *on ;
// Récupération du type de l'écran
//
Dcl-Proc Getdevtyp Export;
Dcl-PI Getdevtyp Char(10);
Inp_Device Char(10) Const;
End-PI;
// Prototypes de la l'API QDCRDEVD récupération des attributs
// d'une unité
Dcl-PR QDCRDEVD ExtPgm('QDCRDEVD');
Rcvar Like(Rcvar);
Varlen Like(Varlen);
Format Like(Format);
Device Like(Device);
Apierr Like(Apierr);
End-PR;
Dcl-DS Apierr;
Bytprv BinDec(8:0) Pos(1) Inz(216);
Bytavl BinDec(8:0) Pos(5) Inz;
Errorid Char(7) Pos(9) Inz;
Reserved Char(1) Pos(16) Inz;
ErrorDesc Char(200) Pos(17) Inz;
End-DS;
Dcl-S device Char(10) INZ;
// Format pour unité écran
Dcl-S Format Char(8) Inz('DEVD0600');
Dcl-S Rcvar Char(5000) Inz;
Dcl-S Varlen BinDec(4:0) Inz(5000);
Dcl-S typ_dev Char(10) INZ;
// Si * on considère l'écran en cours
if Inp_Device = '*' ;
Device = JOB; // attention variable globale
else ;
Device = Inp_Device;
endif ;
// Appel API systéme
CallP QDCRDEVD(
Rcvar
:Varlen
:Format
:Device
:Apierr
);
If BytAvl = 0;
// Lecture position du type de DSP
typ_dev = %Subst(Rcvar:175:10);
EndIf;
Return typ_dev;
End-Proc Getdevtyp;
Remarque :
Il y a sans doute d’autres solutions, mais vous pouvez vous contenter de l’une des deux citées ci dessus.
Il peut y avoir quelques subtilités mais globalement ça fonctionne bien.
Pour traduire du texte dans un programme RPGLE, on peut utiliser un appel à une API de traduction (via SQL). Les principales API de traductions publiques sont DeepL API et Google Cloud Translation. Dans cet article nous utiliserons l’API de DeepL dans sa version gratuite, limitée à 500 000 caractères par mois, mais DeepL propose également d’autres offres payantes pour son API.
Documentation de l’API DeepL : https://www.deepl.com/docs-api
Introduction
Pour réaliser simplement un appel API en RPGLE, il est possible d’utiliser plusieurs méthodes SQL qui permettent de :
- Formater le corps d’une requête API en JSON
- Exécuter la requête API en POST
- Récupérer des informations dans la réponse JSON
Nous allons voir ensemble un exemple d’utilisation de ces méthodes pour créer un programme qui traduit un texte donné dans la langue choisie.
Cas d’exemple
Prenons donc le cas d’un programme simple qui récupère les paramètres suivants :
- Le texte à traduire
- Le code de la langue ciblée (‘FR’, ‘EN’, ‘ES’, ‘DE’, …)
Le programme affichera ensuite via un dsply le résultat de la traduction et le code retour HTTP de la requête.
On commence par les déclarations du programme.

On y retrouve :
- nos paramètres texte et langue_cible
- une ds qui contiendra les données retournées par la requête API (traduction et code HTTP)
- deux variables pour l’URL de la requête et le token d’authentification (qui s’obtient en créant un compte API sur DeepL)
On peut maintenant construire notre requête API en utilisant SQL.

La requête HTTP faite à l’API est exécutée via la fonction de table QSYS2.HTTP_POST_VERBOSE (qui est similaire à QSYS2.HTTP_POST, mais avec plus de détails en retour).
Elle prend en paramètres :
- l’URL d’appel de l’API
- le body de notre requête -> un objet JSON contenant le texte et la langue cible
- le header de notre requête -> un objet JSON contenant des informations supplémentaires requises comme le token d’authentification
Ici la traduction se fera avec une détection automatique de la langue source (celle du texte qu’on demande à traduire), mais on peut également ajouter le paramètre source_lang dans notre body si on veut préciser la langue source de notre texte.
On remarque l’usage des fonctions SQL JSON_OBJECT et JSON_ARRAY qui permettent de formater des données au format JSON (JSON_ARRAY pour un tableau JSON)
Les éléments que l’on récupère grâce à cette fonction sont au format JSON, on utilise donc la fonction JSON_VALUE pour les récupérer en VARCHAR dans notre ds résultat.
Dans notre cas, on s’intéresse au texte traduit et au code retour HTTP, c’est donc les valeurs translations et HTTP_STATUS_CODE qui sont extraites du JSON.
Vous pouvez obtenir plus d’informations sur la structure des requêtes API DeepL et leurs retours sur la documentation en ligne de l’API : https://www.deepl.com/docs-api
Pour finir, on affiche avec un simple dsply nos éléments de retour (dans le cas où la requête SQL a été exécutée sans erreur).

Test du programme
Lorsqu’on appelle notre programme avec les bons paramètres :

On obtient bien une traduction du texte saisi, et le code retour 200 (réussite).

Pour rappel, les listes de validation sont des objets sur IBMi, de type VLDL.


Par ligne de commande, on peut seulement créer une liste ou la supprimer.


L’utilisation classique des listes d’autorisation est la sécurisation de vos serveurs IWS par authentification basique. Celles-ci permettent l’utilisation d’un profil qui n’est pas un réel utilisateur IBM i.
La gestion, de ces listes, se passe dans navigator for i, dans le HTTPAdmin :
Onglet « Advanced »

Ce que vous pouvez faire :
- Ajouter une entrée dans la liste, si la liste de validation n’existe pas, elle sera créé pour l’occasion. A minima, il faut renseigner la liste de validation, un profil et un mot de passe
- Changer le mot de passe d’une entrée
- Supprimer une entrée
- Lister les entrées d’une liste
Ce que vous ne pouvez pas faire :
- Consulter le mot de passe en cours d’une entrée, un classique en terme de sécurité.
- Supprimer une liste de validation, il faut utiliser la commande 5250 DLTVLDL
Pour mettre en place la sécurisation d’un serveur via ces listes, il faut, toujours depuis le HTTPAdmin, au niveau de la gestion des sécurités de votre serveur HTTP*, sélectionner l’option liste de validation :

Avantage :
- Ne pas créer de réel utilisateur IBMi pour l’authentification.
- Permettre à des tiers extérieurs d’avoir un login ne pouvant servir que dans le cadre d’appel HTTP à un serveur protéger par la liste d’autorisation dont est issu le login
- Encryption de la liste au niveau OS. Pas de possibilité d’accès aux données de la liste de façon simple.
En cas d’appel depuis l’extérieur du réseau de confiance, ça semble une bonne option.
Inconvénient :
- Il faut connaître en amont le client qui va se connecter, et donc avoir une gestion de demande /création de compte
- L’interface de gestion n’est pas compatible avec un grand nombre d’entrées dans la liste. Dans ce cas il faudra, soit trouver une autre solution pour sécuriser son serveur, soit utiliser les API misent à disposition par IBM
* Sur les versions récentes, la sécurité peut aussi être gérée au niveau du serveur applicatif. A vous de voir, si vous voulez un duo de serveurs HTTP/applicatif ou seulement un serveur applicatif, mais c’est un autre sujet…
Les API de gestion des listes de validation
La documentation officielle :
https://www.ibm.com/docs/fr/i/7.5?topic=ssw_ibm_i_75/apis/sec6.html
IBM nous fournit des API pour gérer les listes de validation. On retrouve les actions possibles dans Navigator for i…. Et d’autres !
En regardant de plus près ces API, on constate sur la création d’une entrée de la présence d’un attribut permettant ou non de décrypter un mot de passe :

Navigator for i utilisant les valeurs par défaut, lorsqu’on crée une entrée par ce biais, le mot de passe n’est pas décryptable.
Par contre, si on crée une entrée par l’API correspondante, avec cet attribut positionné à QSY_VFY_FIND (1), on peut par la suite récupérer le mot de passe via l’API C QsyFindValidationLstEntry() ou son équivalent QSYFDVLE
Prenons des exemples :
Je crée dans une liste de validation, dédié à l’article, DTFORM/DEMOBLOG, un profil MdpnonVIsible avec l’attribut de décryptage à ‘0’, et un profil MdpVisible avec l’attribut de décryptage à ‘1’.
Première remarque : l’appel d’api d’ajout d’une entrée dans une liste de validation renvoie un erreur si la liste n’existe pas. Il faut la créer au préalable par la commande CRTVLDL.


En regardant dans Navigator for i, les deux entrées apparaissent sans distinction :
Lors du décryptage, si on tente un appel de l’API find avec une erreur, mauvais nom de liste, profil inexistant, …, le retour est en erreur, comme pour toutes les API : -1. On peut récupérer le message détaillé de l’erreur, on reste sur de la gestion standard :


Si on lance l’API Find avec pour le profil MdpnonVisible :

L’API renvoie un code retour ok, mais pas de mot de passe, normal, il n’est pas décryptable.

Avec le profil décryptable, on récupère bien le mot de passe initial :


Par cette méthode, vous pouvez donc récupérer des mots de passe stockés dans une liste de validation, à la condition que l’entrée ait été créée avec le top de décryptage à ‘1‘.
Pour compléter la sécurité sur le décryptage des mots de passe, vous pouvez mettre :
- Sur la liste de validation
- Un profil technique comme propriétaire
- Aucun droit sur aucun autre profil .
- Avoir un programme dédié au décryptage avec :
- Comme propriétaire le même que celui de la liste de validation
- Compilé pour faire de l’adoption de droit.
- Et si on veut aller plus loin, en cas de debug possible en prod, protéger les sources, du programme de décryptage et ses appelants, par mot de passe.
Bien entendu cette stratégie n’est valable que si la gestion des droits utilisateurs est rigoureuse… Pas de *allobj sur les profils par exemple !
Conclusion :
Vous pouvez utiliser les listes de validation pour stocker des profils/mot de passe, sans les stocker en clair sur la machine. Mais on peut très bien imaginer utiliser ces listes pour stocker toutes les données sensibles permettant les échanges inter-applications ou autre :
- URL d’invocation de WS
- IP ou nom DNS pour FTP / SFTP
- …
Et pour cela de se créer une liste par usage, liste pour URL, liste pour IP/DNS, …, de mettre dans le profil, un code application, et dans le mot de passe la valeur que l’on veut récupérer, avec la limitation de 600 caractères pour le mot de passe, à part pour des URL très spécifique, ça ne devrait pas être limitatif.
Les listes de validation restent des objets très peu connu, mais qui mérite de l’être !
Depuis la V7R1 (SF99701 – DB2 – niveau 23), on peut invoquer des web service via SQL. Les fonctions se trouvent dans SYSTOOLS.
En V7R4 TR5, sont sorties de nouvelles fonctions, elles se trouvent dans QSYS2.
Outre les fonctions HTTP, celles pour encoder / décoder en base64 et pour encoder / décoder L’URL, ont aussi été implémentées dans QSYS2.
Rappel des différences entre ces fonctions
Tout d’abord les performances. Les fonctions de QSYS2 permettent un gain non négligeable, elles sont basé sur les fonctions AXIS en C natif, contrairement à celles de SYSTOOLS qui sont basées sur des classes java.
Les paramètres dans l’entête ou le corps du message sont transmis en JSON pour les fonctions de QSYS2, à la place de XML pour celle de SYSTOOLS.
La gestion des certificats est simplifiée par l’utilisation de DCM, alors qu’avec les fonctions de SYSTOOLS, il fallait pousser le certificat dans le magasin du runtime java utilisé par les fonctions HTTP. En cas de multiple versions de java installées, il fallait s’assurer de laquelle servait pour les fonctions HTTP. L’ajout du certificat, se faisait via des commandes shell.
Les types et tailles des paramètres des fonctions ont été adaptés pour ne plus être des facteurs limitants de l’utilisation des fonctions SQL, voici quelques exemples :



Certaines utilisations ont aussi été simplifiées en automatisant des tâches.
Prenons l’exemple d’un appel à un web service avec une authentification basique. Le couple profil / mot de passe doit être séparé par « : » et l’ensemble encoder en base64. C’est la norme HTTP.
Dans le cas des fonctions de SYSTOOLS, il fallait effectuer l’ensemble des opérations, alors qu’avec les fonctions de QSYS2, il suffit de passer le profil et le mot de passe dans la propriété BasicAuth. La mise en forme et l’encodage étant faits directement par les fonctions AXIS :

Il y a par contre un cas limitatif des fonctions QSYS2, que IBM a rajouté, alors que la norme HTTP autorise ce type d’appel.
Il s’agit d’avoir une authentification basique sur un appel en http.
Ce cas n’est pas trop contraignant, aujourd’hui le https est la norme et le http quasiment disparu…. quasiment !
Nous rencontrons encore chez nos clients des web services « interne » en http. La migration en https n’étant pas vendeur auprès des directions qui n’y voit aucun gain pour le métier. C’est l’éternel problème des changements structurels en IT.
Dans ces cas, la fonction de QSYS2, renverra une erreur, assez claire !

Le premier réflexe est de voir avec le fournisseur du service s’il ne dispose pas d’une version en https.
Maintenant, si vous n’avez pas d’autre choix que d’appeler un web service en http avec authentification basique, il faudra continuer d’utiliser les fonctions de SYSTOOLS. Dans tous les autres cas, aucune hésitation, utilisez les fonctions de QSYS2.
Mais mettons nous d’accord, de l’authentification basique en http, ce n’est pas de la sécurité, c’est une absurdité.
En http, le message passe en clair sur la trame réseau, avec votre profil / mot de passe, encodé en base 64, et non encrypté, donc en clair eux aussi.
Edit : Précision apportée par Gautier Dumas de CFD-innovation. Merci à lui.
On peut contourner le problème avec les fonctions de QSYS2. Il ne faut pas utiliser la propriété BASICAUTH, mais construire l’authentification basique comme on le faisait avec celle de SYSTOOLS.
VALUES QSYS2.HTTP_GET(
‘http://hostname/wscommon/api/contacts’,
‘{« header »: »Authorization, BASIC dGVzdHVzZXI6dGVzdHB3ZA== »}’);
Il n’y a donc vraiment plus de raison de continuer avec les fonctions de SYSTOOLS !
C’est une solution à base d’UIM qui permet de gérer des sous fichiers sans écran DDS, en utilisant un panel de groupe et des API pour le manipuler.
Toutes les commandes WRKXXX de votre IBMi sont codées avec cette solution.
Nous allons vous présenter un exemple pour les PF par exemple vous trouverez le code sur github ici https://github.com/Plberthoin/PLB/tree/master/WRKPF
Cet article est inspiré à l’origine d’un article de mcpressonline, j’ai remis le RPG à jour
Vous devrez avoir un PNLGRP avec la structure suivante, pour indiquer les listes
:PNLGRP.
:CLASS.
:ECLASS.
:VAR.
:VARRCD.
:LISTDEF.
:KEYL.
:KEYI.
:EKEYL.
:PANEL.
.
.
:LIST.
:LISTACT.
:LISTCOL.
:LISTVIEW.
.
.
.
:ELIST.
.
:CMDLINE.
.
:EPANEL.
.
.
.
:HELP.
.
.
.
:EHELP.
:EPNLGRP.
Vous aurez un programme ici en RPG qui va utiliser les API suivantes :
Api Utilisation
QUIOPNDA Ouverture du panel création du Handle
QUIPUTV Renseignement variable
QUIADDLE Ecriture d’un poste dans la liste
QUIDSPP Affichage du panel
QUIDLTL Suppression du contenu de la liste
QUICLOA Fermeture du panel
Rappel, vous pouvez chercher les APIs disponibles sur votre IBMi avec API-FINDER disponible
ici https://www.ibm.com/docs/en/i/7.5?topic=interfaces-api-finder
Conclusion
C’est une solution standard qui ne nécessite pas de DSPF, mais qui nécessite de connaitre un peu le langage UIM.
Avec un squelette de PNL et de programme on peut créer des outils WRKXXX rapidement, idéal pour les outils d’administration par exemple
Préambule
Cet article est une suite à l’article de Pierre-Louis BERTHOIN qui présente les fonctions géospatiales intégrées à DB2.
Choix de l’API
Sur le site https://adresse.data.gouv.fr/ En cliquant sur l’item « Outils et API », on accède librement à la documentation des API en rapport avec les adresses. Nous choisissons donc celle sobrement intitulée « API Adresse ». La documentation montre différentes manières d’utiliser cette API. Le retour est un geojson FeatureCollection respectant la spec GeoCodeJSON.
Récupération et manipulation des données
But du programme
Nous allons réaliser un programme qui permettra, en écrivant partiellement une adresse, de récupérer une adresse complète d’après une liste déroulante de 50 occurrences.
Nous choisirons pour notre programme une interrogation relativement simple et nous n’extrairons qu’une partie des données du geojson.
Nous écrirons les adresses dans un fichier, sous forme d’une fiche client contenant les éléments suivants :
- Identifiant (integer auto incrémenté)
- Raison Sociale (varchar)
- Adresse (varchar)
- Code Postal (varchar)
- Ville (varchar)
- Coordonnées géographiques (ST_POINT)
Préparation du fichier
create table GGEOLOC.MESCLIENTS (ID int GENERATED ALWAYS AS IDENTITY ( START WITH 1 INCREMENT BY 1 NO MINVALUE NO MAXVALUE NO CYCLE NO ORDER CACHE 20 ), RAISOC varchar(64), ADRESSE varchar(128), CODEPOS varchar(16), VILLE varchar(64), COORDGEO QSYS2.ST_POINT);
Programme de saisie
**free ctl-opt dftactgrp(*no) ; // Fichiers dcl-f FORMCLIE workstn indds(DS_Ind) usropn; // Procédures dcl-pr Touche_F4 EXTPGM('TOUCHE_F4'); p_Sql char(1024) ; p_Titre char(35); p_Ret char(116); end-pr; // Variables dcl-s reqSqlDelete varchar(2000); dcl-s reqSqlCreate varchar(2000); dcl-s reqSqlDrop varchar(2000); dcl-s coordonees varchar(64) ; dcl-s queryapi varchar(64); dcl-s httpText varchar(256); dcl-s pIndicators Pointer Inz(%Addr(*In)); // Pour F4 : liste des fichiers dcl-s w_Sql char(1024) ; dcl-s w_Titre char(35); dcl-s w_Ret char(116); // DS Informations Programme dcl-ds *N PSDS; nom_du_pgm CHAR(10) POS(1); nom_du_prf CHAR(10) POS(358); end-ds; // Déclaration des indicateurs de l'écran Dcl-DS DS_Ind based(pIndicators); Ind_Sortie ind pos(3); Ind_Liste ind pos(4); Ind_Annuler ind pos(12); Ind_Valider ind pos(17); Ind_SFLCLR ind pos(40); Ind_SFLDSP ind pos(41); Ind_SFLDSPCTL ind pos(42); Ind_SFLEnd ind pos(43); Ind_DisplayCoord ind pos(80); Ind_RaisonS ind pos(81); Ind_5Lettres ind pos(82); Ind_CodePos ind pos(84); Ind_Ville ind pos(85); Indicators char(99) pos(1); End-DS; // Paramètres en entrée dcl-pi *N; end-pi; // SQL options --------------------------------------------- // Exec SQL Set Option Naming=*Sys, Commit=*None, UsrPrf=*User, DynUsrPrf=*User, Datfmt=*iso, CloSqlCsr=*EndMod; //--------------------------------------------------------- // // Contrôle taille écran Monitor; Open FORMCLIE; On-Error; Dsply 'Nécessite un écran 27 * 132'; *inlr=*on; Return; EndMon; Dou Ind_Sortie or Ind_Annuler; znompgm = nom_du_pgm; znomprf = nom_du_prf; Exfmt FMT01; select ; when Ind_Sortie ; leave; when Ind_Annuler; leave; when Ind_Liste; if %len(%trim(zadresse)) <= 4 ; Ind_5Lettres = *on; iter; endif; traitementListe(); when Ind_Valider; if zraisoc = *blanks; Ind_RaisonS = *on ; endif; if zcodpos = *blanks; Ind_CodePos= *on ; endif; if zville = *blanks; Ind_Ville = *on ; endif; if %subst(indicators:81:4) <> '0000'; iter ; endif; Exec SQL insert into MESCLIENTS (RAISOC, ADRESSE, CODEPOS, VILLE, COORDGEO) values (:zraisoc, :zadresse, :zcodpos, :zville, QSYS2.ST_POINT(:zlongit, :zlatit)) ; Ind_DisplayCoord = *off; clear FMT01; endsl ; Enddo; *inlr = *on; //======================================================================== // // Procédures // //======================================================================== // //------------------------------------------------------------------------ // // Nom : rechercheAdresse // // But : lister des adresses recueillies via une API // // à partir d'une chaine de plus de 4 caractères // // Retour : N/A // //------------------------------------------------------------------------ // dcl-proc rechercheAdresse ; dcl-pi *n ; l_httpText varchar(256) value; end-pi; reqSqlDrop = 'drop table QTEMP/WADRESSE' ; Exec sql Execute immediate :reqSqlDrop ; reqSqlCreate = 'create table QTEMP/WADRESSE' + ' (address varchar(128), numero varchar(8), street varchar(128), ' + 'postcode varchar(16), city varchar(64), coordinates blob)' ; Exec sql Execute immediate :reqSqlCreate ; reqSqlDelete = 'delete from QTEMP/WADRESSE' ; Exec sql Execute immediate :reqSqlDelete ; Exec sql insert into QTEMP/WADRESSE (select ltrim(ifnull(numero, '') || ' ' || coalesce(street, locality, '') || ' ' || postcode || ' ' || city), ifnull(numero, ''), coalesce(street, locality, ''), postcode, city, QSYS2.ST_POINT(longitude, latitude) from json_table(QSYS2.HTTP_GET(:l_httpText, ''), '$.features[*]' COLUMNS (numero varchar(8) PATH '$.properties.housenumber', street varchar(128) PATH '$.properties.street', locality varchar(128) PATH '$.properties.locality', name varchar(128) PATH '$.properties.name', municipality varchar(128) PATH '$.properties.municipality', postcode varchar(8) PATH '$.properties.postcode', city varchar(64) PATH '$.properties.city', longitude float PATH '$.geometry.coordinates[0]', latitude float PATH '$.geometry.coordinates[1]')) ); end-proc ; //------------------------------------------------------------------------ // // Nom : traitementListe // // But : Affichage d'une liste de 50 adresses maximum // // Retour : N/A // //------------------------------------------------------------------------ // dcl-proc traitementListe ; queryapi = %scanrpl(' ':'+':%trim(zadresse)) ; httpText ='https://api-adresse.data.gouv.fr/search/?q=' + queryapi + '&limit=50' ; rechercheAdresse(httpText); clear w_ret ; w_sql = 'select address from QTEMP/WADRESSE' ; w_titre = 'Adresses proposées'; touche_f4(W_Sql: W_titre : w_ret) ; if (w_ret <> ' ') ; clear zadresse; clear zcodpos; clear zville; Exec SQL select ltrim(ifnull(numero, '') || ' ' || street), postcode, city, REPLACE( REPLACE(QSYS2.ST_asText(COORDINATES), 'POINT (', ''), ')', '') into :zadresse, :zcodpos, :zville, :coordonees from QTEMP.WADRESSE where address = :w_ret ; if sqlcode = 0 ; Ind_DisplayCoord = *on ; zlongit = %dec(%subst(coordonees: 1 : %scan(' ':coordonees)):15:12) ; zlatit = %dec(%subst(coordonees: %scan(' ':coordonees) + 1 : %len(%trim(coordonees)) - %scan(' ':coordonees)):15:12) ; endif; endif ; end-proc ;
Quelques explications sur les fonctions SQL utilisées
Tout d’abord nous choisissons de ne pas utiliser la propriété « label » proposée par l’API Adresse. En effet, si celle-ci semble pratique de prime abord, elle n’est pas toujours significative (voir photo du milieu qui où elle ne contient que le nom de la municipalité)

Nous, préférerons donc reconstituer cette adresse en concaténant des zones que l’on retrouve dans chaque occurrence du fichier JSON.
QSYS2.ST_POINT : Cette fonction est utilisée lors de la collecte des données fournie par l’API Adresse.
Elle permet de transformer les coordonnées longitude, latitude en une variable de type BLOB qui représente un point précis et qui est utilisable par les fonctions Géospatiales du SQL.
QSYS2.ST_ASTEXT : Cette fonction permet de transformer un champ géométrique (ST_POINT, ST_LINESTRING, ST_POLYGON, …) en un champ WKT (well-known-text) qui nous sera plus compréhensible.
Cinématique du programme
Ce programme est un simple écran qui nous permet la saisie d’un formulaire avec la possibilité de rechercher ou compléter une adresse en utilisant la touche F4 (la fonction externe appelée n’est pas décrite dans cet article). Une fois le formulaire validé, on l’efface.
Tout d’abord on commence à remplir le formulaire mais on ne connait pas précisément l’adresse.

Donc, après avoir tapé un morceau d’adresse on presse F4

On valide, le formulaire est alors complétement rempli

On presse F17 pour valider celui-ci (et réinitialiser l’écran).
Vérification des données enregistrées
Version BLOB

Version WKT (well-known-text)

Conclusion
Nous avons montré ici un exemple simple de l’utilisation d’une API couplée avec les fonctions géospatiales proposées par IBM. Il est possible d’envisager des requêtes plus complexes incluant le code postal, la ville ou encore le type de donnée (rue, lieu-dit ou municipalité). On peut aussi envisager des requêtes d’après les coordonnées géographiques pour retrouver une adresse. Le champ des possibles, comme le monde, est vaste …
Dans la dernière TR est apparue une nouveauté très intéressante « API RSE », c’est un ensemble d’API REST fournies avec votre système d’exploitation au travers du serveur ADMIN5.
Ces APIs sont utilisables depuis le web et permettent de lancer des commandes, accéder à l’IFS, de lancer de requêtes SQL…
Ce service ne fonctionne qu’en TLS vous devrez donc le sécuriser par l’administration http ://Votre_systeme:2001/HTTPAdmin
Vous devez ensuite le démarrer s’il ne l’est pas c’est le serveur ADMIN5
==>STRTCPSVR SERVER(*IAS) INSTANCE(admin5)
Ce serveur tourne par défaut sur le port 2012
https://Votre_systeme:2012/openapi/ui/
Vous devrez vous authentifiez pour commencer utilisez un user et un mot de passe ibmi, vous cliquez sur authorize
Nous allons tester une commnde CL
Vous cliquez sur try input
Vous avez un flux json avec vos commandes IBMI à l’intérieur vous cliquez sur execute
Vous avez alors le résultat
A noter que vous avez le lien, en haut pour l’intégrer dans vos applications
Conclusion
C’est gratuit c’est technologies actuelles, testez les possibilités de ce service
Pour en savoir plus
la pause café de volubis ici : https://www.volubis.fr/Pausecaf/PAUSECAF91.html
le site IBM : https://www.ibm.com/support/pages/node/6982701