Cegid XRP Ultimate  |  
I3   Actualisé le 06/10/2022
Fondations
Utilisation des services REST

Procédure de connexion aux WebServices
   Swagger
   Principe de connexion
Les options
Exemple en mode "cookie"
Exemple en mode "header"
A quoi sert l'accessToken ?
A quoi sert le refreshToken ?
Demande de renouvellement de l'accessToken
Codes retour
Configuration
Comment passer le jeton accessToken lors des appels "utiles" ?
   Qu'est-ce qui est retourné ?
Les codes de statuts possibles
Les codes http possibles
   Verbes http, path, noms des services
   Généralités sur les recherches
Critères complexes
Restreindre le nombre de colonnes ramenées
   Types numériques
   Généralités sur les insertions et mises à jour
Identifications des enregistrements
Retour des enregistrements
   Valeurs nulles
   Service de lancement d'un traitement
Mode "fire and forget"
Mode "wait for result"
Retour
   Récupération des informations des jobs (CJOB/GTCJCR)
   Récupération du fichier résultat d'un job
   GED
Recherche
Accès
Ecriture
Suppression
WebServices "LEGACY"
Exécuter une requête
Appeler une procédure

Procédure de connexion aux WebServices

   Swagger
   Avec l'installation des services REST vient l'installation de Swagger, un outil (http://swagger.io/) qui permet de visualiser les méthodes exposées, de connaître les noms, formats, structures, description des paramètres attendus par chacune de ces méthodes.
Swagger permet de réaliser de façon assez simple le test de ces services.

   Cet outil sera largement utilisé et commenté dans cette documentation.

   Principe de connexion

   On utilise un système de connexion basé sur des notions d'accessToken et de refreshToken.
Le principe est le suivant :
- Lors de la première utilisation, il faut s'authentifier en passant un couple utilisateur/mot de passe. En retour, deux jetons sont fournis, un accessToken et un refreshToken. La durée de vie de l'accessToken est aussi retournée.
- Lors de chaque demande de service, il faut impérativement passer l'accessToken.
- Si l'accessToken est périmé, il faut faire une demande de renouvellement. Pour cela, il faut refaire une demande d'authentification, mais en passant le refreshToken.
- Si le refreshToken est périmé, il faut refaire une demande d'authentification en passant de nouveau le couple utilisateur/mot de passe.

   Coté Client, il faut donc stocker le refreshToken et l'accessToken.

   Les options
Voici les paramètres à passer lors d'une demande d'authentification :

{
     "login": "",
     "password": "",
     "target": ""
}

"login" et "password" se passent de commentaire.

"target" peut prendre trois valeurs :
- "cookie" : le token sera retourné dans un cookie "com.qualiac.auth.accessToken". Ce mode est principalement utilisé pour les tests, et notamment avec Swagger.
- "header": le token sera retourné dans le header http (de nom = "com.qualiac.auth. accesstoken"). Idem pour le refreshtoken qui lui va se retrouver dans com.qualiac.auth.refreshtoken. La durée de vie de l'accessToken sera dans com.qualiac.auth.lifetime.
- "value" : le token sera retourné en tant que JSON dans le payload http. (de nom = "com.qualiac.auth.accessToken").

Dans les trois cas, on retrouve ces informations dans le body de la réponse, avec en complément la durée de vie en millisecondes de l'accessToken.

Attention, coté Client, il est conseillé de ne pas être case sensitive, notamment dans le cas des header qui sont parfois transformés en minuscule en traversant les différentes couches de transport.

   Exemple en mode "cookie"



   Exemple en mode "header"

   A quoi sert l'accessToken ?
Le but de ce jeton est double. Une fonction de contrôle, et une fonction de persistance de données.
Il doit être passé, d'une façon ou d'une autre, lors de chaque appel de services "utiles". La première chose que fait le serveur est de vérifier si ce jeton est bien présent, s'il n'est pas périmé et si sa signature correspond au contenu (le moindre changement dans les données "casse" la signature). Si ces trois conditions sont vérifiées, on considère que la demande est "fiable" et on autorise donc la suite des instructions.

   A quoi sert le refreshToken ?
Le principe retenu dans notre philosophie de sécurité est d'utiliser la notion de jeton d'accès (accessToken) à durée de vie limitée (assez courte) et de jeton de "refresh" (durée de vie bien plus longue).
Les principales notions sont les suivantes :

- S'authentifier une première fois avec le couple user/mot de passe. Si possible, ne jamais stocker le mot de passe coté Client.

- Récupérer les deux types de jetons (access et refresh). Stocker ces deux jetons côté Client. De façon assez volatile pour l'accessToken : cookie, localstorage, mémoire, ..., et de façon plus persistante : type base embarquée ou "coffre fort" de l'OS pour le refreshToken.

- Passer systématiquement l'accessToken lors de chaque appel utile. Ce type de jeton étant moins critique (durée de vie courte, données peu sensibles embarquées, modification impossible sans connaître la clef de signature, etc.). Même s'il est intercepté cela ne sera pas forcement une faille.

- Ne jamais passer le refreshToken , sauf si l'accessToken a expiré (ou va expirer dans peu de temps). Dans le cas, d'un accessToken périmé, le serveur renvoie une erreur de type "accessToken expired", et le client doit faire une demande de renouvellement d'accessToken en utilisant le refreshToken. Pour cette raison, il ne faut pas stocker le refreshToken dans un cookie, sinon, il serait repassé à chaque appel. Il peut exister deux façons pour repérer que l'accessToken a expiré :
     . "attendre" qu'une erreur http 401 avec un header "com.qualiac.errorcause" ayant pour valeur "AUTH-002" soit retournée ;
     . Et/ou   mettre en place coté Client un système qui prédit la fin de vie de l'accessToken en se basant sur la propriété "lifeTime" retournée lors de l'authentification. Par exemple, si on a un accessToken qui est valide pendant 30 minutes, que l'authentification s'est faite à 10h15 et qu'on approche de 10h45, on demande le renouvellement de l'accessToken sans attendre l'erreur 401.

   Demande de renouvellement de l'accessToken
Dans tous les cas, pour demander le renouvellement de l'accessToken, il faut utiliser le refreshToken (celui qui a été stocké lors de la première connexion réussie). Pour cela, il faut simplement le passer dans la propriété "refreshToken" de notre service d'authentification.
Attention, si le refreshToken est passé, il faut que le champ "password" soit vide, et inversement, sinon erreur http 400 avec header = AUTH-005.

Dans le cas où l'accesToken est périmé (ou va périmer dans peu de temps), il suffit de refaire un appel au service security/authentication, en passant :

{
     "login": "[code utilisateur]",
     "refreshToken": "le refreshToken stocké précédemment",
     "target": "header ou cookie ou value"
}



Ou



En effet, par défaut, lors d'un renouvellement d'un accessToken, le refreshToken n'est pas renvoyé, sauf si on positionne une option. Si on active cette option, un nouveau refreshToken (donc avec une durée de vie réinitialisée) sera retourné, ce qui veut dire qu'en théorie le refreshToken aura peu de chances de se retrouver obsolète. D'un autre côté, cela peut s'avérer moins sécurisé. Ce choix de configuration vous appartient.

   Codes retour
Si problème d'authentification, le serveur retourne une erreur http de type 401 (authentification requise). Le détail du problème (code erreur) se trouve dans le header com.qualiac.errorcause.



Les erreurs possibles

AUTH-001      Mauvais couple user/mot de passe
AUTH-002      AccessToken expiré
AUTH-003      AccessToken invalide
AUTH-004      RefreshToken invalide
AUTH-005      Les deux paramètres password et refreshToken sont renseignés. Il n'en faut qu'un seul (dans ce cas, c'est l'erreur http 400 qui est retournée)
AUTH-006      RefreshToken expiré
AUTH-007      Token not found
AUTH-008      Erreur LDAP

   Configuration
Un fichier properties (dont le nom est configurable dans la configuration du serveur Web), peut permettre de positionner, en plus des configurations de base de données, de pool et de LDAP, certaines autres options.


   Comment passer le jeton accessToken lors des appels "utiles" ?
Nous avons vu que le service d'authentification retournait diverses informations, sous différentes formes possibles (cookie, header, values).
Si, lors de l'authentification, le mode choisi est "cookie", alors sur l'appel d'un service "utile", le serveur vérifiera que le cookie "com.qualiac.auth.accessToken" est présent et contrôlera sa validité et sa signature.
Si, lors de l'authentification, le mode choisi est "header" ou "value", alors sur l'appel d'un service "utile", le serveur vérifiera que le header http custom "com.qualiac.auth.accesstoken" (tout en minuscule) est présent et contrôlera sa validité et sa signature. Il n'est donc pas possible de passer en mode header un token issus d'une demande en mode cookie, et inversement.
Il n'y a pas la possibilité de passer directement l'accessToken dans le corps de la requête.

   Qu'est-ce qui est retourné ?

   Tous les WebServices fonctionnels retournent un objet de type SvcResponse.
Ce dernier contient les propriétés suivantes :
- status : donne un statut fonctionnel de l'appel ;
- errorsCodeList : liste des codes erreurs fonctionnelles rencontrées ;
- errorsTxtList : liste des messages d'erreurs ;
- errorCause : exception détectée ;
- stackTrace : trace complète de l'exception ;
- dpc : signale si la fonction utilisée est dépréciée ;
- payload : objet générique, donc la description doit être documentée pour chaque WebService.

   Les codes de statuts possibles
0, le service s'est bien passé, sans aucun warning
1, le service s'est bien passé, avec des warnings. Dans ce cas, les warning seront dans les listes (errorcodeList, ...)
4, l'enregistrement passé en entrée n'a pas été traité par le serveur

-1, le service s'est techniquement bien passé, mais avec au moins une erreur fonctionnelle
-2, authentification refusée
-3, le service s'est techniquement mal passé (Exception, problème base de données, ...)
-5, timeout (pour l'instant, uniquement géré sur l'action runjob dans certaines conditions)

   Les codes http possibles
Les codes http concernent les retours d'un appel qui est techniquement allé jusqu'à son terme.

OK (200) : ok
CREATED (201) : insertion effectuée
NO_CONTENT(204) : suppression effectuée
BAD_REQUEST(400) : objet fourni en entrée incorrect, demande incohérente
UNAUTHORIZED (401) : problème authentification
NOT_FOUND (200) AVEC PAYLOAD VIDE : ressource non trouvée (le select ne ramène rien)
CONFLICT (409) : typiquement "enregistrement modifié par un autre utilisateur"

A noter que les codes d'erreur http peuvent être configurés dans le fichier properties, particulièrement si on veut surcharger le code 404 (code sur lequel différentes approches sont possibles, et notamment le remplacer par un code 204).

En cas de problème technique non prévu coté serveur, ce sera une erreur http 500 qui sera retournée. Par définition, ce type d'erreur ne devrait pas arriver et il est donc difficile de donner la liste des causes possibles.

En cas d'erreur qui a pu être trappée coté serveur, ce sera principalement le code 400 qui sera remonté. Le détail sera fourni via le "status", les propriétés errorCause.


NOT_FOUND (404) ou OK (200) avec un payload vide ?
Nous avons choisi de retourner un OK avec un payload vide. A chacun de tester le payload. Aucun de nos services ne retournera une erreur 404. Si vous rencontrez cette erreur, c'est que l'URL sera incorrecte.

   Verbes http, path, noms des services

   On n'utilise pas de verbe dans le nom du WebService. C'est la méthode http qui va piloter la fonction.
Le nom du service = nom de la ressource gérée, toujours au pluriel, par exemple purchaseOrders.

   
Fonction "CRUD" Path Verbe http
Insertion pure purchaseOrders POST
Modification pure purchaseOrders PUT
Suppression pure purchaseOrders DELETE
Suppression pure avec un index composé d'un seul champ purchaseOrders/{internalNumber} DELETE
Suppression pure avec un index composé de quelques champs purchaseOrders/indexedBy?transaction=GTMES&order=10 DELETE
Recherche simple (via un critère simple) purchaseOrders/{internalNumber} GET
Recherche simple (via quelques critères simples) purchaseOrders/query?entity=IFR&purchaseClass=HANS GET
Recherche en mode "block query"
Possibilité de passer des critères sur tout ou partie des champs (l'équivalent du mode recherche dans l'interface utilisateur)
purchaseOrders/search POST
Autre (mélanges des actions ci-dessus, autres, etc.) purchaseOrders/manage POST


   A noter :
On ne proposera pas plusieurs méthodes de delete, une du type purchaseOrders/{transaction} et une autre du type purchaseOrders/indexedBy ...
Dans ce cas, on passera plutôt par la seconde. Même principe pour les méthodes de recherche (GET).

   Certaines parties clientes n'ont que deux verbes dans leur vocabulaire: GET et POST. Dans ce cas, il leur faudra utiliser un en-tête http normalisé pour préciser le verbe : X-HTTP-Method-Override.
Par exemple, pour une action DELETE, appel du service avec une méthode POST mais passer X-HTTP-Method-Override=DELETE.

   Généralités sur les recherches

   Critères complexes
Dans le cas de recherche de type .../search, chaque champ peut recevoir un critère, et il peut être "complexe", c'est-à-dire contenir un "%" pour faire du "like", des &&, des >> (Cf. mode recherche dans l'interface utilisateur).

   Restreindre le nombre de colonnes ramenées
Par défaut, c'est l'ensemble des colonnes qui sera ramené. Pour éviter d'encombrer le réseau, si on ne souhaite que quelques colonnes, il est généralement possible, lors de l'appel, de préciser quelques colonnes en retour.
Pour cela, il faut utiliser la propriété "fieldList", dans laquelle on va donner le tableau des champs souhaités.

Exemple
{
     "entityName": "AccountingAccounts",
     "account": "606100&&606200",
     "entity": "IND",
     "fieldsList": ["entity", "account", "accountFamily","description", "status"]
}

   Types numériques

   Les numériques sont gérés de façon légèrement différente et ne sont pas affectables directement.
Un numérique de type "Long" possèdera deux propriétés "searchCriteria" et "longValue".
Un numérique de type "BigDecimal" possèdera deux propriétés "searchCriteria" et "decimalValue".
La zone "searchCriteria " peut recevoir de l'alphanumérique et est utilisée pour passer des critères de recherche (par exemple ">1234").
Les zones "longValue" et "decimalValue" ne reçoivent que du type numérique et sont utilisées pour affecter des valeurs.

   Généralités sur les insertions et mises à jour

   Identifications des enregistrements
Lors de l'envoi des données, la partie cliente peut utiliser une propriété "recordIndex", qui est une zone libre disponible sur chaque objet de type enregistrement.
La valeur de cette zone n'est jamais modifiée par le serveur (sauf indication contraire dans la documentation du service).
Lors du retour du service, la partie cliente pourra donc assez simplement s'y retrouver et éventuellement faire le mapping entre les valeurs envoyées et celles retournées.

   Retour des enregistrements
Sauf indication contraire dans la documentation fonctionnelle du service, quand un enregistrement est envoyé, il sera aussi retourné, mais seules les valeurs modifiées par le service seront alimentées.
Les autres zones seront absentes du JSON renvoyé.

   Valeurs nulles

   Afin d'éviter d'être trop verbeux, les propriétés, dont les valeurs seront nulles, seront systématiquement ignorées et ne seront donc pas retournées.

   Service de lancement d'un traitement

   Pour lancer un traitement, le service à utiliser est (POST) /api/foundations/jobs.

   Le JSON en entrée est basé sur le modèle suivant :

   {
     "mnemonic": "string",                      // mnémonique du job à lancer
     "waitForResult": false                     // mode (attente du résultat ou non)
     "maxTimeWaitInSeconds": 0,         // attente maximale
     "transactionSequence": "string",   // numéro de séquence dans un enchaînement
     "execType": "string",                     // type d'exécution (C : différée, E: immédiat)
     "JobParameters": [                       // tableau de critères
          {
               "parameterName": "string",
               "parameterValue": "string"
          }
      ]
}

   Ce service permet de lancer un traitement batch, et propose deux modes de fonctionnement.

   Mode "fire and forget"
Dans ce mode, le service lance le job qui lui retourne immédiatement toutes les informations connues du job au moment du lancement (toutes les informations présentes dans la consultation des travaux et dans le compte rendu des travaux).
C'est le mode par défaut.

Exemple, lancement de l'édition des journaux (EJRN) :

{
     "mnemonic": "EJRN",
     "execType": "E",
     "JobParameters": [
          {
               "parameterName": "DATECPTD",
               "parameterValue": "20170101"
          },
          {
               "parameterName": "DATECPTF",
               "parameterValue": "20170101"
          },
          {
               "parameterName": "SECNUMD",
               "parameterValue": "."
          },
          {
               "parameterName": "SECNUMF",
               "parameterValue": "ZZZZZZ"
          }
      ]
}

   Mode "wait for result"
Dans ce mode, le service lance le job, attend (pendant un délai maximum défini dans l'appel) que celui-ci se termine, puis le job lui retourne toutes les informations connues du job au moment du lancement (toutes les informations présentes dans la consultation des travaux et dans le compte rendu des travaux).

Exemple, lancement de l'édition des journaux (EJRN) :

{
     "mnemonic": "EJRN",
     "execType": "E",
     "waitForResult": true,
     "maxTimeWaitInSeconds": 120,
     "JobParameters": [
          {
               "parameterName": "DATECPTD",
               "parameterValue": "20170101"
          },
          {
               "parameterName": "DATECPTF",
               "parameterValue": "20170101"
          },
          {
               "parameterName": "SECNUMD",
               "parameterValue": "."
          },
          {
               "parameterName": "SECNUMF",
               "parameterValue": "ZZZZZZ"
          }
      ]
}

   Retour
En retour, on obtient un objet de type SvcResponse dont le payload contient :
- Toutes les propriétés non nulles du job (CJOB) ;
- Dans la propriété "children", on retrouve un tableau d'objets contenant toutes les propriétés non nulles des comptes rendus des travaux (GTCJCR).

   Récupération des informations des jobs (CJOB/GTCJCR)

   C'est la méthode GET /api/foundations/jobsInformation/{jobNumber}

   Exemple : http://localhost:8080/iacqws/rest/api/foundations/jobsInformation/358931

   Le retour est équivalent à celui du service POST.

   Récupération du fichier résultat d'un job

   GET /api/foundations/jobFileResult/{jobNumber}?type=

   Exemple : http://localhost:8080/iacqws/rest/api/foundations/jobFileResult/359803?type=JOB_FILE_RESULT

   En retour : fichier résultat sous forme de stream, avec le nom du fichier dans le header
Content-Disposition ?attachment; filename=nom du fichier lu

   GED

   Recherche
POST /api/foundations/documentsReferencing/search

Permet de retourner l'ensemble des documents trouvés par les critères de recherche.

En retour : JSON contenant les caractéristiques des documents et jetons permettant l'accès direct aux documents (jetons ayant une durée de vie limitée).

   Accès
GET /api/foundations/documentsReferencing/query

Permet de retourner un stream correspondant au fichier demandé.

Exemple :
http://vmdevqng:8084/dvtoraws/rest/api/foundations/documentsReferencing/query?documentAccessToken=1rTS435ffT65.Rt......

En retour : Stream du fichier

   Ecriture
POST /api/foundations/documentsReferencing

Permet d'insérer un document en GED en fournissant le stream à uploader en entrée.

   Suppression
DELETE /api/foundations/documentsReferencing

Supprime le document passé en entrée.

WebServices "LEGACY"

   Il s'agit d'une mise à disposition et d'une adaptation de fonctions de plus bas niveau qui étaient présentes dans les services SOAP.

   Exécuter une requête
POST /WimRestService/action

En entrée :

{
     "queryString": "string",
     "bound": "string"
}

Exemple :

{
     "queryString": "action=select&requete=TESTPL&$PARAM1=TEST",
     "bound": "&"
}

Retour de type SvcResponse, avec dans le payload la mise en forme construite par la requête WIM.

   Appeler une procédure
Il s'agit d'appeler une procédure stockée. Contacter Cegid pour connaître les informations nécessaires à l'appel.

POST /WimRestService/callProc

En entrée

{
     "appCode": "string",                     // application qui porte la procédure
     "procName": "string",
     "parameter": {},
     "procedureType": "PROC",                // type de la procédure
     "systemValues": {                               // facultatif
               "retcode": "string",
               "sysret": "string",
               "iac_mes": "string",
               "syschp": "string",
               "sysets": "string",
               "sysdte": "string",
               "syscnf": "string",
               "syssec": "string",
               "sysorg": "string",
               "syswsa": "string",
               "sysaut": "string",
               "systra": "string",
               "sysapp": "string",
               "sysdtc": "string",
               "sysusr": "string",
               "sysmaj": "string",
               "syssid": "string",
               "syslan": "string",
               "sysuln": "string",
               "systrn": "string",
               "ident": "string",
               "nomtab": "string",
               "iac_maj": "string",
               "iac_act": "string",
               "iac_zon": "string"                // seule zone gérée actuellement
          }
}

Exemple :

{
     "appCode": "gti",
     "procName": "Psgtetsext",                // attention à la casse, notamment la majuscule
     "parameter": {"numgtets" :"PL"},
     "procedureType": "PROCEXT"
}

En retour, on obtient un objet de type SvcResponse, avec dans le payload les paramètres que retourne la procédure.