Esup-Signature

Arborescence des pages

Comparaison des versions

Légende

  • Ces lignes ont été ajoutées. Ce mot a été ajouté.
  • Ces lignes ont été supprimées. Ce mot a été supprimé.
  • La mise en forme a été modifiée.

...

Dans "Admin" puis "Circuit", vous avez accès à l'outil permet de consulter et de modifier les circuits de signatures. Il est possible de filtrer les circuits présents dans esup-signature :

  • Workflows globaux (définis pas les administrateurs)
  • Classes workflow (définies par les développeurs)
  • Workflows Utilisateurs (définis pas les utilisateurs)

Création d'un circuit classique

...

Dans cette vue il est possible d'activer la notion d'étape "infinie" en activant "L'utilisateur peut ajouter une étape avant la suivante". Si cette option est activée, vous donnerez la possibilité aux participants de cette étape, d'ajouter des étapes intermédiaires (non prévues à l'origine).

Chaque étape créée à la suite d'une étape "infinie" sera elle aussi une étape "infini". Cela se traduit, au niveau de l'interface utilisateur, par une question qui lui est posée lorsque qu'il s’apprête à signer : "Signer et passer à l'étape suivante" ou "signer et ajouter une étape"

Paramètres généraux d'un circuit

...

[TextField]^(SIG_SIGNATURE|SIG_CACHET)$ : pour détecter les champs de type TextField avec les attributs "nom" ("name") SIG_SIGNATURE ou SIG_CACHET

...

TypeCheminAdresse à saisirRemarques
SMBAbsolusmb://<adresse du serveur>/<chemin du dossier>smb-test-uri à configurer obligatoirement pour activer la fonctionnalité. L'utilisateur est configuré dans application.yml (smb-login, smb-password)
CMISRelatifcmis://<chemin du dossier> ex: cmis://default-domain/workspaces/test

Il faut configurer l'adresse du server nuxeo dans application.yml au niveau de (cmis-test-uri ex: http://mon-nuxeo.univ-ville.fr:8081/nuxeo, cmis-login, cmis-password)

Cette partie est amenée à changer prochainement pour permettre les chemins absolus

FILEAbsolu/<chemin du dossier>vfs-test-urià configurer obligatoirement pour activer la fonctionnalité.
FTP / SFTPAbsoluftp://<user>:<password>@<adresse du serveur>/<chemin du dossier>vfs-test-uri à configurer obligatoirement pour activer la fonctionnalité.


Remarque

Lorsqu'un document est intégré de cette façon, il est supprimé du dossier source.

...

  • smb, cmis, file, sftp / ftp, pour ces types, il faut suive les explications du tableau ci-dessus
  • http:// https:// (fera une requete, type REST, vers l'url avec en paramètre l'ID de la demande de signature, son statut signé ou refusé et le numéro d'étape concerné
  • mailto (envoi du document en pièce jointe au destinataire)

Transmission vers REST

(maj depuis pour la version 1.30.x)

Lorsque vous configurez une adresse type "rest", donc vers un websevice en écoute, esup-signature va tenté de transmettre les données via GET et via POST.

Voici la liste (et le format) des données transmisent :

Bloc de code
languagejs
themeRDark
{
    "signRequestId": ["XXXX"],
    "status": ["deleted"],
    "step": [
        "2"
    ],
    "userEppn": [
        "esupdem@univ-rouen.fr"
    ],
    "comment": [
        ""
    ]
}

En GET les informations sont transmisent via les paramètres de la requête.

Voici un exemple de code à mettre coté application métier pour permettre à esup-signature de transmettre les informations du circuit via REST. Ici il s'agir d'un controleur Spring. La requête est envoyée en GET, esup-signature doit avoir un accès direct à cette API. 

Bloc de code
languagejava
themeRDark
    @GetMapping(value = "/return-test")
    @ResponseBody
    public ResponseEntity<Void> returnTest(@RequestParam("signRequestId") String signRequestId, @RequestParam("status") String status, @RequestParam("step") String step) {
        logger.info(signRequestId + ", " + status + ", " + step);
        //ici, le code à executer coté application métier en fonction du statut
        return ResponseEntity.ok().build();
    }

En POST, les informations sont transmisent sous forme de map json:

Voici un script python permettant de simuler un mini server web et affichant les données reçues:

Verification du bon fonctionnement de la copie coté serveur avec smbclient :


Bloc de code
languageshell
apt install smbclient
echo "Contenu de test" > /tmp/test_fichier.txt
smbclient //stockage.univ-ville.fr/nom_du_partage -W MON_WORKGROUP -U mon_uid -c "put /tmp/test_fichier.txt mon_du_dossier/test_fichier.txt"

La copie doit fonctionner pour qu'esup-signature puisse en faire autant.

Transmission vers REST

(maj depuis pour la version 1.30.x)

Lorsque vous configurez une adresse type "rest", donc vers un websevice en écoute, esup-signature va tenté de transmettre les données via GET et via POST.

Voici à quels moments les requettes sont transmisent :

  • À chaque signature / refus (avec le motif)
  • À la suppression / suppression définitive
  • À la restautation
  • À la fin du circuit

Les statuts transmis sont les suivants : pending, refused, deleted, restored, cleaned, completed

Voici la liste (et le format) des données transmisent :

Bloc de code
languagejs
themeRDark
{
    "signRequestId": "XXXX",
    "status": "pending",
    "step": "1",
    "userEppn": "toto@univ-ville.fr",
    "comment": ""
}


En GET les informations sont transmisent via les paramètres de la requête.

Voici un exemple de code à mettre coté application métier pour permettre à esup-signature de transmettre les informations du circuit via REST. Ici il s'agir d'un controleur Spring. La requête est envoyée en GET, esup-signature doit avoir un accès direct à cette API. 

Bloc de code
languagejava
themeRDark
    @GetMapping(value = "/return-test")
    @ResponseBody
    public ResponseEntity<Void> returnTest(@RequestParam("signRequestId") String signRequestId, @RequestParam("status") String status, @RequestParam("step") String step) {
        logger.info(signRequestId + ", " + status + ", " + step);
Bloc de code
languagepy
themeRDark
from http.server import BaseHTTPRequestHandler, HTTPServer
from urllib.parse import urlparse, parse_qs
import json

class SimpleHTTPRequestHandler(BaseHTTPRequestHandler):
    
    def do_GET(self):
        # Parse l'URL et ses paramètres
        parsed_path = urlparse(self.path)
        query_components = parse_qs(parsed_path.query)
//ici, le code à executer coté application métier en fonction du statut
        return ResponseEntity.ok().build();
    }


En POST, les informations sont transmisent sous forme de map json:

Voici un script python permettant de simuler un mini server web et affichant les données reçues ici en http://localhost:8000 :

Bloc de code
languagepy
themeRDark
from http.server import BaseHTTPRequestHandler, HTTPServer
from urllib.parse import urlparse, parse_qs
import json

class SimpleHTTPRequestHandler(BaseHTTPRequestHandler):    # Affiche les informations dans la console
        print("Received GET request:")
    
    print(f"Path: {self.path}")def do_GET(self):
        print("Query parameters:")
    # Parse l'URL et ses paramètres
    for key, values in query_components.items():
  parsed_path = urlparse(self.path)
        query_components  for value in values:
= parse_qs(parsed_path.query)
        
        # Affiche les informations dans la console
        print(f"Received GET {key}: {value}request:")
        print(f"Path: {self.path}")
        # Prépare la réponse (simple pour le client)
        self.send_response(200)print("Query parameters:")
        for key, values in query_components.items():
        self.send_header('Content-type', 'text/html')
    for value in values:
   self.end_headers()
        self.wfile.write(b"<html><body><h1>GET request received</h1></body></html>")

     print(f"  {key}: {value}")
       def do_POST(self):
        # RécupèrePrépare la réponse longueur(simple despour donnéesle envoyéesclient)
        content_length = int(self.headers['Content-Length']self.send_response(200)
        self.send_header('Content-type', 'text/html')
        # Récupère le corps de la requête
  self.end_headers()
      post_data = self.rfilewfile.read(content_length)write(b"<html><body><h1>GET request received</h1></body></html>")

        def do_POST(self):
        # TenteRécupère dela décoderlongueur lesdes données comme JSONenvoyées
        try:content_length = int(self.headers['Content-Length'])
        # Récupère le corps json_datade = json.loads(post_data)la requête
        post_data    print("Received POST request with JSON data:"= self.rfile.read(content_length)
        
    print(json.dumps(json_data, indent=4))
    # Tente de décoder les données except json.JSONDecodeError:comme JSON
        try:
    print("Received POST request with invalid JSON")
   json_data = json.loads(post_data)
        json_data   = {"error": "Invalid JSON"}print("Received POST request with JSON data:")
        
    print(json.dumps(json_data, indent=4))
   # Prépare la réponse (simple pour le client)
except json.JSONDecodeError:
         self.send_response(200   print("Received POST request with invalid JSON")
        self.send_header('Content-type', 'text/html')    json_data = {"error": "Invalid JSON"}
        self.end_headers()
         self.wfile.write(b"<html><body><h1>POST request received</h1></body></html>")

def run(server_class=HTTPServer, handler_class=SimpleHTTPRequestHandler, port=8000):# Prépare la réponse (simple pour le client)
    server_address   = ('', portself.send_response(200)
      httpd = server_class(server_address, handler_class self.send_header('Content-type', 'text/html')
    print(f"Starting server on port {port}...")
self.end_headers()
        httpd.serve_forever(self.wfile.write(b"<html><body><h1>POST request received</h1></body></html>")

ifdef __name__ == '__main__':run(server_class=HTTPServer, handler_class=SimpleHTTPRequestHandler, port=8000):
    server_address = ('', port)
    httpd = server_class(server_address, handler_class)
    run()

Définir les participants à un circuit à posteriori

print(f"Starting server on port {port}...")
    httpd.serve_forever()

if __name__ == '__main__':
    run()

Avertissement

Attention à partir du moment où une destination REST à été mise en place, il faut qu'il y ai au moins un web service GET ou POST qui répond sinon les créations, signature, et refus ne pourront pas fonctionner.


...

Définir les participants à un circuit à posteriori

Dans certains cas, les participants à Dans certains cas, les participants à une étape ne peuvent pas être prédéfinis dans esup-signature.

...

  • Mettre en place un script qui va calculer le workflow, utiliser les web-services pour configurer les participants du circuit des bons de commandes et injecter le document. (pour plus de détails sur les web services voir : Web services REST)

  • Mettre en place un script qui calcule le workflow, inscrit ce workflow dans les métas-données du document (PDF) et le copie dans un dossier défini comme "source" au niveau d'Esup-signature.
    Pour cette dernière solution il faut donc créer un circuit comme vu précédemment, cocher la case "Scanner les métadonnées des PDF" et définir une source pour la récupération des documents au niveau des paramètres généraux

  • Implémenter une classe worflow (voir le chapitre suivant)

Les métas-données qui doivent être inscrite dans les document PDF sont les suivantes:

...

Bloc de code
languagejava
themeRDark
PDDocument document = PDDocument.load(in);
PDDocumentInformation info = document.getDocumentInformation();
info.setCustomMetadataValue("sign_type_default_val", "pdfImageStamp");
info.setCustomMetadataValue("sign_step#1", "[machin@univ-ville.fr, truc@univ-ville.fr]");
info.setCustomMetadataValue("sign_target_key","smb://serveur_de_fichiers/la_destination/signed");

...

Utiliser les classes workflow

Pour les cas les plus spécifiques, il est possible d'ajouter, au code source original d'esup-signature, une classe qui décrira précisément un circuit.

...

Bloc de code
languagejava
themeRDark
package org.esupportail.esupsignature.service.workflow.impl;

import org.esupportail.esupsignature.entity.Data;
import org.esupportail.esupsignature.entity.User;
import org.esupportail.esupsignature.entity.WorkflowStep;
import org.esupportail.esupsignature.entity.enums.SignType;
import org.esupportail.esupsignature.exception.EsupSignatureUserException;
import org.esupportail.esupsignature.service.workflow.DefaultWorkflow;
import org.springframework.stereotype.Component;

import java.util.ArrayList;
import java.util.List;

@Component
public class BasicWorkflow extends DefaultWorkflow {

   private String name = "BasicWorkflow";
   private String description = "Une signature";
   private List<WorkflowStep> workflowSteps;

   @Override
   public String getName() {
      return name;
   }

   @Override
   public String getDescription() {
      return description;
   }

   @Override
   public List<WorkflowStep> getWorkflowSteps() {
      if(this.workflowSteps == null) {
         try {
            this.workflowSteps = generateWorkflowSteps(userService.getCurrentUser(), null, null);
         } catch (EsupSignatureUserException e) {
            return null;
         }
      }
      return this.workflowSteps;
   }

   public void initWorkflowSteps() {
      this.workflowSteps = new ArrayList<>();
   }

   @Override
   public List<WorkflowStep> generateWorkflowSteps(User user, Data data, List<String> recipentEmailsStep) throws EsupSignatureUserException {
      List<WorkflowStep> workflowSteps = new ArrayList<>();
/* ici on construit la liste des étapes du circuit */
      WorkflowStep workflowStep = new WorkflowStep();
      workflowStep.setStepNumber(1);
      workflowStep.setSignType(SignType.pdfImageStamp);
      workflowStep.setDescription("Choix du signataire");
      workflowStep.setChangeable(true);
      if(data != null) {
         workflowStep.setRecipients(workflowService.getFavoriteRecipientEmail(1, data.getForm(), recipentEmailsStep, user));
      } else {
         workflowStep.getRecipients().add(recipientService.createRecipient(null, userService.getGenericUser("Utilisateur issue des favoris", "")));
      }
      workflowSteps.add(workflowStep);
      return workflowSteps;
   }
}


Il faut donc a minima :

...

workflowService.getFavoriteRecipientEmail(1, data.getForm(), recipentEmailsStep, user));
      } else {
         workflowStep.getRecipients().add(recipientService.createRecipient(null, userService.getGenericUser("Utilisateur issue des favoris", "")));
      }
      workflowSteps.add(workflowStep);
      return workflowSteps;
   }
}


Il faut donc a minima :

  • Préciser un nom et une description (dans name et description)
  • Implémenter la fonction generateWorkflowSteps() qui retournera une liste de WorkFlowStep (les étapes calculée en fonction de l'utilisateur courant "user")

...

Récupérer les données via Power Query

Tout d'abord il faut se générer une clé API depuis les paramètres utilisateurs. Si le champ est vide il suffit de cliquer sur actualiser.

Remarque

Attention, si une clé est déjà présente, cela invalidera la précédente et déconnectera les sources existant.

Attention aussi, les données présentes dans le fichier excel ne pourrons plus être mises à jour mais resterons présentes tout de même ! Il faudra donc bien protéger le fichier. 

Un nouveau paramètre csv-access-authorize-mask permet de limiter l'usage de la synchro csv pour un réseau précis.

Via Excel, Ouvrir Power Query ("Obtebir des données → Power Query)

Dans Power Query, "Nouvelle source" → "Autres sources" → "Requête vide"

Clique droit sur la requête (Requête1 par ex) → "Éditeur avancé" et copier un script du type :

Bloc de code
languageactionscript3
themeRDark
let
    Source = Csv.Document(Web.Contents("https://esup-signature.univ-ville.fr/ws/workflows/XXXXXXX/datas/csv", [ApiKeyName="XApiKey"]), [Delimiter=",", Columns=24, Encoding=65001, QuoteStyle=QuoteStyle.None]),
    Table_table = Table.PromoteHeaders(Source, [PromoteAllScalars=true])
in
    Table_table

Il faut, ensuite, cliquer sur "Modifier les informations d'identification", choisir API Web et coller votre clé.

...