Utilisation de la comptabilité and API OpenFlyers: Difference between pages

From Documentation de la solution web de gestion OpenFlyers
(Difference between pages)
Jump to navigation Jump to search
 
 
Line 1: Line 1:
__TOC__
=Présentation=
=Présentation=
L'objet de cette page est de décrire la saisie des opérations de [[Comptabilité|comptabilité]] dans la [[Accueil|version 4 d'OpenFlyers]].
L'objet de cette page est de décrire l'API OpenFlyers.


==Date par défaut dans les formulaires==
;Description de l'API
La date proposée par défaut dans les formulaires de saisie (saisie des factures fournisseurs, saisie des transferts de compte à compte, etc.) est la date de la dernière écriture enregistrée.
OpenFlyers possède une API basée sur [[Wikipedia-en:OAuth#OAuth_2.0|OAuth2]] qui permet à des serveurs extérieurs, dûment [[#Enregistrer-un-client|enregistrés]], de mettre en œuvre un processus d'[[Wikipedia-fr:Authentification_unique|authentification unique (SSO)]] et/ou de récupération des résultats des requêtes SQL de la [[Bibliothèque-des-rapports|bibliothèque des rapports]] ou des rapports personnalisés sous la forme de fichiers CSV.


==Définitions==
OAuth2 propose plusieurs mécanismes pour permettre l'authentification. Un mécanisme d'authentification détermine la séquence exacte des étapes impliquées dans le processus d'authentification d'OAuth2. OpenFlyers met à disposition deux mécanismes d'authentification :
===Compte d'export===
*[[#Authorization Code|Authorization Code]] basé sur la méthode d'authentification par code d'autorisation et qui correspond au mécanisme associé à l'[[Wikipedia-fr:Authentification_unique|authentification unique (SSO)]],
Pour chaque compte, il faut [[Configuration de la comptabilité#Définir_les_comptes_d'export|préciser un compte d'export]] qui sert dans le cas où on exporte la comptabilité vers un autre logiciel de comptabilité.
*[[#Client Credentials|Client Credentials]] basé sur la méthode d'authentification avec les identifiants clients et qui est utilisé dans un contexte d'automatisme sans autorisation de l'utilisateur au préalable.


Cela sert également si on souhaite éditer une comptabilité depuis OpenFlyers et son module de gestion des rapports tout en respectant un plan comptable codifié.
Dans les chapitres qui suivent, le terme ''ressource'' fait référence à la définition OAuth2. Une ressource dans OAuth2 est un élément qui peut être :
*une ou des données comme des photos, des documents, des contacts ou des informations personnelles,
*un ou plusieurs services comme des transferts de fonds, la récupération de rapports ou l'ajout d'articles sur un blog,
*toute ressource nécessitant un accès restreint.


=Clôturer l'exercice comptable=
OpenFlyers définit plusieurs [[#Utiliser-l'API|types de ressources]] :
[https://www.youtube.com/watch?v=_hjOHHXP_ig Vidéo tutorielle pour clôturer une comptabilité]
*les [[#Récupérer-les-informations-de-l'utilisateur-connecté|informations de l'utilisateur connecté]],
*les [[#Récupérer les rapports génériques|rapports génériques]] ou [[#Récupérer les rapports personnalisés|personnalisés]] au format CSV.


''Attention : la clôture de la comptabilité ne doit être effectuée qu'une fois qu'on est sûr et certain de ne plus avoir à intervenir sur l'exercice à clôturer. Cette opération doit donc être temporisée dans le temps sachant qu'il est tout à fait possible de poursuivre son activité sur l'exercice suivant sans que l'exercice précédent ne soit clôturé.''
OAuth2 dispose de ''scopes''. Un scope est un privilège définit de manière explicite permettant l'accès à une ressource protégée. OpenFlyers met à disposition une [[#Liste des scopes disponibles|liste de scopes]] utilisables à travers l'API.


OpenFlyers affiche une [[Alertes de configuration#L'exercice_comptable_courant_a_plus_d'un_an|alerte lorsque l'exercice en cours est ouvert depuis 400 jours]].
Deux protocoles de sécurité sont présents dans l'API OpenFlyers :
*[[#Authentification-TLS-Mutuelle-(mTLS)|mTLS]] : il permet d'authentifier le client avec un certificat TLS, en plus d'authentifier le serveur avec un certificat. Ce protocole permet d'éviter les usurpations d'identité.
*[[#HTTP-Signature|HTTP-Signature]] : il permet de signer les en-têtes et le corps (lorsqu'il y en a un) des messages échangés afin d'en garantir leur intégrité.


Avant de clôturer, il faut :
;Premiers pas - Client de démonstration
*Si le bilan est effectué dans OpenFlyers, [[Écritures comptables#Écritures_de_fin_d'exercice_avant_la_clôture_de_la_comptabilité|saisir les écritures de fin d'exercice]]
Un client de démonstration est disponible pour comprendre les mécanismes décrits ci-dessous.
*[[#Solder-les-comptes|Solder les comptes d'exploitation]].
*[[Utilisation-de-la-comptabilité#Valider-toutes-les-écritures|Valider toutes les écritures antérieures à la date de clôture]].
En effet, on ne peut clôturer qu'une période pour laquelle toutes les écritures comptables ont été validées sinon cela engendre [[Alertes-administrateur#Alertes-lors-de-la-clôture-de-la-comptabilité|des alertes]]. Si des comptes n'ont pas été [[#Solder-les-comptes|soldés]] alors un [[Alertes-administrateur#Alertes-lors-de-la-clôture-de-la-comptabilité|message d'alerte]] apparaitra. Il n'est également pas possible de clôturer sur une date future.


Ensuite, pour clôturer :
L'utilisation de ce client de démonstration est décrite dans la procédure [[#Utiliser-le-client|Utiliser le client]] de cette page.
*Aller dans '''Gestion > Comptes > Exercice > Clôturer'''.
*Dans la première colonne, la date de début de l'exercice comptable est renseignée ainsi qu'un tableau intitulé '''Statistiques''' fournit des informations sur les écritures. Sa lecture est indispensable pour vérifier qu'il ne subsiste pas d'écriture non validée antérieures à la fin de la période de clôture souhaitée. Si tel est le cas, la date sera affichée en rouge. Dans ce scénario, il est nécessaire de localiser les écritures non validées et de les valider en utilisant le bouton "Aller vers la première écriture comptable non validée" pour les écritures comptables non validées, ou le bouton "Aller vers la première activité non validée" pour les activités non validées.
*Si l'utilisateur connecté dispose du [[Gestion-des-profils#Modifier-la-date-d%27un-encaissement,-d%27un-flux-ou-d%27un-transfert|droit de modifier la date de clôture]], un calendrier apparaît à côté de la mention '''Clôturer l'exercice à la date du :''' en bas du formulaire. Lorsque l'utilisateur saisit une date, '''la date de début du nouvel exercice''' est automatiquement mise à jour dans la deuxième colonne intitulée '''Gestion de l'exercice comptable'''. Sinon, la date par défaut affichée sera le 31/12 de l'année suivant celle de la dernière clôture.


Pour une structure qui a un exercice annuel correspondant à l'année civile les dates devraient être 31/12/XXXX et 01/01/XXXX+1
Le client de démonstration est lui-même accessible à cette adresse : https://openflyers.com/oauth2-demo/index.php
*Cliquer sur le bouton '''Clôturer l'exercice puis ouvrir un nouvel exercice'''.


En cas d'erreur de clôture, il est possible de [[#Déclôturer-l'exercice-comptable|Déclôturer le dernier exercice clôturé]].
Le code source du client de démonstration est mis à disposition par OpenFlyers à cette adresse : https://openflyers.com/oauth2-demo/oauth2-demo-src.zip


=Créer un tarif pour la facturation des activités=
L'utilisation du code source est décrite dans la procédure [[#Créer-un-client-à-partir-du-code-source|Créer un client à partir du code source]].
*'''Admin > Ventes > Variables > Définition'''
*Cliquer sur le bouton '''Ajouter'''
*Champ '''Nom de variable''', renseigner un nom de variable sous la forme ''tarifSoloXXXX'' en remplaçant XXXX par le nom du type de ressource pour lequel le tarif doit s'appliquer. Exemple : ''tarifSoloDR42''. Il est primordial de respecter la casse (c'est à dire les majuscules et minuscules).
*Champ '''Intitulé''', renseigner le nom en clair de la variable sous la forme ''Tarif Solo XXXX'' en remplaçant XXXX par le nom du type de ressource pour lequel le tarif doit s'appliquer.
*Champ '''Type de valeur''', laisser ''Nombre à virgule''
*Cliquer sur le bouton '''Enregistrer'''


Il faut ensuite associer une valeur au tarif :
=Définitions=
*'''Admin > Ventes > Variables > Actualisation'''
==Authentification TLS Mutuelle (mTLS)==
*Cliquer sur le bouton '''Ajouter une nouvelle valeur à une variable existante''' en bas du tableau
En général dans une communication TLS, seul le serveur a l'obligation de fournir un certificat. Il est également possible pour le client de fournir un certificat. Ce principe s'appelle l'authentification mutuelle et est mise en place avec Mutual TLS (ou [[Wikipedia-en:Mutual_authentication#mTLS|mTLS]]).
*Champ '''Nom de variable''', sélectionner le nom de la variable précédemment créé
*Champ '''Valeur''', renseigner le tarif à associer à la variable
*Champ '''Date de début''', laisser la date renseignée par défaut : elle correspond à la date de début d'exercice comptable et permet de ne pas avoir de "trou tarifaire"
*Cliquer sur le bouton '''Enregistrer'''


=Déclôturer l'exercice comptable=
OpenFlyers associe un certificat pour l'authentification mutuelle unique à chaque client OAuth2.
;Prérequis
 
*Disposer du droit [[Gestion-des-profils#Gestion-des-comptabilités|Gestion des comptabilités]]
;Envoyer un certificat client
Côté client, le code suivant peut être utilisé pour fournir à cURL le certificat et la clé correspondante ainsi que le certificat du CA d'OpenFlyers à utiliser pour la connexion :
<syntaxhighlight lang="php">
curl_setopt_array($request, [
    CURLOPT_SSLVERSION => CURL_SSLVERSION_TLSv1_2,
    CURLOPT_CAINFO    => $caCertificatePath,
    CURLOPT_SSLCERT    => $certificatePath,
    CURLOPT_SSLKEY    => $keyPath
]);
</syntaxhighlight>
 
'''À noter''' : le certificat du CA d'OpenFlyers est nécessaire pour assurer la validité des certificats utilisés.
 
==HTTP Signature==
HTTP Signature utilise le principe de la signature numérique pour garantir l'authenticité et l'intégrité du message HTTP.
 
La signature est générée à l'aide d'une clé privée et vérifiée à l'aide de la clé publique correspondante ou d'un certificat contenant cette clé publique.
 
HTTP Signature utilise deux en-têtes HTTP :
*Signature : contient la signature et ses métadonnées.
*Digest : contient le corps du message haché.
 
===Digest===
Le ''digest'' est calculé comme ceci : <code>digest = base64encode(sha256(corps du message))</code>
 
Et l'en-tête est structuré de la manière suivante : <code>Digest: SHA-256=<digest></code>
 
Un autre algorithme de hachage peut être utilisé, SHA-256 reste cependant le plus répendu.
 
;Exemple en php
<syntaxhighlight lang="php">
$digestHeader = 'Digest: SHA-256=' . base64_encode(hash('sha256', $postData, true));
</syntaxhighlight>
 
===Signature===
L'en-tête HTTP de signature est structuré de la manière suivante : <code>Signature: keyId="<keyId>",algorithm="<algo>",headers="<signed_headers>",signature="<signature>"</code>
 
Le champ ''keyId'' correspond à un identifiant permettant l'identification de la clé utilisée pour vérifier la signature. Pour l'API d'OpenFlyers, sa valeur correspond à l'empreinte SHA-1 du certificat au format PEM à utiliser pour vérifier la signature.
 
L'algorithme ''algo'' correspond à celui utilisé pour générer la signature, exemple : <code>rsa-sha256</code>.
 
La valeur de ''signed_headers'' correspond à la liste des en-têtes inclus dans la signature séparés d'un espace. Exemple : <code>date digest (request-target)</code>
 
Pour générer la signature, une chaîne de caractères appelée <code>signing string</code> contenant les en-têtes au format <code>lowercase_header_name: value</code> séparés par une nouvelle ligne au format LF (<code>\n</code>) est d'abord générée. Exemple avec les en-têtes "date" et "(request-target)" :
 
<pre>(request-target): post /some/uri\ndate: Tue, 07 Jun 2014 20:51:35 GMT</pre>
 
La signature est ensuite générée comme ceci : <code>base64encode(algo(signing string))</code>
 
;Exemple en php
<syntaxhighlight lang="php">
function generateSignatureHeader(array $headersToSign, string $certificateFingerprint, string $privateKey): string
{
    // generating the signing string and header list
    $headers        = '';
    $signatureString = '';
    foreach ($headersToSign as $key => $value) {
        $normalizedHeaderKey = trim(strtolower($key));
        $headers            .= $normalizedHeaderKey . ' ';
        $signatureString    .= $normalizedHeaderKey . ': ' . trim($value) . "\n";
    }
 
    // trim extra whitespace
    $headers        = trim($headers);
    $signatureString = trim($signatureString);
 
    // signing the signing string
    $signature = '';
    openssl_sign($signatureString, $signature, $privateKey, 'RSA-SHA256');
    $signature = base64_encode($signature);
 
    // compiling the header line
    return "Signature: keyId=\"$certificateFingerprint\",algorithm=\"rsa-sha256\",headers=\"$headers\",signature=\"$signature\"";
}
</syntaxhighlight>
Les variables ''$certificateFingerprint'' et ''$privateKey'' correspondent respectivement à une empreinte SHA-1 de certificat et à une clé privée, tous deux au format PEM.
 
La variable ''$headersToSign'' est un tableau formaté de la manière suivante :
<syntaxhighlight lang="php">
[
    $headerName => $headerValue,
    ...
]
</syntaxhighlight>
 
'''À noter''' : les en-têtes sont à signer côté client avec le certificat de signature signé par le CA d'OpenFlyers et dédié au client ainsi que la clé privée associée. Le certificat de signature dédié au client est téléchargeable depuis l'interface de gestion des clients OAuth2. Les en-têtes de la réponse du serveur quant à elles doivent être vérifiées avec le certificat de signature HTTP du serveur, téléchargeable aussi depuis l'interface de configuration des clients OAuth2.
 
=Client OAuth2=
Une fois le client OAuth2 configuré sur OpenFlyers, il faut l'utiliser avec un client créé au préalable pour communiquer avec le serveur d'autorisation et l'API.
 
Plusieurs [https://oauth.net/code/ bibliothèques] simplifiant la création d'un client sont disponibles.
 
Des scripts client basiques écrits en php sont aussi fournis pour les mécanismes [[#Script-client-:-authorization-code|''authorization_code'']] et [[#Script-client-:-client-credentials|''client_credentials'']]
 
==Authorization Code==
Ce flux OAuth2 se déroule en plusieurs étapes :
*Le client redirige le navigateur de l'utilisateur vers l'URL d'autorisation.
*Le navigateur de l'utilisateur est redirigé vers l'URL fournie durant la demande ou durant l'enregistrement du client.
*Le client récupère un code d'autorisation grâce à la redirection précédente, et échange ce code contre un jeton d'accès auprès du serveur d'autorisation.
*Le client peut utiliser ce code d'accès :
**Comme preuve d'authentification pour une solution SSO (Single Sign-On).
**Pour accéder à des données sur le serveur distant en utilisant l'API.
 
 
    +-----------+                  +------+                +-----------+                  +-------------+                  +-----------+
    |Utilisateur|                  |Client|                |Navigateur |                  |  Serveur  |                  |  Serveur  |
    +-----+-----+                  +--+---+                +-----+-----+                  |Autorisation |                  | Ressources|
          |                            |                          |                        +------+------+                  +-----+-----+
          |                            |                          |                              |                              |
          |                            |                  Demande|d'autorisation                |                              |
          |                            +------------------------->+------------------------------>|                              |
          |                            |                          |                              |                              |
          |                            |Authentification + Formulaire d'autorisation              |                              |
          |<---------------------------+--------------------------+<------------------------------+                              |
          |                            |                          |                              |                              |
          |                            |      Autorisation        |                              |                              |
          +----------------------------+------------------------->+------------------------------>|                              |
          |                            |                          |                              |                              |
          |                            |                      Code|d'autorisation                |                              |
          |                            |<-------------------------+<------------------------------+                              |
          |                            |                          |                              |                              |
          |                            |                Demande de|token                          |                              |
          |                            +--------------------------+------------------------------>|                              |
          |                            |                          |                              |                              |
          |                            |                Access (+|Refresh) token                |                              |
          |                            |<-------------------------+-------------------------------+                              |
          |                            |                          |                              |                              |
          |                            |                          |      Requête vers API        |                              |
          |                            +--------------------------+-------------------------------+------------------------------>|
          |                            |                          |                              |                              |
          |                            |                          |                              |                              |
          |                            |                          |                              |<------------------------------+
          |                            |                          |                              |  Vérification d'autorisation  |
          |                            |                          |                              +------------------------------>|
          |                            |                          |                              |                              |
          |                            |                          |      Données/Réponse          |                              |
          |                            |<-------------------------+-------------------------------+-------------------------------+
          |                            |                          |                              |                              |
 
 
 
===Générer les codes pour PKCE===
Pour faire fonctionner le client OAuth2, il faut générer deux codes pour l'extension PKCE :
*Un ''code_verifier'' échangé pendant la demande de jeton d'accès.
*Un ''code_challenge'' dérivé du ''code_verifier'' et échangé pendant la demande d'autorisation.
 
;Générer le ''code_verifier''
<syntaxhighlight lang="php">
function generateCodeVerifier($length = 128): string
{
    $characters = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-._~';
    $outputCode = '';
 
    for ($i = 0; $i < $length; $i++) {
        $index      = random_int(0, strlen($characters) - 1);
        $outputCode .= $characters[$index];
    }
 
    return $outputCode;
}
</syntaxhighlight>
 
Cette fonction permet de générer un ''code_verifier'' avec une longueur donnée. Le ''code_verifier'' doit avoir une longueur entre 43 et 128 caractères.
 
;Générer le ''code_challenge''
<syntaxhighlight lang="php">
function computeCodeChallenge(string $codeVerifier): string
{
    return strtr(rtrim(base64_encode(hash('sha256', $codeVerifier, true)), '='), '+/', '-_');
}
</syntaxhighlight>
 
Cette fonction génère le ''code_challenge'' à partir du ''code_verifier'' fourni.
 
===Demande d'autorisation===
Pour initier la demande d'autorisation, rediriger le navigateur de l'utilisateur vers l'URL : <code>GET https://openflyers.com/nom-de-plateforme/oauth/authorize.php</code>. Remplacer <code>nom-de-plateforme</code> par le nom de la plateforme utilisée.
 
Envoyer également les paramètres suivants :
{| class="wikitable"
!Nom!!Type!!Description
|-
|client_id||string||L'identifiant unique reçu pendant l'enregistrement du client.
|-
|response_type||string||Le type de réponse envoyée par le serveur. Doit utiliser la valeur "code" (sans guillements) pour ce mécanisme.
|-
|redirect_uri||string||L'URI (ou l'URL) fourni pendant l'enregistrement du client et vers lequel l'utilisateur est redirigé après la demande d'autorisation.
|-
|scope||string||[[#Liste-des-scopes-disponibles|Liste des droits demandés par le client]], séparés par des espaces.
|-
|state||string||Chaîne de caractère aléatoire utilisée pour éviter les [https://fr.wikipedia.org/wiki/Cross-site_request_forgery attaques CSRF]. '''Fortement recommandé'''.
|-
|code_challenge||string||Code nécessaire pour le fonctionnement de l'extension [[#Générer-les-codes-pour-PKCE|PKCE]].
|-
|code_challenge_method||string||Méthode utilisée pour générer le ''code_challenge''. Ici, la valeur est "S256".
|}
 
===Demande de jeton d'accès===
Après avoir répondu à la demande, l'utilisateur est redirigé vers l'URI fourni pendant l'enregistrement du client. Si la demande est acceptée, un code temporaire : ''code'' est fourni, ainsi que le paramètre ''state'' fourni pendant la demande avec la même valeur. Si la demande est refusée, un code d'erreur est renvoyé.
;Si le paramètre ''state'' a une valeur différente de celle envoyée avec la demande, c'est peut-être une tentative d'attaque et il faut refuser la réponse.
 
Echanger ce ''code'' contre un jeton d'accès via l'URL : <code>POST https://openflyers.com/nom-de-plateforme/oauth/access_token.php</code>. Remplacer <code>nom-de-plateforme</code> par le nom de la plateforme utilisée.
 
Les paramètres suivants sont également nécessaires :
{| class="wikitable"
!Nom!!Type!!Description
|-
|client_id||string||L'identifiant unique reçu pendant l'enregistrement du client.
|-
|client_secret||string||La passphrase reçue pendant l'enregistrement du client.
|-
|code||string||Le code temporaire reçu dans la réponse à la demande d'autorisation.
|-
|grant_type||string||Le mécanisme d'autorisation utilisé. Ici, sa valeur doit être ''authorization_code''
|-
|redirect_uri||string||L'URI (ou l'URL) de redirection fourni pendant l'enregistrement du client.
|-
|code_verifier||string||Le ''code_verifier'' utilisé pour générer le ''code_challenge'' de la demande d'autorisation.
|}
 
Si la requête est correcte, un jeton d'accès (Access Token) ainsi qu'un jeton de rafraîchissement (Refresh Token) sont fournis en réponse dans un objet au format JSON.
<syntaxhighlight lang="javascript">
{
  "token_type": "Bearer",
  "expires_in": 3600,
  "access_token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJSU-G7fOBOYzOgioSGNIQKI2A2OxjsNBbOOoaHsqNpsQxWZse3rofExAaGKh2tXbeuz1YAVhdLUGYgq-oKRK4ONFhw2NvcRf3QPxQXZImLWw",
  "refresh_token": "a59ef39fa9bab9b95cd554f921e7f3080a34c90f23d2b8031d692b5ff2d0993dcc392d9c7f9a43242337ef144c1a5fe1d0174413ade973e1b628ac0bbfc39b23973534"
}
</syntaxhighlight>
 
===Script client : Authorization Code===
Voici un exemple simple de client OAuth2 pour le mécanisme d'authentification par code d'autorisation (Authorization Code).
 
Fichier de configuration ''config.authcode.json'' :
<syntaxhighlight lang="javascript">
{
  "client_id": "",
  "client_secret": "",
  "authorize_uri": "https://openflyers.com/nom-de-plateforme/oauth/authorize.php",
  "token_uri": "https://openflyers.com/nom-de-plateforme/oauth/access_token.php",
  "resource_uri": "https://openflyers.com/nom-de-plateforme/oauth/resources.php",
  "auth_cert": "/path/to/client/auth_cert.crt",
  "auth_key": "/path/to/client/auth.key",
  "sign_cert": "/path/to/client/sign_cert.crt",
  "sign_key": "/path/to/client/sign.key",
  "auth_cacert": "/path/to/ca.crt",
  "sign_cert_server": "/path/to/server/sign_cert_server.crt"
}
</syntaxhighlight>
Où <code>/path/to/client/</code> est le chemin d'accès vers le dossier qui contient le code source du client de démonstration.
*Exemple sur '''windows''': <code>C:/wamp64/www/4.0/oauth-demo/ssl/AuthCodeDemo/</code>.
*Exemple sur un serveur Linux '''debian''': <code>./ssl/AuthCodeDemo/</code>.
 
Ce fichier de configuration doit se situer au même niveau que le script PHP dans le système de fichiers.
 
Script PHP :
<syntaxhighlight lang="php">
<?php
$localTokenFile    = 'token.json';
// create token file if it does not exist
if (!file_exists($localTokenFile))
    file_put_contents($localTokenFile, '');
 
$config                = json_decode(file_get_contents('config.authcode.json'), true);
$localToken            = json_decode(file_get_contents($localTokenFile), true);
$GLOBALS['config']      = $config;
 
// Build the baseURL, accounting for protocol, port, name and endpoint. Used for redirection
$protocol = isset($_SERVER['HTTPS']) ? 'https://' : 'http://';
$port    = ($protocol == 'https://' && $_SERVER['SERVER_PORT'] == 443)
    || ($protocol == 'http://' && $_SERVER['SERVER_PORT'] == 80) ? '' : ':' . $_SERVER['SERVER_PORT'];
 
$baseURL            = $protocol . $_SERVER['SERVER_NAME'] . $port . $_SERVER['PHP_SELF'];
$errorLog          = 'error_log.log';
$responseLog        = 'response_log.log';
$GLOBALS['baseURL'] = $baseURL;
 
//Session cookies are used to store information necessary for the authorization code flow
session_start();
 
/**
* This function is used to make api calls to the Authorization Server and the Resource Server
*
* @param      $url
* @param      $post
* @param array $headers
*
* @return mixed
*/
function apiRequest($url, $post = null, $token = false, $auth = true, $refresh = false, $headers = array())
{
    $ch = curl_init($url);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    curl_setopt($ch, CURLOPT_HEADER, true);
 
    $method = 'get';
 
    if ($post) {
        $method  = 'post';
        $postData = http_build_query($post);
        curl_setopt($ch, CURLOPT_POST, true);
        curl_setopt($ch, CURLOPT_POSTFIELDS, $postData);
 
        $headersToSign['Content-Type'] = 'application/x-www-form-urlencoded';
        $headersToSign['digest']      = 'SHA-256=' . base64_encode(hash('sha256', $postData, true));
    }
 
    $urlComponents = parse_url($url);
 
    $headersToSign['(request-target)'] = $method . ' ' . $urlComponents['path'];
    $headersToSign['Host']            = $urlComponents['host'];
    $headersToSign['Date']            = gmdate('D, j M Y H:i:s T');
 
    // generating the signature header
    $keyId                = openssl_x509_fingerprint(file_get_contents($GLOBALS['config']['sign_cert']));
    $privateKey          = file_get_contents($GLOBALS['config']['sign_key']);
    $headers['Signature'] = generateSignatureHeader($headersToSign, $keyId, $privateKey);
 
    $headers['Accept']    = 'application/json';
    $headers['User-Agent'] = $GLOBALS['baseURL'];
    unset($headersToSign['(request-target)']);
    $headers              += $headersToSign;
 
    if ($token) {
        $headers['Authorization'] = $token['token_type'] . ' ' . $token['access_token'];
    }
 
    // formatting the headers
    $httpFormattedHeaders = [];
    foreach ($headers as $key => $value) {
        $httpFormattedHeaders[] = trim($key) . ': ' . trim($value);
    }
 
    curl_setopt($ch, CURLOPT_HTTPHEADER, $httpFormattedHeaders);
    curl_setopt($ch, CURLINFO_HEADER_OUT, true);
 
    // for development environment only
    //curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
    //curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
    //curl_setopt($ch, CURLOPT_VERBOSE, true);
 
    // defining TLS client certificates for Mutual TLS
    curl_setopt($ch, CURLOPT_SSLVERSION, CURL_SSLVERSION_TLSv1_2);
    curl_setopt($ch, CURLOPT_CAINFO, $GLOBALS['config']['auth_cacert']);
    curl_setopt($ch, CURLOPT_SSLCERT, $GLOBALS['config']['auth_cert']);
    curl_setopt($ch, CURLOPT_SSLKEY, $GLOBALS['config']['auth_key']);
 
    $response = curl_exec($ch);
 
    // logging errors and responses
    errorLog(true, $response, $ch);
 
    curl_close($ch);
 
    // in case an authentication request is executed
    if ($auth) {
        // required when a refresh token request is issued
        // because there is only one header in the response
        // while there are two for regular auth requests
        if (!$refresh) {
            list($firstResponseHeaders, $secondResponseHeaders, $responseBody) = explode("\r\n\r\n", $response, 3);
            if (!$responseBody) {
                list($secondResponseHeaders, $responseBody) = explode("\r\n\r\n", $response, 2);
            }
        } else {
            list($secondResponseHeaders, $responseBody) = explode("\r\n\r\n", $response, 2);
        }
 
        $responseHeadersDigest = '';
        $responseHeadersSignature = '';
        // extracting digest and signature from headers
        $responseHeadersArray = explode("\r\n", $secondResponseHeaders);
        $responseProtocol = array_shift($responseHeadersArray);
 
        foreach ($responseHeadersArray as $value) {
            list($responseHeadersKeys, $responseHeadersValue) = explode(": ", $value, 2);
            $responseHeaders[strtolower($responseHeadersKeys)] = $responseHeadersValue;
        }
 
        $responseDigestAlgo = '';
        $responseDigestKey = '';
        foreach ($responseHeaders as $key => $value) {
            if ($key === "digest") {
                $responseHeadersDigest = $value;
                // stripping SHA algorithm for later comparison
                list($responseDigestAlgo, $responseDigestKey) = explode("=", $responseHeadersDigest, 2);
            } else if ($key === "signature")
                $responseHeadersSignature = $value;
        }
 
        // calculating response digest
        $responseDigest = base64_encode(hash(strtolower(str_replace("-", "", $responseDigestAlgo)), $responseBody, true));
 
        // checking digest validity
        if ($responseDigest !== $responseDigestKey) {
            errorLog(false, false, "Digests are not the same");
            die();
        }
 
        // extracting variables from signature header
        $signatureArray = explode(",", $responseHeadersSignature);
        foreach ($signatureArray as $value) {
            list($signatureKey, $signatureValue) = explode("=", $value, 2);
            $signature[$signatureKey] = trim($signatureValue, '"');
        }
 
        // generating siging string
        $signingStringArray = explode(" ", $signature['headers']);
        $signingString = '';
        foreach ($signingStringArray as $value) {
            $signingString = $signingString . trim($value) . ": " . trim($responseHeaders[$value]) . "\n";
        }
 
        // trimming last '\n' character
        $signingString = trim($signingString);
 
        // decoding signature
        $decodedSignature = base64_decode($signature['signature']);
 
        // verifying signature
        $signatureVerify = openssl_verify($signingString, $decodedSignature, openssl_get_publickey(file_get_contents($GLOBALS['config']['sign_cert_server'])), 'RSA-SHA256');
 
        if (!$signatureVerify) {
            errorLog(false, false, "Signature is not correct");
            while (($err = openssl_error_string()))
                errorLog(false, false, $err);
            die();
        }
 
        return json_decode($responseBody, true);
    }
 
    return $response;
}
 
/**
* This function logs curl responses and errors in text files
*
* @param mixed $response the response
* @param mixed $request  the request handle, used to get errors
*/
function errorLog($curl, $response, $request = false)
{
    $timestamp = '[' . date('Y-m-d H:i:s') . ']:';
    global $errorLog;
    global $responseLog;
 
    if ($response === false) {
        if ($curl)
            file_put_contents($errorLog, $timestamp . curl_error($request) . "\n", FILE_APPEND);
        else
            file_put_contents($errorLog, $timestamp . $request . "\n", FILE_APPEND);
    } else {
        file_put_contents($responseLog, $timestamp . "\n" . $response . "\n", FILE_APPEND);
    }
}
 
/**
* This function generates the full signature header line from a set of headers, a certificate and the linked private key
*
* @param array  $headersToSign the headers to sign, in the $key => $value format
* @param string $certificate  the certificate linked to the used private key
* @param string $privateKey    the private key used to sign the headers
*
* @return string the full signature header line
*/
function generateSignatureHeader(array $headersToSign, string $certificate, string $privateKey): string
{
    // generating the signing string and header list
    $headers        = '';
    $signatureString = '';
    foreach ($headersToSign as $key => $value) {
        $normalizedHeaderKey = trim(strtolower($key));
        $headers            .= $normalizedHeaderKey . ' ';
        $signatureString    .= $normalizedHeaderKey . ': ' . trim($value) . "\n";
    }
    $headers        = trim($headers);
    $signatureString = trim($signatureString);
 
    // signing the signing string
    $signature = '';
    openssl_sign($signatureString, $signature, openssl_get_privatekey($privateKey), 'RSA-SHA256');
    $signature = base64_encode($signature);
 
    // compiling the header line
    return "keyId=\"$certificate\",algorithm=\"rsa-sha256\",headers=\"$headers\",signature=\"$signature\"";
}
 
/**
* This function takes a code_verifier and outputs the corresponding code_challenge
*
* @param string $codeVerifier the generated code_verifier
*
* @return string the computed code_challenge
*/
function computeCodeChallenge(string $codeVerifier): string
{
    return strtr(rtrim(base64_encode(hash('sha256', $codeVerifier, true)), '='), '+/', '-_');
}
 
/**
* This function takes an optional string length and outputs a random code_verifier string
*
* @param int $length the length of the output code_verifier. Default = 128
*
* @return string the code_verifier
* @throws Exception
*/
function generateCodeVerifier($length = 128): string
{
    $characters = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-._~';
    $outputCode = '';
 
    for ($i = 0; $i < $length; $i++) {
        $index      = random_int(0, strlen($characters) - 1);
        $outputCode .= $characters[$index];
    }
 
    return $outputCode;
}
 
/**
* This function calculates the entropy of a given string
*
* @param string $string  the string for which to calculate the entropy
* @param string $charset a string with all the usable characters. Default = 0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-._~
*
* @return float|int
*/
function computeEntropy(string $string, string $charset = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-._~')
{
    $chars = str_split($charset);
    $probs = [];
 
    foreach ($chars as $char) {
        $probs[$char] = floatval(substr_count($string, $char)) / strlen($string);
    }
 
    $sum = 0.0;
    foreach ($probs as $prob) {
        $sum += $prob != 0 ? $prob * log($prob, 2) : 0.0;
    }
 
    return -$sum;
}
 
/**
* This function calculates the ideal (maximum) entropy for a string of a given length
*
* @param int $length length of the string. Default = 128
*
* @return float|int
*/
function idealEntropy(int $length = 128)
{
    $prob = 1.0 / $length;
 
    return -1.0 * $length * $prob * log($prob, 2);
}
 
/**
* This function is used to initiate the authentication code flow.
*
* @param string $clientID    the client's ID
* @param string $redirectURL  the URL where to redirect after auth
* @param string $authorizeURL the target URL to request authorization
*
* @throws Exception
*/
function login(string $clientID, string $redirectURL, string $authorizeURL): void
{
    global $localTokenFile;
    file_put_contents($localTokenFile, '');
 
    // This unguessable string is used to prevent csrf attacks
    $_SESSION['state'] = bin2hex(random_bytes(16));
 
    // Generate code_verifier and code_challenge for PKCE   
    $_SESSION['code_verifier']  = generateCodeVerifier();
    $_SESSION['code_challenge'] = computeCodeChallenge($_SESSION['code_verifier']);
 
    // required parameters for the redirection, redirect_uri is where the browser should be redirected
    // when the user grants (or denies) access, scope are the authorizations (rights) requested
    $params = [
        'response_type'        => 'code',
        'client_id'            => $clientID,
        'redirect_uri'          => $redirectURL,
        'scope'                => 'default.login',
        'state'                => $_SESSION['state'],
        'code_challenge'        => $_SESSION['code_challenge'],
        'code_challenge_method' => 'S256'
    ];
 
    // redirecting the browser to the AS authorization endpoint to obtain the authorization code
    header('Location: ' . $authorizeURL . '?' . http_build_query($params));
}
 
/**
* This function deletes the access token from the session
*
* @param string $baseURL
*/
function logout(string $baseURL): void
{
    global $localTokenFile;
    file_put_contents($localTokenFile, '');
 
    header('Location: ' . $baseURL);
}
 
function displayMenuLoggedIn(): void
{
    echo '<h2><a href="/">Home</a></h2>';
    echo '<h3>Logged In</h3>';
 
    echo '<form id="view_repos" method="post">';
    echo '<input type="hidden" id="action" name="action" value="view">';
    echo '<p><a href="javascript:{}" onclick="document.getElementById(\'view_repos\').submit(); return false;">View Repos</a></p>';
    echo '</form>';
 
    echo '<form id="logout" method="post">';
    echo '<input type="hidden" id="action" name="action" value="logout">';
    echo '<p><a href="javascript:{}" onclick="document.getElementById(\'logout\').submit(); return false;">Log Out</a></p>';
    echo '</form>';
}
 
function displayMenuLoggedOut(): void
{
    echo '<h2><a href="/">Home</a></h2>';
    echo '<h3>Not logged in</h3>';
    echo '<form id="login" method="post">';
    echo '<input type="hidden" id="action" name="action" value="login">';
    echo '<p><a href="javascript:{}" onclick="document.getElementById(\'login\').submit(); return false;">Log In</a></p>';
    echo '</form>';
}
 
// display client "home" page
if (!isset($_POST['action'])) {
    if (!empty($localToken['access_token'])) {
        displayMenuLoggedIn();
    } else {
        displayMenuLoggedOut();
    }
}
 
if (isset($_POST['action'])) {
    // Building query array for resource dumping
    $repoQueryParameters = [
        'resource_type' => 'user_information',
        'client_id' => $config['client_id']
    ];
    switch ($_POST['action']) {
        case 'login':
            login($config['client_id'], $baseURL, $config['authorize_uri']);
            break;
        case 'logout':
            logout($baseURL);
            break;
        case 'view':
            // call to the resource server to retreive user information
            $resources = apiRequest(
                $config['resource_uri'],
                $repoQueryParameters,
                $localToken,
                false
            );
 
            // Separating headers from body
            list($resourceHeader, $resourceBody) = explode("\r\n\r\n", $resources, 2);
 
            $resourceHeaders = explode("\r\n", $resourceHeader);
            $firstHeaderLine = array_shift($resourceHeaders);
 
            // Displaying user information
            echo '<pre>';
            echo $resourceBody;
            echo '</pre>';
 
            // Automatic refreshing token once access token is expired
            if (strpos($firstHeaderLine, "401")) {
                $errorBody = json_decode($resourceBody, true);
                if ($errorBody['error'] == 'access_denied' && isset($errorBody['hint'])) {
                    if ($errorBody['hint'] == 'Access token could not be verified') {
                        $token = apiRequest(
                            $config['token_uri'],
                            [
                                'grant_type'    => 'refresh_token',
                                'refresh_token' => $localToken['refresh_token'],
                                'client_id'    => $config['client_id'],
                                'client_secret' => empty($config['client_secret']) ? null : $config['client_secret']
                            ],
                            false,
                            true,
                            true
                        );
 
                        // We store the access token in the session so the user is "connected"
                        // Only if the request has been successfully executed
                        if (isset($token['access_token'])) {
                            file_put_contents($localTokenFile, json_encode($token, true));
 
                            // Redirecting the user's browser to the "home" page
                            header('Location: ' . $baseURL);
                        }
                    }
                }
            }
            break;
    }
}
 
// Once the browser has been redirected to the AS to ask for the user's authorization, assuming it has been granted,
// the browser will get back here with a "code" parameter in the query string
if (isset($_GET['code'])) {
    // The AS MUST redirect the browser here with the exact same state parameter we sent it before, so we check if it is indeed the same
    // to detect if the oauth flow has been tampered with
    if (!isset($_GET['state']) || $_SESSION['state'] != $_GET['state']) {
        header('Location: ' . $baseURL . '?error=invalid_state');
        die();
    }
 
    // We communicate directly with the AS to exchange the code received against an access token.
    // The id/secret pair is send to authenticate the client, the redirect_uri is sent to verify the code's validity
    $token = apiRequest(
        $config['token_uri'],
        [
            'grant_type'    => 'authorization_code',
            'client_id'    => $config['client_id'],
            'client_secret' => empty($config['client_secret']) ? null : $config['client_secret'],
            'redirect_uri'  => $baseURL,
            'code'          => $_GET['code'],
            'code_verifier' => $_SESSION['code_verifier']
        ]
    );
 
    // We store the access token in the session so the user is "connected"
    // Only if the request has been successfully executed
    if (isset($token['access_token'])) {
        file_put_contents($localTokenFile, json_encode($token, true));
 
        // Redirecting the user's browser to the "home" page
        header('Location: ' . $baseURL);
    } else {
        echo $token;
    }
    die();
}
</syntaxhighlight>
 
;Ce script a été conçu pour montrer le fonctionnement du mécanisme afin de tester l'implémentation d'un serveur et n'a pas été testé extensivement. Il est conseillé d'utiliser une solution adaptée à un environnement de production.


==Client Credentials==
Ce mécanisme d'autorisation est adapté pour l'automatisation. Il fonctionne de la manière suivante :
*Le client effectue la demande de jeton d'accès au serveur d'autorisation en fournissant ses identifiants.
*Le serveur authentifie le client avec les identifiants fournis et renvoie un jeton d'accès.
*Le client utilise ce jeton d'accès pour accéder à des données via l'API.


;Procédure
*Aller dans '''Gestion > Comptes > Exercice > Déclôturer'''.
*Activer l'interrupteur '''Autoriser la déclôture de l'exercice''', le bouton '''Supprimer''' devient opérationnel sur la première ligne du tableau, qui correspond à la dernière clôture (la date la plus récente).
*Cliquer sur le button '''Supprimer'''.


'''NB''': Il n'est pas permis de déclôturer l'exercice comptable en cas d'une seule clôture comptable.
    +------+                      +-------------+                  +-----------+
    |Client|                      |  Serveur  |                  |  Serveur  |
    +--+---+                      |Autorisation |                  | Ressources|
      |                          +------+------+                  +-----+-----+
      |        Authentification          |                              |
      |        + Demande token          |                              |
      +--------------------------------->|                              |
      |                                  |                              |
      |          Access token            |                              |
      |<---------------------------------+                              |
      |                                  |                              |
      |                      Requête vers|API                            |
      +----------------------------------+------------------------------>|
      |                                  |                              |
      |                                  |                              |
      |                                  |<------------------------------+
      |                                  |  Vérification d'autorisation  |
      |                                  +------------------------------>|
      |                                  |                              |
      |                        Données /|Réponse                        |
      |<---------------------------------+-------------------------------+
      |                                  |                              |


=Éditions=
==[[Compte de résultat]]==


==Grand livre==
;Description
Le [https://fr.wikipedia.org/wiki/Grand_livre grand livre] sert à lister l'ensemble des écritures comptables sur une période donnée dans l'ordre chronologique où ils ont lieu et l'ordre des numéros des flux. Il  présente pour chaque compte :
* Le solde initial
* La liste des écritures
* Le solde final


;Utilisation
===Demande de jeton d'accès===
*Aller dans '''Gestion > Comptes > Gestion > Grand livre'''
Pour obtenir un jeton d'accès, il faut effectuer la requête suivante : <code>POST https://openflyers.com/nom-de-plateforme/oauth/access_token.php</code>. Remplacer <code>nom-de-plateforme</code> par le nom de la plateforme utilisée.
*Choisir la date de début '''du'''
*Choisir la date de fin '''au'''
*Sélectionner le '''Compte d'export de début'''
*Sélectionner le '''Compte d'export de fin'''
*Cliquer sur le bouton '''Sauver dans un fichier CSV'''


'''/!\ Le grand livre n'affiche que les comptes ayant eu au moins une écriture comptable sur la période donnée.'''
Les paramètres suivants sont nécessaires :
{| class="wikitable"
!Nom!!Type!!Description
|-
|client_id||string||L'identifiant unique reçu pendant l'enregistrement du client.
|-
|client_secret||string||La passphrase reçue pendant l'enregistrement du client.
|-
|grant_type||string||Le mécanisme d'autorisation utilisé. Ici, la valeur doit être ''client_credentials''.
|-
|scope||string||[[#Liste-des-scopes-disponibles|Liste des droits demandés par le client]], séparé par des espaces.
|}


=Exporter la comptabilité=
;Ce mécanisme a la particularité de ne pas nécessiter d'URI de redirection, il n'est donc pas utile d'en renseigner un lors de l'enregistrement d'un client.
''Conseil OpenFlyers : l'export de la comptabilité nécessite au préalable des actions irréversibles comme la validation de l'ensemble des écritures. Pour les personnes débutantes avec cette fonction et qui souhaite pouvoir exporter sans effectuer ces actions irréversibles, il est recommandé de [[Compte-client-OpenFlyers-et-modèle-commercial#Plateforme-supplémentaire-de-test|s'entrainer avec le bac à sable]] en effectuant une recopie de sa plateforme de production.''


Pour les exports comptable, la date de début d'export proposée par défaut correspond à la date du mouvement le plus vieux qui n'ait pas été encore exporté.
Si la requête est correcte, un jeton d'accès (Access Token) est fourni en réponse dans un objet au format JSON.
==Actions à effectuer par ordre chronologique==
<syntaxhighlight lang="javascript">
OpenFlyers permet d'exporter uniquement les [[Bien débuter avec OpenFlyers#Validation_des_écritures|écritures validées]] et ainsi ne propose par défaut comme période d'export que la période maximum exportable ne contenant que des écritures validées.
{
  "token_type": "Bearer",
  "expires_in": 3600,
  "access_token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJSU-G7fOBOYzOgioSGNIQKI2A2OxjsNBbOOoaHsqNpsQxWZse3rofExAaGKh2tXbeuz1YAVhdLUGYgq-oKRK4ONFhw2NvcRf3QPxQXZImLWw"
}
</syntaxhighlight>


Les exports se font généralement à intervalle régulier : tous les mois, tous les trimestres ou tous les ans.
===Script client : Client Credentials===
Voici un exemple simple d'un client OAuth2 pour le mécanisme d'authentification par les identifiants client (Client Credentials).


Si vous n'avez pas de logiciel de comptabilité, il est recommandé d'effectuer quand même l'export et de l'archiver sur un support de stockage (disque dur par exemple) pour conservation.
Fichier de configuration ''config.clientcred.json'' :
<syntaxhighlight lang="javascript">
{
  "client_id": "",
  "client_secret": "",
  "token_uri": "https://openflyers.com/nom-de-plateforme/oauth/access_token.php",
  "resource_uri": "https://openflyers.com/nom-de-plateforme/oauth/resources.php",
  "revoke_uri": "https://openflyers.com/nom-de-plateforme/oauth/revoke.php",
  "auth_cert": "/path/to/client/auth_cert.crt",
  "auth_key": "/path/to/client/auth.key",
  "sign_cert": "/path/to/client/sign_cert.crt",
  "sign_key": "/path/to/client/sign.key",
  "auth_cacert": "/path/to/ca.crt",
  "sign_cert_server": "/path/to/server/sign_cert_server.crt"
}
</syntaxhighlight>
Où <code>/path/to/client/</code> est le chemin d'accès vers le dossier qui contient le code source du client de démonstration.
*Exemple sur '''windows''': <code>C:/wamp64/www/4.0/oauth-demo/ssl/ClientCredDemo/</code>.
*Exemple sur un serveur Linux '''debian''': <code>./ssl/ClientCredDemo/</code>.


Dans le cas du dernier export d'un exercice comptable donné, il est important de décider avant l'export si tout ou partie des écritures de fin d'exercice doivent être réalisées avant ou après. En général, si l'export sert ensuite à être retravaillé dans un logiciel de comptabilité, alors les écritures de fin d'exercice seront réalisées dans le logiciel de comptabilité et il ne faudra faire le "nettoyage" côté OpenFlyers qu'à l'issue de l'export comptable officiel. Ainsi les points 2 et 3 des "Opérations à effectuer avant tout export" ci-dessous doit être, dans certains cas, réalisé en partie ou en totalité après le point 2 des "Opérations supplémentaires pour l'export de fin d'exercice" ci-dessous.
Ce fichier de configuration doit se situer au même niveau que le script PHP dans le système de fichiers.


De plus, si vous utilisez un logiciel de comptabilité uniquement pour le bilan, c'est à dire que les charges sont gérées dans OpenFlyers, alors la clôture dans OpenFlyers doit être faite après la clôture dans ce logiciel de comptabilité. Il est ainsi possible de vérifier que les "A nouveaux" sont identiques dans les 2 logiciels et de corriger dans OpenFlyers si nécessaire. Il existe un [[OF-doc-en:Accounting_exports_3#Carry_forwards|rapport qui permet d'avoir les à nouveaux]].
Script php :
<syntaxhighlight lang="php">
<?php
$localTokenFile    = 'token.json';
// create token file if it does not exist
if (!file_exists($localTokenFile))
    file_put_contents($localTokenFile, '');


Prérequis à tout export comptable :
$config                = json_decode(file_get_contents('config.clientcred.json'), true);
*[[Configuration de la comptabilité#Comptes_d'export|Attribuer les comptes d'export]] pour ne pas avoir l'alerte "[[#Vous_ne_pouvez_exporter_car_il_y_a_des_comptes_sans_valeur_d'export|Vous ne pouvez exporter car il y a des comptes sans valeur d'export]]".
$localToken            = json_decode(file_get_contents($localTokenFile), true);
$GLOBALS['config']     = $config;


Opérations à effectuer pour tout export :
// Build the baseURL, accounting for protocol, port, name and endpoint. Used for redirection
#[[#Retrouver-les-écritures-non-validées|Retrouver les écritures non validées]] pour [[Bien débuter avec OpenFlyers#Validation_des_écritures|les valider]]
$protocol = isset($_SERVER['HTTPS']) ? 'https://' : 'http://';
#[[#Export_de_la_comptabilité|Export de la comptabilité]]
$port    = ($protocol == 'https://' && $_SERVER['SERVER_PORT'] == 443)
#[[Import dans un logiciel comptable tiers|import dans un logiciel de comptabilité]]
    || ($protocol == 'http://' && $_SERVER['SERVER_PORT'] == 80) ? '' : ':' . $_SERVER['SERVER_PORT'];


Opérations supplémentaires pour l'export de fin d'exercice :
$baseURL            = $protocol . $_SERVER['SERVER_NAME'] . $port . $_SERVER['PHP_SELF'];
#[[Écritures comptables#Écritures_de_fin_d'exercice_avant_la_clôture_de_la_comptabilité|Écritures de fin d'exercice avant la clôture de l'exercice]] dont le [[#Solder-les-comptes|solde des comptes d'exploitation]]
$errorLog          = 'error_log.log';
#Validation des écritures de fin d'exercice par la [[Utilisation de la comptabilité#Validation_d'un_flux|validation des flux]] correspondant.
$responseLog        = 'response_log.log';
#Ultime export pour stockage [[#Marquer_les_écritures_lors_de_l'export|en marquant les écritures]].
$GLOBALS['baseURL'] = $baseURL;
#[[#Clôturer-l'exercice-comptable|Clôture / ouverture du nouvel exercice comptable]].
#[[Écritures comptables#Écritures_de_début_d'exercice_après_l'ouverture_de_la_comptabilité|Écritures de début d'exercice après l'ouverture de la comptabilité]].


==Alertes générées lors d'une tentative d'[[Utilisation de la comptabilité#Export-de-la-comptabilité|export comptable]]==
$replacementListItems = [
===Vous ne pouvez exporter car il y a des comptes sans valeur d'export===
    1 => "year",
Cette alerte bloquante apparait dans '''Comptes > Exporter''' avec les éléments suivants :
    2 => "validityTypeId",
<pre>Vous ne pouvez exporter car il y a des comptes sans valeur d'export
    3 => "icao",
intitulé de compte X (type de compte Y)</pre>
    4 => "profileId",
Pour supprimer cette alerte, il faut [[Configuration de la comptabilité#Comptes_d'export|attribuer les comptes d'export manquants]].
    7 => "accountingId",
    8 => "paymentType",
    9 => "startDate",
    10 => "endDate",
    11 => "occupiedSeat",
    12 => "date",
    13 => "activityTypeId",
    14 => "age",
    15 => "resourceId",
    16 => "personId",
    17 => "accountId",
    20 => "rightPlacePersonId",
    21 => "month",
    22 => "numberMonth",
    23 => "oneValidityTypeId",
];


==Export de la comptabilité==
//Session cookies are used to store information necessary for the authorization code flow
Pré-requis : vérifier que le format d'export pour votre logiciel est présent dans le tableau '''Format de l'export'''. S'il n'est pas présent, [[Configuration#Formats-d'export-à-afficher|le rendre visible depuis la page Configuration]].
session_start();


Pour exporter la comptabilité, il faut :
/**
*Aller dans '''Gestion > Comptes > Gestion > Exporter'''
* This function is used to make api calls to the RS
*
* @param      $url
* @param      $post
* @param array $headers
*
* @return mixed
*/
function apiRequest($url, $post = null, $token = false, $auth = true, $headers = array())
{
    $ch = curl_init($url);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    curl_setopt($ch, CURLOPT_HEADER, true);


Dans la première colonne, le tableau '''Statistiques''' donne des informations sur les écritures. Sa lecture est indispensable pour vérifier qu'il ne subsiste pas d'écriture non validée antérieures à la fin de la période d'export souhaitée. Si c'est le cas, il faut alors [[#Retrouver-les-écritures-non-validées|trouver les écritures non validées]] et les valider.
    $method = 'get';


Dans la deuxième colonne:
    if ($post) {
*Choisir la '''Comptabilité''' à exporter.
        $method  = 'post';
*Indiquer la période que l'on souhaite exporter.
        $postData = http_build_query($post);
        curl_setopt($ch, CURLOPT_POST, true);
        curl_setopt($ch, CURLOPT_POSTFIELDS, $postData);


Attention: la période proposée par défaut correspond à la plus grande période possible et ne correspond donc pas à la période souhaitée. Il faut donc modifier la date de fin.
        $headersToSign['Content-Type'] = 'application/x-www-form-urlencoded';
        $headersToSign['digest']      = 'SHA-256=' . base64_encode(hash('sha256', $postData, true));
    }


Exemples :
    $urlComponents = parse_url($url);
*Si on souhaite exporter '''une année civile complète''', alors il faut sélectionner comme date de fin le 31 décembre.
*Si on souhaite exporter '''un mois''', il faut sélectionner comme date de fin le dernier jour du mois concerné.


Il est également possible de marquer les écritures exportées afin qu'elles ne soient plus ré-exportées. Cependant, à la place de cette zone de saisie, il peut apparaitre la liste des comptes sans [[#Compte_d'export|valeur d'export]] c'est à dire sans le numéro de compte du plan comptable. Dans ce cas, il faut d'abord rechercher le compte concerné dans '''Comptes > Lister les comptes''' puis attribuer au compte concerné une valeur d'export.
    $headersToSign['(request-target)'] = $method . ' ' . $urlComponents['path'];
*Cocher ou non '''[[#Marquer_les_écritures_lors_de_l'export|Marquer les écritures]]'''.
    $headersToSign['Host']            = $urlComponents['host'];
*Dans la troisième colonne, sélectionner le format d'export souhaité.
    $headersToSign['Date']             = gmdate('D, j M Y H:i:s T');
*L'export s'effectue en cliquant sur le bouton '''Exporter'''.
*Il faut ensuite [[Import dans un logiciel comptable tiers|importer dans un logiciel comptable tiers]].


'''Remarques :'''
    // generating the signature header
*Sauf à avoir cocher la case '''Ecritures non validées''', il n'est possible d'exporter que des écritures validées. Par conséquent, s'il subsiste des écritures non validées sur la période d'export souhaitée, alors une alerte apparait signalant qu'il n'est pas possible d'exporter la période souhaitée.
    $keyId                = openssl_x509_fingerprint(file_get_contents($GLOBALS['config']['sign_cert']));
*Dans le fichier d'export, les écritures sont triées par numéro de flux.
    $privateKey          = file_get_contents($GLOBALS['config']['sign_key']);
    $headers['Signature'] = generateSignatureHeader($headersToSign, $keyId, $privateKey);


===Exporter les écritures marquées comme exportées===
    $headers['Accept']     = 'application/json';
Il est possible de ré-exporter les écritures marquées comme ayant été déjà exportées (et [[#Marquer-les-écritures-lors-de-l'export|marquées comme tel]]) en cochant '''Ecritures marquées comme exportées''' dans la colonne '''Écriture'''. En cochant cette case, un message d'alerte apparait indiquant ''Attention : vous allez exporter des écritures marquées, elles ont sans doute déjà étaient importées dans la comptabilité réelle.''.
    $headers['User-Agent'] = $GLOBALS['baseURL'];
    unset($headersToSign['(request-target)']);
    $headers              += $headersToSign;


===Exporter les écritures non validées===
    if ($token) {
Il est possible d'exporter les écritures non validées en cochant '''Ecritures non validées''' dans la colonne '''Écriture'''. En cochant cette case, un message d'alerte apparait indiquant ''Attention : vous allez exporter des écritures non validées, elles ne devraient pas être importées dans la comptabilité réelle.''.
        $headers['Authorization'] = $token['token_type'] . ' ' . $token['access_token'];
    }


===Marquer les écritures lors de l'export===
    // formatting the headers
Lors d'un export comptable, OpenFlyers donne la possibilité de marquer ou non les écritures qui vont être exportées. Cela permet :
    $httpFormattedHeaders = [];
*Soit de pouvoir ré-exporter des écritures déjà exportées (mais non marquées)
    foreach ($headers as $key => $value) {
*Soit au contraire de ne plus exporter des écritures dont OpenFlyers sait qu'elles ont déjà été exportées car marquées.
        $httpFormattedHeaders[] = trim($key) . ': ' . trim($value);
    }


Ce choix se fait dans le formulaire d'export en cochant '''Marquer les écritures (elles ne pourront plus être exportées de nouveau)'''.
    curl_setopt($ch, CURLOPT_HTTPHEADER, $httpFormattedHeaders);
    curl_setopt($ch, CURLINFO_HEADER_OUT, true);


Il est conseillé de toujours procéder à un 1er export sans marquer les écritures. Ainsi, si l'export se passe mal, on aura la garantie de pouvoir le refaire.
    // for development environment only
    //curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
    //curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
    //curl_setopt($ch, CURLOPT_VERBOSE, true);


=Importer un relevé bancaire=
    // defining TLS client certificates for Mutual TLS
'''Attention :''' avant d'effectuer un import de relevé bancaire en production, il est vivement recommandé de le faire sur la [[Compte-client-OpenFlyers-et-modèle-commercial#Plateforme-supplémentaire-de-test-(bac-à-sable)|plateforme bac à sable]]. En effet, un import de relevé bancaire génère de nombreuses écritures qu'il peut être ensuite fastidieux de supprimer à la main en cas de mauvais import.
    curl_setopt($ch, CURLOPT_SSLVERSION, CURL_SSLVERSION_TLSv1_2);
    curl_setopt($ch, CURLOPT_CAINFO, $GLOBALS['config']['auth_cacert']);
    curl_setopt($ch, CURLOPT_SSLCERT, $GLOBALS['config']['auth_cert']);
    curl_setopt($ch, CURLOPT_SSLKEY, $GLOBALS['config']['auth_key']);


De la même façon, lors des tests, il est recommandé d'importer qu'une petite période comptable (quelques jours) afin de ne pas générer de trop nombreuses écritures qui peuvent rendre totalement illisibles la consultation des comptes.
    $response = curl_exec($ch);


Pour accéder à cette fonctionnalité il faut disposer des droits de [[Gestion-des-profils#Gestion-des-comptes|Gestion des comptes]] ou [[Gestion-des-profils#Saisir-tout-flux|Saisir tout flux]].
    // logging errors and responses
    errorLog(true, $response, $ch);


Il est recommandé de saisir les [[Gestion-des-achats#Saisie-d'une-facture-fournisseur|factures fournisseurs]] avant d'effectuer l'import du relevé bancaire conformément au [[Comptabilité#Règles-de-saisies-de-la-comptabilité-courante|règles de saisies de la comptabilité courante]].
    curl_close($ch);


Voir également le chapitre [[Conseils-spécifiques-pour-la-comptabilité#Rapprochement-des-encaissements|Rapprochement des encaissements]] concernant la ventilation des écritures comptables liées à des encaissements.
    // in case an authentication request is executed
    if ($auth) {
        list($responseHeader, $responseBody) = explode("\r\n\r\n", $response, 2);


OpenFlyers propose plusieurs méthodes d'import des relevés bancaires :
        $responseHeadersDigest = '';
*[[#Importer-par-fichier|Importer par fichier]] : il s'agit de la méthode historique d'import. Elle présente plusieurs inconvénients et est désormais déconseillée. OpenFlyers n'effectue plus d'évolution sur cette fonctionnalité et n'assure pas de support.
        $responseHeadersSignature = '';
*[[#Importer-par-API|Importer API]] : il s'agit de la nouvelle méthode recommandée par OpenFlyers. Elle nécessite d'[[Configuration-de-la-comptabilité#Agréger-un-compte-bancaire|avoir agrégé un compte bancaire]] et peut être complètement automatisée en activant la synchronisation automatique 1 fois par jour.
        // extracting digest and signature from headers
        $responseHeadersArray = explode("\r\n", $responseHeader);
        $responseProtocol = array_shift($responseHeadersArray);


==Importer par API==
        foreach ($responseHeadersArray as $value) {
;Vidéo tutorielle
            list($responseHeadersKeys, $responseHeadersValue) = explode(": ", $value, 2);
[https://youtu.be/VVCdPWlwglM Importer un relevé bancaire] : à partir de la minute 2'37 uniquement pour le paramétrage de l'imputation automatique.
            $responseHeaders[strtolower($responseHeadersKeys)] = $responseHeadersValue;
        }


        $responseDigestAlgo = '';
        $responseDigestKey = '';
        foreach ($responseHeaders as $key => $value) {
            if ($key === "digest") {
                $responseHeadersDigest = $value;
                // stripping SHA algorithm for later comparison
                list($responseDigestAlgo, $responseDigestKey) = explode("=", $responseHeadersDigest, 2);
            } else if ($key === "signature")
                $responseHeadersSignature = $value;
        }


;Prérequis
        // calculating response digest
*Avoir [[Configuration-de-la-comptabilité#Agréger-un-compte-bancaire|agrégé un ou plusieurs compte(s) bancaire(s)avec des comptes de trésorerie]]
        $responseDigest = base64_encode(hash(strtolower(str_replace("-", "", $responseDigestAlgo)), $responseBody, true));
*Les [[Configuration-de-la-comptabilité#Règles-d'imputations-automatiques|règles d'imputation]] doivent être paramétrées


        // checking digest validity
        if ($responseDigest !== $responseDigestKey) {
            errorLog(false, false, "Digests are not the same");
            die();
        }


;Procédure
        // extracting variables from signature header
Aller dans '''Gestion > Comptes > Relevé bancaire > Importer par API''':
        $signatureArray = explode(",", $responseHeadersSignature);
*'''Compte de trésorerie''' : Sélectionner le compte de trésorerie qui va être mis à jour par import du relevé du compte bancaire agrégé avec ce compte.
        foreach ($signatureArray as $value) {
*'''Lettrer automatiquement les écritures importées''' : en cochant cette case, le [[#Lettrage-automatique|lettrage automatique]] des écritures générées par l'import s'effectue
            list($signatureKey, $signatureValue) = explode("=", $value, 2);
*Cliquer sur le bouton '''Importer'''
            $signature[$signatureKey] = trim($signatureValue, '"');
Les écritures générées via l'import par API sont identifiées dans la liste des mouvements par l'icône [[File:b-letter-bank-symbol.png|10px]].
        }


        // generating siging string
        $signingStringArray = explode(" ", $signature['headers']);
        $signingString = '';
        foreach ($signingStringArray as $value) {
            $signingString = $signingString . trim($value) . ": " . trim($responseHeaders[$value]) . "\n";
        }


;Messages d'erreurs possibles
        // trimming last '\n' character
En cas d'échec de l'import, des messages d'alertes peuvent s'afficher :
        $signingString = trim($signingString);
*'''Ce compte n'est plus agrégé, refaire le processus d'agrégation'''
**[[Configuration-de-la-comptabilité#Agréger-des-comptes-bancaires|Supprimer l'agrégation et la réinitialiser]]
*'''Le compte trésorerie Compte courant (512000) n'a aucun compte agrégé associé: Connecter et agréger un compte'''
**S'assurer que le compte de trésorerie est correctement affecté en accédant à '''Admin > Comptes > Banque > Agréger'''
*'''Il n'est pas possible de saisir une écriture comptable avec une date antérieure à la date de début de l'exercice comptable'''
:Pour corriger ce problème, il y a 2 possibilités :
:*Soit modifier la date de début d'agrégation pour qu'elle soit postérieure à la date de début de l'exercice comptable :
:**Aller dans '''Admin > Comptes > Banque > Agréger'''
:**Modifier la '''date de début d'import''' en conséquence
:*Soit modifier la date de début de l'exercice comptable pour qu'elle soit antérieure à la date de début d'import :
:**[[Utilisation-de-la-comptabilité#Déclôturer-l'exercice-comptable|Déclôturer le dernier exercice comptable]]
:**Retenter l'[[#Importer-par-API|import par API]]
:**Temps que le même message d'erreur s'affiche, renouveler les 2 actions précédentes
:**[[Utilisation-de-la-comptabilité#Clôturer-l'exercice-comptable|Reclôturer l'exercice comptable]]


        // decoding signature
        $decodedSignature = base64_decode($signature['signature']);


;Messages d'avertissements possibles
        // verifying signature
*Si lors de l'import des lignes ne sont pas importées car déjà présentes en base de données, un avertissement du type suivant s'affiche :
        $signatureVerify = openssl_verify($signingString, $decodedSignature, openssl_get_publickey(file_get_contents($GLOBALS['config']['sign_cert_server'])), 'RSA-SHA256');
<pre>20 lignes comprises entre 01-03-2023 et 30-03-2023 n'ont pas été importées</pre>
*Si le compte n'a aucune transaction à importer un message d'avertissement en orange s'affiche :
<pre>La liste des transactions est vide dans la période souhaitée</pre>
:Dans ce cas, deux options sont possibles :
:*Soit, le compte n'a réellement aucune transaction enregistrée côté banque pour la période souhaitée. Donc, il faudra attendre les futures transactions pour les importer.
:*Soit, la date indiquée dans "Date de début d'import" est incorrecte. Il faudra alors saisir la date correcte en accédant à '''Admin > Comptes > Banque > Agréger'''.


==Importer par fichier==
        if (!$signatureVerify) {
;Vidéo tutorielle
            errorLog(false, false, "Signature is not correct");
[https://youtu.be/VVCdPWlwglM Importer un relevé bancaire]
            while (($err = openssl_error_string()))
                errorLog(false, false, $err);
            die();
        }


        return json_decode($responseBody, true);
    }


;Prérequis
    return $response;
*L'export du relevé doit avoir été réalisé (voir les procédures pour chaque dans la page [[Exporter un relevé bancaire depuis un site internet de banque]])
}
*Les [[Configuration-de-la-comptabilité#Règles-d'imputations-automatiques|règles d'imputation]] doivent être paramétrées


/**
* This function logs curl responses and errors in text files
*
* @param mixed $response the response
* @param mixed $request  the request handle, used to get errors
*/
function errorLog($curl, $response, $request = false)
{
    $timestamp = '[' . date('Y-m-d H:i:s') . ']:';
    global $errorLog;
    global $responseLog;


;Procédure
    if ($response === false) {
*'''Gestion > Comptes > Relevé bancaire > Importer par fichier'''
        if ($curl)
*'''Fichier''' : Sélectionner le fichier à importer
            file_put_contents($errorLog, $timestamp . curl_error($request) . "\n", FILE_APPEND);
*'''Format d'import''' : Sélectionner le [[Modèles-de-format-d'import-de-relevé-bancaire#Format-d'import|format]] correspondant au fichier à importer. La sélection est mémorisée pour les imports suivants.
        else
*'''Compte trésorerie''' : Sélectionner le compte de trésorerie qui va être débité. Lorsque le montant est négatif, ce compte est crédité.
            file_put_contents($errorLog, $timestamp . $request . "\n", FILE_APPEND);
:Le compte affiché par défaut est déterminé dans l'ordre suivant :
    } else {
:#Compte utilisé lors du précédent import
        file_put_contents($responseLog, $timestamp . "\n" . $response . "\n", FILE_APPEND);
:#Compte ayant le plus petit numéro d'export comptable
    }
*'''Lettrer automatiquement les écritures importées''' : en cochant cette case, le [[#Lettrage-automatique|lettrage automatique]] des écritures générées par l'import s'effectue
}
*Cliquer sur le bouton '''Importer'''


* S'il y a un message d'erreur '''Ligne : xxx déjà présente en base de données''', l'import n'est pas réalisé afin de ne pas générer de doublons (cf. ci-dessous dans la partie '''Messages d'erreurs possibles''').
/**
* This function generates the full signature header line from a set of headers, a certificate and the linked private key
*
* @param array  $headersToSign the headers to sign, in the $key => $value format
* @param string $certificate  the certificate linked to the used private key
* @param string $privateKey    the private key used to sign the headers
*
* @return string the full signature header line
*/
function generateSignatureHeader(array $headersToSign, string $certificate, string $privateKey): string
{
    // generating the signing string and header list
    $headers        = '';
    $signatureString = '';
    foreach ($headersToSign as $key => $value) {
        $normalizedHeaderKey = trim(strtolower($key));
        $headers            .= $normalizedHeaderKey . ' ';
        $signatureString    .= $normalizedHeaderKey . ': ' . trim($value) . "\n";
    }
    $headers        = trim($headers);
    $signatureString = trim($signatureString);


Lors de l'import, des [[Comptabilité#Flux|flux]] sont générés. Ils sont définis en fonction des [[Configuration-de-la-comptabilité#Règles-d'imputations-automatiques|règles d'imputation]]. Il est ensuite possible de modifier ces écritures en [[Utilisation-de-la-comptabilité#Modifier-un-flux|modifiant les flux]] depuis la [[Gestion-de-la-comptabilité-côté-utilisateur#Extrait-de-compte|liste des mouvements]] ou le détail des comptes imputés.
    // signing the signing string
    $signature = '';
    openssl_sign($signatureString, $signature, openssl_get_privatekey($privateKey), 'RSA-SHA256');
    $signature = base64_encode($signature);


Le compte de banque sélectionné lors de l'import contient une ligne de débit ou de crédit pour chaque flux généré par l'import.
    // compiling the header line
    return "keyId=\"$certificate\",algorithm=\"rsa-sha256\",headers=\"$headers\",signature=\"$signature\"";
}


/**
* This function deletes the access token from the session
*
* @param string $baseURL
*/
function logout(string $baseURL): void
{
    global $localTokenFile;
    file_put_contents($localTokenFile, '');


;Messages d'erreurs possibles
    header('Location: ' . $baseURL);
En cas d'échec de l'import, des messages d'alertes peuvent s'afficher :
}
*'''Ligne : xxx déjà présente en base de données'''
*:Ce message d'erreur peut être généré lors de l'utilisation de la fonctionnalité d'import de relevé bancaire.
*:Il indique que la ligne affichée est déjà présente en base de données.
*:Dans ce cas, aucune ligne du fichier n'est chargée en base de données.
*:En cas de présence de cette erreur, vérifier que la période d'export de votre relevé bancaire ne chevauche pas la période de l'export précédent.


=Lettrer des écritures comptables=
function displayMenuLoggedIn(): void
Tutoriel OpenFlyers : [https://www.youtube.com/watch?v=Bgb32Ay6TVU Lettrer des écritures comptables]
{
    global $replacementListItems;
    echo '<h2><a href="/">Home</a></h2>';
    echo '<h3>Logged In</h3>';


Le [https://fr.wikipedia.org/wiki/Lettrage_comptable lettrage] est une opération comptable qui consiste à affecter un seul repère à 2 ou plusieurs entrées comptables dans ce compte. la somme des entrées au crédit devant être égale à la somme des entrées au débit.
    echo '<form id="view_repos" method="post">';
    echo '<label for="report_id">Report ID: </label>';
    echo '<input type="number" id="report_id" name="report_id"><br><br>';
    foreach ($replacementListItems as $value) {
        echo '<label for="' . $value . '">' . $value . ': </label>';
        echo '<input type="text" id="' . $value . '" name="' . $value . '"><br><br>';
    }
    echo '<input type="hidden" id="action" name="action" value="view">';
    echo '<p><a href="javascript:{}" onclick="document.getElementById(\'view_repos\').submit(); return false;">View Repos</a></p>';
    echo '</form>';


Pour pouvoir effectuer le lettrage il faut disposer du droit [[Gestion-des-profils#Gestion-des-comptes|Gestion des comptes]].
    echo '<form id="logout" method="post">';
    echo '<input type="hidden" id="action" name="action" value="logout">';
    echo '<p><a href="javascript:{}" onclick="document.getElementById(\'logout\').submit(); return false;">Log Out</a></p>';
    echo '</form>';
}


==Annulation automatique du lettrage==
function displayMenuLoggedOut(): void
Dans le cas où un flux comptable est modifié ou annulé, les écritures comptables lettrées avec les écritures du flux concerné sont automatiquement délettrées.
{
    echo '<h2><a href="/">Home</a></h2>';
    echo '<h3>Not logged in</h3>';
    echo '<form id="login" method="post">';
    echo '<input type="hidden" id="action" name="action" value="login">';
    echo '<p><a href="javascript:{}" onclick="document.getElementById(\'login\').submit(); return false;">Log In</a></p>';
    echo '</form>';
}


==Lettrage automatique==
// display client "home" page
Le lettrage automatique se fait lors de l'[[#Importer-un-relevé-bancaire|import de relevés bancaires]] en cochant la case à cocher '''Lettrer automatiquement les écritures comptables''' de l'interface correspondante. Un robot va alors essayer de trouver des écritures à lettrer. Son fonctionnement est le suivant :
if (!isset($_POST['action'])) {
*Génération de l'écriture comptable
    if (!empty($localToken['access_token'])) {
*Recherche d'une écriture comptable ayant le montant inverse (somme des crédits et débits égal à 0) et appartenant au même compte
        displayMenuLoggedIn();
*Création d'un lettrage pour les deux écritures correspondantes ainsi qu'une date de lettrage
    } else {
*Enregistrement de l'écriture dans la base de données.
        displayMenuLoggedOut();
Pour la recherche de l'écriture inverse, le robot se limite à X jours avant et Y jours après la date de l'écriture d'origine. X et Y sont définis dans le [[Configuration-de-la-comptabilité#Paramétrage-général-de-la-gestion-des-comptes|Paramétrage général de la gestion des comptes]].
    }
}


Si plusieurs écritures ont été trouvées dans la base de données comme correspondante alors le lettrage n'est pas fait. Si plusieurs écritures générées ont la même écriture correspondante seule, la première est lettrée.
if (isset($_POST['action'])) {
    $replacementList = [];
    // Building replacement list
    foreach ($replacementListItems as $key => $value) {
        $replacementList[$value] = (isset($_POST[$value]) && strlen($_POST[$value]) > 0) ? $_POST[$value] : null;
    }
    // Building query array
    $repoQueryParameters = [
        'resource_type' => 'generic_report',
        'client_id' => $config['client_id'],
        'report_id' => (isset($_POST['report_id']) && strlen($_POST['report_id']) > 0) ? $_POST['report_id'] : null,
        'replacementList' => $replacementList
    ];
    switch ($_POST['action']) {
        case 'login':
            $token = apiRequest(
                $config['token_uri'],
                [
                    'grant_type'    => 'client_credentials',
                    'client_id'    => $config['client_id'],
                    'client_secret' => empty($config['client_secret']) ? null : $config['client_secret'],
                    'scope'        => 'genericreports.readonly reports.readonly'
                ]
            );
            // We store the access token in the session so the user is "connected"
            // Only if the request has been successfully executed
            if (isset($token['access_token'])) {
                file_put_contents($localTokenFile, json_encode($token, true));


==Lettrer manuellement==
                // Redirecting the user's browser to the "home" page
Le lettrage manuel est fait par l'utilisateur via l'interface '''Gestion > Comptes › Comptes > X'''. Pour ajouter un lettrage à une écriture il faut :
                header('Location: ' . $baseURL);
*Cliquer sur le bouton d'édition du lettrage en haut de la colonne lettrage
            } else {
*Cliquer sur les écritures que l'on souhaite associer
                echo $token;
*Chaque entrée sélectionnée est ajoutée au panier des écritures en attente de lettrage affiché en haut de l'extrait de compte. Le contenu de ce panier est préservé en affichant d'autres pages d'extraits de compte du compte concerné. Cela permet d'associer des écritures comptables réparties sur des dates éloignées.
            }
*Re-cliquer sur le bouton d'édition.
            break;
OpenFlyers va alors s'assurer que la somme des entrées au crédit est égale à la somme des entrées au débit. Si c'est le cas alors le lettrage est effectué.
        case 'logout':
            logout($baseURL);
            break;
        case 'view':
            // call to the resource server
            $resources = apiRequest(
                $config['resource_uri'],
                $repoQueryParameters,
                $localToken,
                false
            );


Seules les écritures n'ayant pas encore de lettrage peuvent être sélectionnées par l'utilisateur.
            // Separating headers from body
            list($resourceHeader, $resourceBody) = explode("\r\n\r\n", $resources, 2);


Pour supprimer un lettrage, il faut :
            $resourceHeaders = explode("\r\n", $resourceHeader);
*Cliquer sur le bouton de dissociation en haut de la colonne lettrage
            $firstHeaderLine = array_shift($resourceHeaders);
*Cliquer sur les écritures que l'on souhaite dissocier.
*Re-cliquer sur le bouton d'édition.


L'ensemble des écritures sélectionnées n'auront alors plus de lettrage ainsi que leurs écritures correspondantes. Si l'utilisateur a sélectionné deux écritures correspondantes, leur lettrage sera supprimé comme si une seul avait été sélectionnée.
            // Building form for download CSV button
            echo '<form method="post">';
            foreach ($_POST as $key => $value) {
                if ($key == "action") {
                    $value = "download";
                }
                echo '<input type="hidden" id="' . $key . '" name="' . $key . '" value="' . $value . '">';
            }
            if (isset($_POST['report_id']) && strlen($_POST['report_id']) > 0 && !isset(json_decode($resourceBody, true)['error'])) {
                echo '<button>Download as CSV</button>';
            }
            echo '</form>';


Ainsi si l'on souhaite modifier le lettrage d'une écriture il faut dans un premier temps supprimer son lettrage puis en ajouter un nouveau.
            // Displaying requested content
            echo '<pre>';
            echo (strpos($firstHeaderLine, "200")) ? json_decode($resourceBody) : $resourceBody;
            echo '</pre>';
            break;
        case 'download':
            // call to the resource server
            $resources = apiRequest(
                $config['resource_uri'],
                $repoQueryParameters,
                $localToken,
                false
            );


=Mettre à jour les tarifs=
            // Separating headers from body
==Mettre à jour les tarifs associés à des produits==
            list($resourceHeader, $resourceBody) = explode("\r\n\r\n", $resources, 2);
*'''Gestion > Ventes > Produits'''
            $resourceHeaders = explode("\r\n", $resourceHeader);
            array_shift($resourceHeaders);


*Les tarifs sont définis dans la partie droite du tableau à partir de la colonne '''Tarif associé'''.
            // Dumping headers to insert them in current page
[[Image:Tableau_produits_actualisation_tarifs.png|800px]]
            foreach ($resourceHeaders as $key => $value) {
*Dans '''Historique des montants''', sélectionner '''Nouveau couple...''' pour créer un nouveau tarif.
                header($value);
*Saisir un nouveau '''Montant du tarif''' et la nouvelle '''Date d'entrée en vigueur du tarif''' à partir de laquelle le tarif s'appliquera. Vous pouvez anticiper un changement de tarif. Le tarif s'appliquera à la date du mouvement.
            }
            echo json_decode($resourceBody);
            break;
    }
}
</syntaxhighlight>


;Ce script a été conçu pour montrer le fonctionnement du mécanisme et tester l'implémentation d'un serveur, il n'a pas été testé extensivement. Il est conseillé d'utiliser une solution adaptée à un environnement de production.


'''Attention''': si vous sélectionnez un couple existant dans '''Historique des montants''' et que vous modifiez le montant ou la date d'entrée en vigueur, alors vous changez le prix de cette période mais vous ne créez pas un nouveau tarif. Vous perdrez toute possibilité de corriger par exemple un vol à une date donnée.  
==Refresh Token==
Refresh Token (ou jeton de rafraîchissement en français) est un mécanisme d'autorisation particulier. Il ne peut fonctionner en tant que tel, il fonctionne de pair avec [[#Authorization-Code|Authorization Code]]. Lorsqu'un jeton d'accès arrive est arrivé en fin de vie, si le client y est autorisé, il peut faire une demande de renouvellement de son jeton d'accès auprès du serveur d'autorisation en présentant son jeton de rafraîchissement. Le serveur d'autorisation vérifie la validité et génère un autre jeton d'accès qu'il transmet au client, sans que l'utilisateur final n'aît à se connecter de nouveau.


* S'il y a un message d'erreur "chaque variable doit disposer d'une valeur antérieure à la date de début de l'exercice comptable" cela signifie que la variable ne possède pas de valeur entre la date de début d'exercice (date définie dans '''Admin > Comptes > Gestion & Export''') et la date indiquée. Le programme refuse qu'il y ait une absence de tarif applicable.
Le principe de fonctionnement du rafraîchissement d'un jeton est le suivant :
*Chaque variable utilisée dans les formules de tarification doit avoir une valeur initialisée depuis une date antérieure à la date de début de l'exercice comptable
* Le jeton d'accès du client est arrivé à péremption. Le client effectue une demande de renouvellement en transmettant son Refresh Token au serveur d'autorisation.
* Le serveur d'autorisation vérifie la validité des informations, "consomme" le jeton de rafraîchissement et génère une nouvelle paire de jetons
* Le client reçoit sa nouvelle paire et utilise le nouveau jeton d'accès pour accéder aux ressources


==Mettre à jour les tarifs associés à des activités==
    +------+                      +-------------+                  +-----------+
*'''Admin > Ventes > Variables > Actualisation'''
    |Client|                      |  Serveur  |                  |  Serveur  |
[[Image:Menu-vente-prix-actualisation-des-prix.jpg|250px]]
    +--+---+                      |Autorisation |                  | Ressources|
      |                          +------+------+                  +-----+-----+
      |        Authentification          |                              |
      |        + Refresh Token          |                              |
      +--------------------------------->|                              |
      |                                  |                              |
      |    Access (+ Refresh) token      |                              |
      |<---------------------------------+                              |
      |                                  |                              |
      |                      Requête vers|API                            |
      +----------------------------------+------------------------------>|
      |                                  |                              |
      |                                  |                              |
      |                                  |<------------------------------+
      |                                  |  Vérification d'autorisation  |
      |                                  +------------------------------>|
      |                                  |                              |
      |                        Données /|Réponse                        |
      |<---------------------------------+-------------------------------+
      |                                  |                              |


Si vous souhaitez mettre à jour un tarif, c'est à dire définir un prix qui doit s'appliquer à partir d'une certaine date, alors il faut :
===Demande de renouvellement d'un jeton===
*Cliquer sur le bouton '''Ajouter une nouvelle valeur à une variable existante''' en bas du tableau et '''non pas sur l'icône crayon à droite de la ligne de tarif'''.
Pour obtenir un renouvellement de jeton d'accès, il faut effectuer la requête suivante : <code>POST https://openflyers.com/nom-de-plateforme/oauth/access_token.php</code>. Remplacer <code>nom-de-plateforme</code> par le nom de la plateforme utilisée.
[[Image:Tableau-vente-prix-actualisation-de-prix.jpg|500px]]


*Saisir une nouvelle valeur et la date à partir de laquelle le tarif s'appliquera. Vous pouvez anticiper un changement de tarif. Le tarif s'appliquera à la date du mouvement.
Les paramètres suivants sont nécessaires :
[[Image:Ajout-nouvelle-valeur-variable-tarif.png|400px]]
{| class="wikitable"
!Nom!!Type!!Description
|-
|client_id||string||L'identifiant ''client_id'' reçu pendant l'enregistrement du client.
|-
|client_secret||string||La passphrase ''client_secret'' reçue pendant l'enregistrement du client.
|-
|grant_type||string||Le mécanisme d'autorisation utilisé. Ici, la valeur doit être "refresh_token" (sans guillements).
|-
|scope||string||('''OPTIONNEL''') Liste des droits demandés par le client, séparés par des espaces.
|}


'''Attention''': si vous cliquez sur l'icône "Édition" (Crayon dans la colonne action) et que vous modifiez la valeur, alors vous changez le prix de cette période mais vous ne créez pas un nouveau tarif. Vous perdrez toute possibilité de corriger par exemple un vol à une date donnée.
Si la requête est correcte, un jeton d'accès ''access_token'' est fourni en réponse dans un objet au format JSON.
<syntaxhighlight lang="javascript">
{
  "token_type": "Bearer",
  "expires_in": 3600,
  "access_token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJSU-G7fOBOYzOgioSGNIQKI2A2OxjsNBbOOoaHsqNpsQxWZse3rofExAaGKh2tXbeuz1YAVhdLUGYgq-oKRK4ONFhw2NvcRf3QPxQXZImLWw",
  "refresh_token": "a59ef39fa9bab9b95cd554f921e7f3080a34c90f23d2b8031d692b5ff2d0993dcc392d9c7f9a43242337ef144c1a5fe1d0174413ade973e1b628ac0bbfc39b23973534"
}
</syntaxhighlight>


*Si vous avez le message d'erreur "chaque variable doit disposer d'une valeur antérieure à la date de début de l'exercice comptable" cela signifie que la variable ne possède pas de valeur entre la date de début d'exercice (date définie dans '''Gestion > Comptes > Gestion > Gestion & Export''') et la date indiquée. Le programme refuse qu'il y ait un "trou tarifaire" (vols non facturés).  
==Liste des scopes disponibles==
*Chaque variable utilisée dans les formules de tarification doit avoir une valeur initialisée depuis une date antérieure à la date de début de l'exercice comptable
Un scope sur OAuth2 correspond à un droit d'accès sur une ressource particulière. Chaque scope est unique et indique de manière explicite le privilège qu'il donne. Il n'y a aucune restriction d'utilisation des scopes par rapport aux mécanismes. Chaque scope peut être utilisé avec n'importe quel mécanisme. Il n'y a que des recommandations vis à vis de leur utilisation. OpenFlyers définit une liste de scopes dédiée aux ressources accessibles par les clients. Ces scopes sont les suivants :
{| class="wikitable"
!Nom!!Description
|-
|default.login||Comportement par défaut lorsqu'aucun scope n'est précisé ou qu'un scope est invalide. Ce scope est recommandé pour '''Authorization Code'''.
|-
|genericreports.readonly||Accéder aux rapports génériques autorisés pour le client en lecture seule. Ce scope est recommandé pour '''Client Credentials'''.
|-
|reports.readonly||Accéder aux rapports personnalisés autorisés pour le client en lecture seule. Ce scope est recommandé pour '''Client Credentials'''.
|}
==Prolonger la durée de vie de la session du client de démonstration==
La session du client de démonstration est initiée avec la méthode PHP [https://www.php.net/manual/en/function.session-start.php session_start]. Cette session peut être interrompue soit en cliquant sur le bouton de déconnexion, soit en fermant le navigateur (car les cookies associés à cette session se détruisent par défaut lorsque le navigateur est fermé).


Après mise à jour des tarifs, s'il est nécessaire de mettre à jour les activités déjà saisies, alors il faut suivre la procédure [[Gestion-des-activités#Mettre-à-jour-les-entrées-comptables-associées-à-des-activités-non-validées|Mettre à jour les entrées comptables associées à des activités non validées]].
Pour permettre à la session de rester active même après la fermeture du navigateur, il faut utiliser la méthode PHP [https://www.php.net/manual/en/function.session-set-cookie-params.php session_set_cookie_params]. Cette méthode donne la possibilité de définir une durée de vie pour les différents cookies associés à la session.
<syntaxhighlight lang="php">
// Set the parameters for the session cookie in a PHP session in order to configure its lifetime in 30 days
$oauth2DemoSessionLifeTime = time() + (86400 * 30);
session_set_cookie_params($oauth2DemoSessionLifeTime);
</syntaxhighlight>


==Supprimer les tarifs associés à des activités==
NB: Cette approche n'est pas particulièrement sécurisée et peut présenter des risques de sécurité pour OpenFlyers. Par conséquent, elle doit être utilisée avec précaution.
;Supprimer une valeur de tarif
*Accéder à '''Admin > Ventes > Variables > Actualisation'''
*Cliquer sur l'icone ''Supprimer'' (représentée par une poubelle dans la colonne Actions)
*Si deux valeurs ou plus de la même variable que celle que nous souhaitons supprimer ont une date de début antérieure ou égale à celle de l'exercice, alors la suppression est autorisée.
*Si une seule valeur de la même variable que celle que nous souhaitons supprimer a une date de début antérieure ou égale à la date de début de l'exercice, nous devons alors vérifier si la valeur que nous voulons supprimer a une date de début ultérieure à celle de l'exercice. Si tel est le cas, nous pouvons la supprimer.
*Dans tous les autres cas, la suppression n'est pas autorisée. Cela signifie soit qu'il n'y aura aucune valeur applicable à la date de début de l'exercice comptable, soit que la situation est déjà compromise, c'est-à-dire qu'aucune valeur applicable n'existe à la date de début de l'exercice comptable.


;Supprimer une variable tarif
==Révocation de token==
*Accéder à '''Admin > Ventes > Variables > Définition'''
Pour initier la demande de révocation, rediriger le navigateur de l'utilisateur vers l'URL : <code>POST https://openflyers.com/nom-de-plateforme/oauth/revoke.php</code>. Remplacer <code>nom-de-plateforme</code> par le nom de la plateforme utilisée.
*Cliquer sur l'icone ''Supprimer'' (représentée par une poubelle dans la colonne Actions)
*Si la valeur est utilisée dans une règle de facturation des '''Activités''' ou des '''Produits''', la suppression n'est pas autorisée et un message d'alerte s'affiche.
*Si aucune règle de facturation n'utilise cette variable tarif, la suppression sera autorisée, entraînant la suppression de toutes les valeurs associées à cette variable tarif.


=Mouvements=
Envoyer également le paramètre suivant:
Aller dans '''Gestion > Comptes > Flux > Mouvements'''
{| class="wikitable"
*Champs de sélection '''Mois''' et '''Année''' : permettent de filtrer les écritures comptables sur une période mensuelle ou annuelle.
!Nom!!Type!!Description
*Champ '''Filtrer par budget''' : pour filtrer les écritures par budget (si le module budget est activé).
|-
*Champ '''Comptabilité''' : pour filtrer par type de comptabilité.
|access_token||string||Le jeton d'accès (Chaîne de caractère unique permettant de prouver qu'un client OAuth a le droit d'accéder à une ressource.)
*Bouton à bascule '''Afficher le bouton de régénération de facture''' : affiche une icône permettant de forcer la regénération du PDF d'une facture.
|}


La liste des mouvements s'affiche selon la description présente dans le chapitre [[Gestion-de-la-comptabilité-côté-utilisateur#Extrait-de-compte|Extrait de compte]].
<syntaxhighlight lang="php">
apiRequest(
                $config['revoke_uri'],
                ['access_token' => $_SESSION['auth_token']['access_token']],
                null,
                false
            );
</syntaxhighlight>


=Opérations sur les comptes=
La demande de révocation se fait quand l'utilisateur clique sur le bouton '''Se déconnecter''' pour l'un des deux Clients '''Authorization Code''' et '''Client Credentials'''.
==Afficher la liste des opérations d'un compte==
*S'il s'agit d'un compte ressource, aller sur '''Gestion > Comptes > Comptes > Ressources'''.
*S'il s'agit d'un compte utilisateur, aller sur '''Gestion > Comptes > Comptes > Utilisateurs'''.
*Sinon, aller sur '''Gestion > Comptes > Comptes > Tous'''.
*Le cas échéant, cliquer la lettre correspond à la première lettre du nom du compte.
*Cliquer sur le pictogramme symbolisant une liste d'écritures dans la colonne '''Action''' de la ligne du compte concerné


==Créer un compte==
Si les jetons sont révoqués, l'utilisateur ne peut pas accéder à la plateforme OpenFlyers, il doit se reconnecter.
===Créer un compte ressource===
Pour créer un compte ressource, il faut :
*Aller sur '''Gestion > Comptes > Comptes > Ressources'''.
*Dans la ligne de la ressource concernée, cliquer sur l'icône '''+''' du compte souhaité.


===Créer un compte utilisateur===
==Utiliser l'API==
Pour créer un compte utilisateur, il faut :
Une fois un jeton d'accès obtenu, les données sont accessibles par une requête POST exécutée vers l'URL : <code>https://openflyers.com/nom-de-plateforme/oauth/resources.php</code>. Remplacer <code>nom-de-plateforme</code> par le nom de la plateforme utilisée. La requête POST doit respecter une structure particulière. Cette structure diffère en fonction du type de ressource souhaité et chaque ressource ne peut être accédée qu'[[#Liste-des-scopes-disponibles|avec le scope qui lui est associé]]. Le jeton d'accès doit être renseigné dans l'en-tête HTTP ''Authorization'' en y indiquant la valeur complète du jeton d'accès reçu précédé du type de jeton reçu  : <code>Authorization: <token_type> <access_token></code>.
*Aller sur '''Gestion > Comptes > Comptes > Utilisateurs'''.
*Dans la ligne de l'utilisateur concerné, cliquer sur l'icône check du compte souhaité.


===Créer un compte non-ressource non-utilisateur===
===Récupérer les informations de l'utilisateur connecté===
Pour créer un compte, il faut :
Ce type de ressource est nommé '''user_information'''. Cette ressource n'est accessible qu'avec le scope '''default.login'''. Cette ressource permet la récupération des informations de l'utilisateur connecté, en d'autre termes, l'utilisateur connecté lors de l'étape d'autorisation et correspondant à celui ayant émis la demande de jeton d'accès. À l'heure actuelle, seul l'identifiant de l'utilisateur connecté est retourné. La requête POST est construite de la manière suivante :
*Aller sur '''Gestion > Comptes > Comptes > Tous'''.
*En bas du tableau, remplir la ligne et choisir la catégorie du compte
*Cliquer sur le bouton '''Ajouter'''


==Créer un type de compte==
{| class="wikitable"
===Créer un type de compte ressource===
!Nom!!Type!!Description
Pour créer un type de compte ressource, il faut :
|-
*Aller sur '''Admin > Comptes > Ressources > Types actifs''' puis cliquer sur le bouton '''Ajouter un compte'''.
|resource_type||string||Le type de ressource demandé, ici, ce champ correspond à '''user_information'''.
*Saisir les champs du formulaire et valider .
|-
|client_id||string||L'identifiant unique reçu pendant l'enregistrement du client.
|}


==Désactiver un compte==
Un exemple de requête complète au format JSON :
Pour désactiver un compte qui ne doit plus être utilisé, il faut que :
<syntaxhighlight lang="javascript">
* le compte ne doit pas être utilisé dans une règle de tarification. Cela concerne directement les comptes et non pas les types de comptes. Ainsi, il est possible de désactiver un compte utilisateur ou un compte ressource alors qu'il existe des règles de tarification qui utilisent le même type de compte que le compte à désactiver.
POST /nom-de-plateforme/oauth/resources.php HTTP/1.1
* dans le cas d'un compte de type utilisateur, l'utilisateur possédant ce compte ne doit pas être lié à un profil qui requiert le type de compte
Content-Type: application/x-www-form-urlencoded
* le compte ne doit pas être utilisé par un type de facture fournisseur
Host: openflyers.com
* le compte ne doit pas créer de trou dans la comptabilité. C'est à dire que :
Date: Wed, 04 Aug 2021 13:57:51 GMT
** le [[#Solder_un_compte|solde du compte doit être à 0]]
Accept: application/json
** les écritures associées à ce compte doivent être toutes validées
User-Agent: curl/7.64.1
Ensuite, on peut procéder à sa désactivation en cliquant sur l'icône symbolisant une poubelle sur la ligne de ce compte. Un compte n'ayant jamais eu d'écriture est supprimé au lieu de passer à l'état désactivé.
Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJSU
Signature: keyId="58c450d937953829c8cca3613001f865a918da07",
          algorithm="rsa-sha256",
          headers="host content-type digest",
          signature="UsTjCPLsXzmo1b8FrA18SdLgaPamCpqR7tDHBhaiBnro6RbOkoD7="
Digest: SHA-256=Bi6/9qfJXEWme2/9o7VQMyvf+MED523bWdtdi91opwk=


==Lister les comptes==
{
*Aller sur '''Gestion > Comptes > Comptes > Ressources''' pour obtenir les comptes ressource
    "resource_type":"user_information",
*Aller sur '''Gestion > Comptes > Comptes > Utilisateurs''' pour obtenir les comptes utilisateur
    "client_id":"d2615fe2020ec476"
*Aller sur '''Gestion > Comptes > Comptes > Tous''' pour obtenir les autres comptes.
}
</syntaxhighlight>


Dans ces pages, le solde de chaque compte est calculé en prenant les écritures datant du début d'exercice comptable jusqu'à la fin de la journée courante dans le [[Configuration#Fuseau-horaire-de-la-structure|fuseau horaire de la structure]]. Ainsi, les écritures comptables avec une date comptable future ne sont pas prises en compte pour le calcul du solde.
La réponse est retournée dans un conteneur JSON.


Le symbole '''*''' peut apparaître à gauche d'un solde : Cela signifie que le compte possède des écritures comptables dans le futur.
===Récupérer les rapports génériques===
Ce type de ressource est nommé '''generic_report'''. Cette ressource n'est accessible qu'avec le scope '''genericreports.readonly'''. Cette ressource permet la récupération des rapports génériques au format CSV autorisés pour le profil de l'utilisateur associé au client OAuth2 enregistré. La requête POST est construite de la manière suivante :


==Renommer un compte==
{| class="wikitable"
*Aller sur '''Gestion > Comptes > Comptes > Tous'''
!Nom!!Type!!Description
*Cliquer éventuellement sur la lettre correspond à la 1ère lettre du nom du compte concerné.
|-
*Cliquer sur le champ de la colonne '''Nom''' du compte concerné.
|resource_type||string||Le type de ressource demandé, ici, ce champ correspond à '''generic_report'''.
*Changer le nom du compte.
|-
*Cliquer en dehors de la zone de saisie afin de permettre l'enregistrement de la saisie et le rafraichissement du tableau.
|client_id||string||L'identifiant unique reçu pendant l'enregistrement du client.
|-
|report_id||int||Identifiant du rapport souhaité.
|-
|replacementList||array||Tableau correspondant aux différents paramètres modifiables pour générer le rapport souhaité.
|}


==Supprimer un compte créé en double==
Un exemple de requête complète au format JSON :
Il peut arriver que deux comptes identiques soient créés par inadvertance. Dans ce cas, il est nécessaire de supprimer le compte en trop. Pour cela il faut :
<syntaxhighlight lang="javascript">
*[[#Renommer-un-compte|Renommer le compte]] en trop pour qu'il ne porte pas le même nom que le compte initial. Cela peut se faire en lui rajoutant un numéro. Exemple : ''Compte en trop'' deviendra ''Compte en trop 1''.
POST /nom-de-plateforme/oauth/resources.php HTTP/1.1
*Si le compte comporte des écritures non validées, il est recommandé de modifier les écritures pour les affecter au bon compte. Pour cela, il faut :
Content-Type: application/x-www-form-urlencoded
*[[#Afficher-la-liste-des-opérations-d'un-compte|Afficher la liste des opérations du compte]].
Host: openflyers.com
*Cliquer sur le pictogramme symbolisant un crayon pour chaque ligne concernée afin d'éditer la saisie. Il est recommandé d'utiliser le [[Ergonomie#Clic-droit-et-clic-molette|clic droit ou le clic molette]] de la souris. Cela permet d'effectuer l'édition dans un nouvel onglet et ainsi de ne pas perdre la page contenant le détail du compte.
Date: Wed, 04 Aug 2021 13:57:51 GMT
*S'il s'agit d'un flux, il suffit de modifier la saisie du flux, puis de cliquer sur le bouton '''Enregistrer'''.
Accept: application/json
*S'il s'agit d'une facture fournisseur, il faut d'abord modifier le type de facture fournisseur correspondant. Pour cela il faut :
User-Agent: curl/7.64.1
**Aller dans '''Gestion > Achats > Types de facture fournisseur'''
Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJSU
**Retrouver le type de facture correspondant
Signature: keyId="58c450d937953829c8cca3613001f865a918da07",
**Cliquer sur le pictogramme symbolisant un crayon dans la colonne '''Actions''' de la ligne du type de facture fournisseur concerné.
          algorithm="rsa-sha256",
**Modifier la sélection du champ '''Compte fournisseur ou trésorerie''' ou du champ '''Compte de charge, de bilan ou ressource''' pour y mettre le ''compte qui n'est pas en trop''.
          headers="host content-type digest",
**Cliquer sur le bouton '''Enregistrer'''.
          signature="UsTjCPLsXzmo1b8FrA18SdLgaPamCpqR7tDHBhaiBnro6RbOkoD7="
:Ensuite, il est possible d'éditer la facture fournisseur et de la réenregistrer. Cela permet l'imputation sur le bon compte.
Digest: SHA-256=Bi6/9qfJXEWme2/9o7VQMyvf+MED523bWdtdi91opwk=
*Répéter l'opération d'édition de chaque ligne afin de faire disparaitre toutes les lignes non validées.
*Une fois qu'il ne reste plus de ligne non validée, alors il faut retourner sur l'affichage des comptes de même catégorie afin de pouvoir cliquer sur le pictogramme symbolisant une poubelle à l'intersection de la colonne '''Actions''' et de la ligne concernée.


=Retrouver les écritures non validées=
{
S'il y a beaucoup d'écritures à valider, il faut procéder de façon systématique en parcourant chaque interface de validation :
    "resource_type":"generic_report",
*Pour les activités, aller dans '''Gestion > Activités > Activités > Liste''' puis '''Valider les activités sélectionnés''' pour chaque ressource.
    "client_id":"d2615fe2020ec476",
*Pour les encaissements, aller dans '''Gestion > Comptes > Pointer > X''' où X correspond au type de paiement à pointer.
    "report_id":135,
*Pour les transferts, aller dans '''Gestion > Comptes > Flux > Valider''' puis '''valider les transferts'''.
    "replacementList":{
*Pour les factures, aller dans '''Gestion > Achats > Liste des factures fournisseurs'''.
        "year":2018
*Pour les ventes, aller dans '''Gestion > Ventes > Valider les ventes'''. Choisir ses critères de recherche et valider le formulaire. Ensuite cocher les différentes ventes listées puis '''Pointer les entrées sélectionnées'''.
    }
En procédant ainsi, il ne doit normalement ne pas subsister d'écriture à valider. Cependant, si l'essentiel des écritures a été validé, alors il est plus rapide de rechercher les écritures à valider que de parcourir chaque interface de validation.
}
</syntaxhighlight>


Pour rechercher les écritures restantes à valider, il faut :
La réponse est un fichier CSV retourné dans un conteneur JSON.
*Aller dans '''Gestion > Comptes > Gestion > Gestion / Export'''
En-dessous du titre '''Statistiques''' figure un tableau :


[[Image:Tableau_statistiques_gestion-export.png]]
Le report_id se trouve dans la [[Bibliothèque-des-rapports#Présentation|bibliothèque des rapports]] dans la 1ère colonne de la ligne du rapport concerné.


Dans l'exemple ci-dessus, on peut voir à la ligne ''Ecritures non validées'', colonne ''Date de première écriture'' l'horodatage "19/08/2013 14:45".
Pour identifier les paramètres à transmettre dans "replacementList", il faut retrouver le rapport [[OF-doc-en:Export-generator-4|dans la version anglaise de la documentation]].


Cela veut dire qu'il y a au moins une écriture qui date du 19 août 2013 et qui n'a pas été validée.
Exemple avec le rapport [[OF-doc-en:Accounting-exports#Balances-of-resource-accounts|"Balances of resource accounts"]] : le chapitre indique que les paramètres/variables nécessaires sont ''accountingId'' et ''endDate''.


Pour retrouver cette écriture non validée, il faut :
===Récupérer les rapports personnalisés===
*Aller dans '''Gestion > Comptes > Flux > Mouvements'''
Ce type de ressource est nommé '''report'''. Cette ressource n'est accessible qu'avec le scope '''reports.readonly'''. Cette ressource permet la récupération des rapports personnalisés au format CSV autorisés pour le profil de l'utilisateur associé au client OAuth2 enregistré. La requête POST est construite de la manière suivante :
*Rechercher l'écriture d'après la date indiquée.
Son contenu permettra de savoir s'il s'agit d'un vol, d'un encaissement, d'un flux, etc. Si un doute subsiste sur le type d'écriture, il faut cliquer sur l'icône "modifier" de l'écriture considérée afin de se retrouver dans son formulaire d'édition.


Si les vols non validés correspondent à une ressource qui a été désactivée, alors pour valider les vols concernés, il faut :
{| class="wikitable"
*Aller dans '''Gestion > Ressources > Actives''' désactiver temporairement '''Réservable''' et '''Saisie d'activité''' pour une ressource existante afin de pouvoir avoir la possibilité de réactiver l'autre ressource
!Nom!!Type!!Description
*Aller dans '''Gestion > Ressources > Désactivées'''
|-
*Réactiver la ressource concernée
|resource_type||string||Le type de ressource demandé, ici, ce champ correspond à '''report'''.
*Aller dans '''Gestion > Ressources > Actives''' et activer temporairement '''Saisie d'activité''' pour la ressource concernée
|-
*Aller dans '''Gestion > Activités > Liste d'activités''' puis cliquer le bouton de la colonne '''Valider les activités sélectionnées''' correspondant à la ressource concernée
|client_id||string||L'identifiant unique reçu pendant l'enregistrement du client.
*Pointer les activités de la ressource
|-
*Retourner dans '''Gestion > Ressources > Actives''' et désactiver la ressource concernée en cliquant sur l'icône symbolisant une poubelle
|report_id||int||Identifiant du rapport souhaité.
*Toujours dans cette interface, remettre '''Réservable''' et '''Saisie d'activité''' pour la ressource temporairement désactivée
|-
|replacementList||array||Tableau correspondant aux différents paramètres modifiables pour générer le rapport souhaité.
|}


Remarques :
Un exemple de requête complète au format JSON :
*Le nombre d'écritures non validées indiqué dans le tableau '''Statistiques''' correspond au nombre TOTAL d'écritures non validées. En effet, OpenFlyers sait quand est-ce que vous commencez un exercice mais il ne sait pas quand est-ce que vous voulez qu'il s'arrête. Donc il prend toutes les écritures non validées.
<syntaxhighlight lang="javascript">
*OpenFlyers ne permet d'exporter ou de clôturer que la période qui inclut des écritures validées. Dès qu'il rencontre une écriture non validée, il bloque toute action d'export ou de clôture.
POST /nom-de-plateforme/oauth/resources.php HTTP/1.1
Content-Type: application/x-www-form-urlencoded
Host: openflyers.com
Date: Wed, 04 Aug 2021 13:57:51 GMT
Accept: application/json
User-Agent: curl/7.64.1
Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJSU
Signature: keyId="58c450d937953829c8cca3613001f865a918da07",
          algorithm="rsa-sha256",
          headers="host content-type digest",
          signature="UsTjCPLsXzmo1b8FrA18SdLgaPamCpqR7tDHBhaiBnro6RbOkoD7="
Digest: SHA-256=Bi6/9qfJXEWme2/9o7VQMyvf+MED523bWdtdi91opwk=


=Saisir des écritures comptables=
{
==Annuler une écriture comptable==
    "resource_type":"report",
Dans le cas où une écriture comptable saisie n'a pas été encore validée, pour l'annuler, il faut cliquer sur le bouton symbolisant une poubelle. Cela supprimera complètement l'écriture.
    "client_id":"d2615fe2020ec476",
    "report_id":1,
    "replacementList":{
        "year":2020
    }
}
</syntaxhighlight>


Dans le cas où une écriture comptable a été validée, il n'est plus possible de la supprimer du fait de l'[[Comptabilité#Inaltérabilité-des-données|inaltérabilité des données]]. Le seul moyen d'en annuler son effet consiste à saisir une écriture opposée. Pour cela, il faut [[#Contrepasser-une-écriture|contrepasser l'écriture]].
La réponse est un fichier CSV retourné dans un conteneur JSON.


Cette solution permet de garantir l'inaltérabilité des écritures qui peuvent avoir été saisies par d'autres personnes et surtout qui peuvent affecter des comptes d'utilisateurs.
=Procédures=
==Créer un client à partir du code source==
;Prérequis
Un client de démonstration est disponible pour le logiciel OpenFlyers à cette adresse : https://openflyers.com/oauth2-demo/index.php


C'est comme pour un compte bancaire : un banquier ne supprime jamais une écriture sur un compte client. Il passe simplement une nouvelle écriture qui annule l'effet de la précédente.
;Télécharger Le code source du client de démonstration :


==Contrepasser une écriture==
Le code source est mis à disposition par OpenFlyers à cette adresse : https://openflyers.com/oauth2-demo/oauth2-demo-src.zip
Cette fonction nécessite le droit [[Gestion-des-profils#Gestion-des-comptes|gestion des comptes]].


La fonction de contrepassation d'une écriture permet d'annuler l'effet d'une vente (produit stocké ou non stocké), d'un encaissement ou d'un flux. Cette fonction n'existe pas pour contrepasser la vente de validité ou la vente d'activité. Pour la vente d'activité, il faut [[Gestion-des-activités#Annuler-une-activité-validée|annuler l'activité validée]].
Le code source est structuré de la manière suivante :


;Procédure
[[File:Oauth2 src demo folder.png|800px]]
*Aller sur la page d'un extrait de compte ou sur la liste des mouvements : '''Gestion > Comptes > Flux > Mouvements'''
*Dans la colonne '''Actions''', cliquer sur le pictogramme '''Contrepasser le flux'''
*Une alerte demande de confirmer le souhait de créer une écriture de contrepassation
*Cela crée une écriture opposée, à la date de l'écriture initiale, qui a pour commentaire le texte "Contrepassation flux X du XX/XX/XXXX". L'écriture générée est directement validée.


==Modifier un flux==
* Le dossier '''css''' contient tout le matériel nécessaire à la stylisation de la page web.
* Aller dans '''Gestion > Comptes > Flux > Valider''' ou dans l'[[Gestion-de-la-comptabilité-côté-utilisateur#Extrait-de-compte|extrait de compte]]
* Le dossier '''img''' contient les images affichées sur la page web.
* Cliquer sur l'icône "Crayon" correspond au flux à éditer
* Le dossier '''ssl''' est le dossier contenant tous les certificats et les clé privées associées à chaque client. Il doit respecter une structure particulière décrite ci-dessous.
* [[#Saisir-un-flux|Procéder à éditer le flux]]
* Le fichier '''ClientDemo.php''' contient la classe '''ClientDemo'''. Cette classe contient toutes les méthodes nécessaires au fonctionnement du client de démonstration OAuth2.
* Le fichier '''index.php''' est le fichier à appeler depuis le navigateur. Ce fichier correspond au fichier qui gère les appels à la classe '''ClientDemo''' et exécute les méthodes dans l'ordre.


==[[Écritures comptables#Avoir_clients|Saisie d'un avoir client]]==
Le dossier '''ssl''' doit respecter la structure suivante :
Il faut [[#Saisir-un-flux|saisir un flux]] qui va créditer un compte utilisateur/client d'un certain montant et débiter un compte produit "Remise X" du même montant.


==Saisir un flux==
[[File:Oauth2 demo tree.png]]
Pour saisir un flux, il faut :
*Soit aller dans '''Gestion > Comptes > Flux > Saisir'''
*Soit afficher un extrait de compte et cliquer sur le bouton '''Saisir un flux''' situé en bas à droite de l'affichage du compte à droite de la ligne du solde.
Si le gestionnaire arrive sur le formulaire de saisie d'un flux depuis un extrait de compte, alors la première ligne est pré-remplie avec le compte depuis lequel le formulaire a été affiché.


Sinon, Il faut sélectionner sur la première ligne de compte, le compte souhaité. Pour cela, il est possible de cliquer sur le champ afin de faire apparaitre la liste de tous les comptes. Ils sont regroupés par catégorie et dans chaque catégorie triés par ordre alphabétique. A droite de chaque nom de compte apparait entre parenthèses le compte d'export associé.  
;[[#Générer des certificats|Générer les certificats]] :
Après la génération des certificats, les clés privées 'auth.key' et 'sign.key' remplacent celles présentes dans les dossiers 'ssl/AuthCodeDemo' et 'ssl/ClientCredDemo'.


Il est possible de renseigner dans le champ de saisie quelques lettres correspondantes à une partie du nom du compte afin de faire apparaitre la liste des comptes qui correspondent. Il est également possible de saisir quelques chiffres du compte d'export.
;[[#Enregistrer un client|Enregistrer les clients]] :
*Remplir le champ débit ou crédit selon le cas
S'il n'y a que 2 lignes comptables pour le flux :
*Sélectionner sur la 2ème ligne un compte de contre-partie
*Sur cette 2ème ligne, cliquer sur l'icône qui symbolise une balance avec une flèche bleue et intitulé "Equilibrer la dernière ligne"
S'il y a plus 2 lignes comptables pour le flux :
*Cliquer sur le pictogramme symbolisant un "plus" dans la colonne '''Action''' de la dernière ligne du flux afin de faire apparaitre une ligne supplémentaire
*Répéter cette opération autant de fois que nécessaire
Une fois que l'ensemble des lignes du flux sont remplies :
*Compléter éventuellement le champ '''commentaires'''
*Si l'utilisateur connecté dispose du [[Gestion-des-profils#Modifier-la-date-d%27un-encaissement,-d%27un-flux-ou-d%27un-transfert|droit de modifier la date de flux]], un calendrier apparaît à côté de la mention '''Choisir la date comptable :''' en bas du formulaire. choisir une date comptable en cliquant sur le calendrier, Si la date est à la date du jour l'heure courante est utilisée. Sinon la date est définie à 12:00 heure locale.
*Sinon, la date actuelle est affichée lors de la création d'un nouveau flux, tandis que la date du flux est affichée en cas de modification d'un flux existant à côté de la mention '''La date comptable :'''
*Cliquer sur le bouton '''Valider'''
Remarque : La date comptable proposée par défaut correspond à la date comptable du dernier flux saisi. Cela permet de faciliter les [[Comptabilité#Règles_de_saisies_de_la_comptabilité_courante|saisies au vu du relevé de banque]].


==Solder les comptes==
*Deux clients doivent être créés :
;Présentation
**Le premier pour le mécanisme d'autorisation '''Authorization Code''',
Cette fonction permet de solder les [[Comptabilité#Comptes-d'exploitation|comptes d'exploitation]] afin d'enregistrer le résultat de l'exercice au bilan.
**Le second pour le mécanisme d'autorisation '''Client Credentials'''.


Cela consiste à générer un flux pour mettre à 0 tous les comptes de catégorie [[Comptabilité#Comptes-de-charges-2|comptes de charges]], [[Comptabilité#Comptes-de-produits|comptes de produits]] et [[Comptabilité#Comptes-ressources|comptes ressources]] avec comme compte de contrepartie un [[Comptabilité#Comptes-de-bilan|compte de bilan]]. Seuls les comptes actifs sont traités.
[[File:Oauth2 demo manage.png|800px]]


Cette écriture doit normalement être enregistrée à la date du dernier jour de l'exercice comptable.
*Télécharger le certificat du CA OpenFlyers en cliquant sur le bouton '''Télécharger le certificat CA''' de la page de gestion. Télécharger aussi le certificat de signature du serveur en cliquant sur le bouton '''Télécharger le certificat de signature du serveur''' de la page de gestion. Placer les deux certificats téléchargés à la racine du dossier '''ssl'''.


;Procédure
*Télécharger les deux certificats ''Certificat d'authentification'' et ''Certificat de signature'' du client '''Authorization Code''' et les placer dans le répertoire '''ssl/AuthCodeDemo'''.
*Aller dans '''Gestion > Comptes > Gestion > Solder les comptes'''
*Modifier le fichier '''ssl/AuthCodeDemo/config.authcode.json''' en le remplissant de la manière suivante :
<syntaxhighlight lang="php">
{
  "client_id": "XXXXXXXXXXXXXXXX",
  "client_secret": "XXXXXXXXXXXXXXXX",
  "authorize_uri": "https://openflyers.com/mastructure/oauth/authorize.php",
  "token_uri": "https://openflyers.com/mastructure/oauth/access_token.php",
  "resource_uri": "https://openflyers.com/mastructure/oauth/resources.php",
  "revoke_uri": "https://openflyers.com/mastructure/oauth/revoke.php",
  "auth_cert": "path_to_client/ssl/AuthCodeDemo/auth_cert.crt",
  "auth_key": "path_to_client/ssl/AuthCodeDemo/auth.key",
  "sign_cert": "path_to_client/ssl/AuthCodeDemo/sign_cert.crt",
  "sign_key": "path_to_client/ssl/AuthCodeDemo/sign.key",
  "auth_cacert": "path_to_client/ssl/ca.crt",
  "sign_cert_server": "path_to_client/ssl/sign_cert_server.crt"
}
</syntaxhighlight>


Si des [[Alertes-de-configuration#Alertes-liées-à-la-cohérence-des-comptes|alertes liées à la cohérence des comptes]] apparaissent, il faut suivre les procédures associées qui permettent de supprimer les alertes, puis reprendre cette procédure à son début.
*Remplacer les <code>XXXXXXXXXXXXXXXX</code> des champs <code>client_id</code> et <code>client_secret</code> par les valeurs obtenues lors de [[#Enregistrer-un-client|l'enregistrement du client]].
*Remplacer <code>mastructure</code> par le nom de la structure sur laquelle la démo est testée.
*Remplacer <code>path_to_client/</code> par le chemin d'accès vers le dossier qui contient le code source du client de démonstration.
**Exemple sur '''windows''': <code>C:/wamp64/www/4.0/oauth-demo/</code>.
**Exemple sur un serveur Linux '''debian''': <code>./</code>.


Dans cette interface :
*Décocher les comptes ressources à ne pas solder
*Sélectionner la date comptable
*Sélectionner la comptabilité
*Choisir un compte de contrepartie
*Cliquer sur '''Générer les écritures'''


==Solder un compte==
*Télécharger les deux certificats ''Certificat d'authentification'' et ''Certificat de signature'' du client '''Client Credentials''' et les placer dans le répertoire '''ssl/ClientCredDemo'''.
Pour solder un compte, il faut [[#Saisir-un-flux|saisir un flux]] avec la particularité que l'on souhaite "ramener le solde à zéro" d'un compte donné. Pour cela, il faut en plus des [[#Saisir-un-flux|opérations]] indiquées pour la saisie d'un flux, il faut :
*Modifier le fichier '''ssl/ClientCredDemo/config.clientcred.json''' en le remplissant de la manière suivante :
*dans la première ligne de compte, sélectionner le compte concerné
<syntaxhighlight lang="php">
*Cliquer sur l'icône qui symbolise une balance intitulé "Ramener le solde à zéro"
{
  "client_id": "XXXXXXXXXXXXXXXX",
  "client_secret": "XXXXXXXXXXXXXXXX",
  "token_uri": "https://openflyers.com/mastructure/oauth/access_token.php",
  "resource_uri": "https://openflyers.com/mastructure/oauth/resources.php",
  "revoke_uri": "https://openflyers.com/mastructure/oauth/revoke.php",
  "auth_cert": "path_to_client/ssl/ClientCredDemo/auth_cert.crt",
  "auth_key": "path_to_client/ssl/ClientCredDemo/auth.key",
  "sign_cert": "path_to_client/ssl/ClientCredDemo/sign_cert.crt",
  "sign_key": "path_to_client/ssl/ClientCredDemo/sign.key",
  "auth_cacert": "path_to_client/ssl/ca.crt",
  "sign_cert_server": "path_to_client/ssl/sign_cert_server.crt"
}
</syntaxhighlight>


==Validation d'un flux==
*Remplacer les <code>XXXXXXXXXXXXXXXX</code> des champs <code>client_id</code> et <code>client_secret</code> par les valeurs obtenues lors de [[#Enregistrer-un-client|l'enregistrement du client]].
Pour pouvoir valider les flux, il faut disposer des droits [[Gestion-des-profils#Valider-tout-flux|Valider tout flux]] et [[Gestion-des-profils#Saisir-tout-flux|Saisir tout flux]].
*Remplacer <code>mastructure</code> par le nom de la structure sur laquelle la démo est testée.
*Aller dans '''Gestion > Comptes > Flux > Valider'''
*Remplacer <code>path_to_client/</code> par le chemin d'accès vers le dossier qui contient le code source du client de démonstration.
*Cocher dans la 1ère colonne '''Pointer''' les lignes à valider
**Exemple sur '''windows''': <code>C:/wamp64/www/4.0/oauth-demo/</code>.
*Cliquer sur le bouton '''Valider les flux sélectionnés (cette action est irréversible)'''
**Exemple sur un serveur Linux '''debian''': <code>./</code>.


=Rechercher=
Aller dans '''Gestion > Comptes > Flux > Rechercher'''
*Champ '''Date de début''' et champ '''Date de fin''' : définir la plage de dates et d’heures de la recherche.
*Champ '''Numéro de flux''' : rechercher une écriture par son identifiant de flux.
*Champ '''Rechercher par commentaire''' : filtrer les écritures dont le commentaire contient le texte saisi.
*Champ '''Compte imputé''' : restreindre la recherche aux écritures imputées sur un compte précis.
*Champ '''Compte de contrepartie''' : restreindre la recherche aux écritures contenant un compte de contrepartie sélectionné.
*Champ '''Comptabilité''' : filtrer les résultats par type de comptabilité.
*Bouton à bascule '''Afficher le bouton de régénération de facture''' : affiche une icône permettant de forcer la regénération du PDF d'une facture.
*Bouton '''Exporter en CSV''' : générer un export des écritures filtrées en tenant compte des filtres actifs et de la page sélectionnée.


Le résultat de la recherche s'affiche selon la description présente dans le chapitre [[Gestion-de-la-comptabilité-côté-utilisateur#Extrait-de-compte|Extrait de compte]].
*Suivre la [[#Utiliser le client|procédure d'utilisation du client de démonstration]] pour faire fonctionner le client.


=Solder les comptes=
*Aller dans '''Gestion > Comptes > Gestion > Solder les comptes'''
*Dans la première colonne '''Comptes à solder''', laisser tous les comptes "Ressources" cochés sauf si certains ne sont pas des comptes d'exploitation
*Si l'utilisateur connecté dispose du [[Gestion-des-profils#Modifier-la-date-d%27un-encaissement,-d%27un-flux-ou-d%27un-transfert|droit de modifier la date de solde des comptes]], un calendrier apparaît à côté de la mention '''Solder les comptes à la date du :''' en bas du formulaire. Sinon, la date par défaut affichée  doit correspondre au 31 décembre de l'année de l'exercice comptable en cours.
*Dans la deuxième colonne '''Période''', si plusieurs comptabilités sont paramétrées, un le champ '''Comptabilité''' est visible, sélectionner la comptabilité souhaitée : il faudra effectuer cette opération pour chacune des comptabilités de la plateforme
*Dans le champ '''Compte de contrepartie''', sélectionner le compte de bilan souhaité
**Par défaut, est sélectionné, soit le compte '''Report à nouveau (Bilan)''' pour la comptabilité générale, soit le compte utilisé lors du précédent import
*Cliquer sur le bouton '''Générer les écritures'''


Ce module génère alors un unique flux qui solde les comptes de charges, de produits et ceux liés aux ressources vers un compte de contrepartie de sorte que ces comptes soient ramenés à 0 à la date sélectionnée.
'''NB''': Le certificat de signature du serveur est unique à chaque plateforme et serveur. Ainsi, si le serveur ou la plateforme est modifié, le certificat doit être renouvelé.


Seuls les comptes actifs sont pris en compte.
==Enregistrer un client==
Pour utiliser l'API OAuth2, il faut enregistrer un client OAuth2 auprès d'OpenFlyers. Pour ceci, suivre les étapes suivantes :


Le flux généré est modifiable.


Après avoir cliqué sur le bouton '''Générer les écritures''', le gestionnaire est redirigé vers '''Admin > Comptes > Valider les flux''' avec par défaut l'affichage du mois et de l'année correspondant aux flux générés afin de lui permettre de valider ces flux. Cette opération est nécessaire avant de pouvoir [[#Clôturer-l'exercice-comptable|Clôturer l'exercice comptable]].
;Pour le mécanisme d'authentification Client Credentials
*Créer un [[Gestion-des-profils#Ajouter-un-profil|nouveau profil]]. Ce profil doit permettre de gérer les droits du client OAuth2. Choisir un nom explicite, par exemple "Client OAuth rapports".


=Valider toutes les écritures=
*Sélectionner les droits à assigner à ce profil. Ces droits limitent les données auxquelles le client OAuth2 a accès.
OpenFlyers permet de valider toutes les écritures comprises avant une date choisie.
**Sélectionner les droits relatifs à l'enregistrement de clients OAuth2 dans l'onglet '''Admin''' (colonne '''Associé aux clients OAuth2''').
*Aller dans '''Gestion > Comptes > Gestion > Valider'''
**Sélectionner les rapports accessibles par le profil précédemment créé.
*Dans la colonne intitulée '''Statistiques''', la date de début de l'exercice comptable est renseignée ainsi qu'un tableau indiquant le nombre d'écritures non validés avec la date de la première écriture non validée.
*Si l'utilisateur connecté dispose du [[Gestion-des-profils#Modifier-la-date-d%27un-encaissement,-d%27un-flux-ou-d%27un-transfert|droit de modifier la date de validation des écritures]], un calendrier apparaît à côté de la mention '''Valider les écritures jusqu'à la date du:''' en bas du formulaire. En cliquant sur le calendrier.
** Cette date ne doit pas être inférieure à la date de début de l'exercice comptable.
** Et, ne doit pas être supérieure au 31 décembre de l'année précédente.
*Sinon, la date par défaut affichée doit correspondre au 31 décembre de l'année précédente.  
* Activer l'interrupteur '''Autoriser''' et cliquer sur le button '''Valider toutes les écritures'''


=Factures fournisseurs=
*Créer un nouvel utilisateur à partir du panneau de gestion. Cet utilisateur est virtuel et représente le serveur sur lequel fonctionne le client OAuth2.
==Saisir des factures fournisseurs==
**''Des identifiant et nom explicites sont recommandés (exemple : "serv1_oauth_client")''
[https://www.youtube.com/watch?v=QVUgYEfqDk0 Vidéo tutorielle pour saisir une facture fournisseur]
**'''Attention''' : tout utilisateur désactivé et associé à un client OAuth2 rend le client inactif, il faut donc changer l'utilisateur associé au client pour réactiver le client.


*Aller dans '''Gestion > Achats > Saisie Factures fournisseurs'''.
*Vous vous retrouvez alors avec le formulaire de saisie suivant :


2 cas peuvent se présenter :
;Pour le mécanisme d'authentification Authorization Code
*Soit [[#Saisir-une-facture-fournisseur-dont-le-type-de-facture-est-déjà-enregistré|le type de facture fournisseur correspondant à la facture à saisir est déjà existant]]
*Aller dans '''Admin > Utilisateurs > Profils'''
*Soit la facture fournisseur à saisir nécessite la création d'un nouveau type de facture fournisseur. Dans ce cas, il existe plusieurs façons de créer un nouveau type de facture fournisseur dont la possibilité de [[#Saisir-une-facture-fournisseur-dont-le-type-est-nouveau|le faire directement en saisissant la facture]].
*Dans l'onglet '''Généralités''', cocher la case relative à la colonne '''Connexion depuis l'extérieur (OAuth2)''' pour le profil souhaité.


Vous pouvez importer un fichier en plus si nécessaire. Seulement [[Gestion-des-documents#Type-de-fichier-autorisé|certains types de fichiers sont autorisés]].


===Saisir une facture fournisseur dont le type de facture est déjà enregistré===
;Créer un nouveau client OAuth2
*Sélectionner le '''type de facture fournisseur''' correspondant à la facture à saisie. Le nombre de champs à renseigner se réduit alors automatiquement :
*Aller dans '''Admin > Transferts > Exports > API OAuth2'''
*Reporter sur la facture originale le '''numéro de pointage''' qui apparaît dans le formulaire. Ce numéro de pointage est unique et permet de rapprocher les factures fournisseurs saisies dans OpenFlyers avec leurs originaux.
[[File:Oauth2 manage.png|800px]]
*Cocher "'''J'ai bien pris en compte cette valeur et je l'ai reporté sur ma facture'''". A défaut, si vous validez, le système refusera votre saisie et une alerte apparaitra.
*Cliquer sur le bouton Ajouter '''+''' ou '''Ajouter un client'''
*Le champ '''Description''' permet d'indiquer toute information utile concernant la facture et d'en faciliter la recherche ultérieure.
[[File:Oauth2 client creation.png|800px]]
*Indiquer la date de facturation en cliquant sur le calendrier. Il est possible de paramétrer le format d'affichage de la date en allant dans le menu '''Données/Affichage''' puis de modifier le contenu du champ '''fiche personnelle/Patron de format de date'''.
*Choisir un nom pour le client.
*Renseigner le '''montant hors taxe'''
*Sélectionner le mécanisme d'autorisation utilisé par le client :
*Renseigner la '''TVA''' (champ visible uniquement si la comptabilité est paramétrée pour la prise en compte de la TVA). Si la TVA est nulle, il faut indiquer 0.
**'''Authorization Code''': permet d'utiliser OAuth2 comme solution SSO ou accéder à des données utilisateurs. Cette méthode peut être couplée avec le mécanisme de mémorisation de connexion (''Refresh Token'').
*Cliquer sur '''Valider''' ou '''Valider et Saisir le suivant''' si vous avez plusieurs factures à saisir à la volée. Dans ce dernier cas, le formulaire de saisie de facture fournisseur s'affiche à nouveau et il faut reprendre les opérations de saisie depuis le début de ce paragraphe.
**'''Client Credentials''': permet d'utiliser OAuth2 dans un contexte d'automatisme.
*Saisir l'URI de redirection vers le client pour le mécanisme ''Authorization Code''.
*Sélectionner l'utilisateur virtuel créé précédemment pour le mécanisme ''Client Credentials''.
*[[#Générer-des-certificats|Générer deux CSR]] afin d'obtenir deux certificats signés et les saisir :
**'''Certificate Signing Request pour le certificat d'authentification''' est utilisé pour l'authentification mutuelle avec mTLS (auth_cert.csr.pem).
**'''Certificate Signing Request pour le certificat de signature''' est utilisé pour la signature des en-têtes HTTP (sign_cert.csr.pem).


===Saisir une facture fournisseur dont le type est nouveau===
[[File:PublicKeysCopyScreen.png|900px]]
Lors de la saisie d'un nouveau compte fournisseur, aucun élément n'est renseigné par défaut.


Plusieurs parties du formulaire peuvent se réduire à partir du moment où dans le champ précédant la partie réductible, l'utilisateur remplace '''Autre...''' par un choix proposé. A contrario, en laissant '''Autre...''' ou '''Associer un nouveau compte''' (dans le cas du premier champ), c'est l'information saisie dans le champ suivant qui sera prise en compte.


La saisie va consister à définir les ventilations comptables associée à ce type de facture fournisseur. A savoir :
*Cliquer sur '''Enregistrer'''.
*le fournisseur et son compte fournisseur qui sera crédité du montant des factures associées à ce type de facture fournisseur
*le compte de charge qui sera débité du montant des factures associées à ce type de facture fournisseur
*Si la gestion de la TVA est activée, le compte de TVA qui sera débité du montant de la TVA associée à ce type de facture fournisseur
*De plus, si la gestion des budgets est activée, il est demandé, pour chaque compte (fournisseur, charge et TVA), le budget à associer


*Ainsi, si on doit saisir une facture fournisseur associée à un nouveau type de facture fournisseur, alors il faut laisser '''associer un nouveau compte''' au champ '''Type de facture fournisseur''' et renseigner l'intitulé souhaité pour le type de facture fournisseur dans le champ suivant '''Nom de type de facture'''
NB: La validité des certificats générés s'étend sur une période de '''3 années'''.
*Ensuite, dans le champ '''Fournisseur''', là aussi, 2 cas peuvent se présenter :
**Soit le fournisseur à mentionner est déjà dans la liste proposée dans le champ '''Fournisseur''' et auquel cas il faut le sélectionner (et le champ suivant '''Nom du fournisseur''' disparaitra)
**Soit le fournisseur à mentionner n'est pas déjà dans la liste proposée dans le champ '''Fournisseur''' et il faut dans ce cas :
***Si la gestion des budgets est active : choisir dans le champ suivant un '''Budget à attribuer'''' au fournisseur ou laisser '''Aucun budget'''. On peut, par exemple, sélectionner pour tous les fournisseurs un budget ''Fournisseurs''.
***Renseigner le champ '''Nom du fournisseur''' qui doit correspondre au fournisseur souhaité
***Choisir un compte d'export associé au nouveau fournisseur créé :
****Soit il existe déjà un compte d'export dans le champ '''Compte du fournisseur'''
****Soit il n'existe pas de compte d'export associable à ce nouveau fournisseur (en général on associe un compte d'export différent pour chaque compte défini dans OpenFlyers) et auquel cas, il faut renseigner le champ '''Compte fournisseur d'export'''. On peut, par exemple, saisir ''401NOMDUFOURNISSEUR'' dans le cas du plan comptable français.
***Si la gestion des budgets est active, le champ suivant est '''Budget à attribuer''' et OpenFlyers a dû le remplir par défaut avec la même valeur que pour le champ du même nom rattaché au '''Nom du fournisseur'''. Il ne doit pas y avoir besoin de modifier le contenu de ce champ.
*Dans le champ '''Compte de charge''', 2 cas peuvent se présenter :
**Soit le compte de charge à associer est déjà dans la liste proposée et auquel cas il faut le sélectionner
**Soit le compte de charge à associer n'est pas déjà dans la liste proposée et il faut dans ce cas :
***Si la gestion des budgets est active : choisir dans le champ suivant un '''Budget à attribuer''' au compte de charge que l'on crée ou laisser '''Aucun budget'''. On peut, par exemple, sélectionner un budget représentatif du type de facture et ainsi avoir la ventilation de toutes les factures fournisseurs par poste budgétaire
***Renseigner le champ '''Nom du compte''' avec l'intitulé correspondant au compte de charge à créer
***Renseigner le champ '''Compte d'export''' avec la valeur du plan comptable correspondante. Pour le plan comptable français, ce sera un compte en 6xx.
*Si la gestion de la TVA est activée, le champ '''Compte TVA du type de facture''' est présent et 2 cas peuvent se présenter :
**Soit le compte de TVA à associer à ce type de facture est dans la liste proposée et auquel cas il faut le sélectionner
**Soit le compte de TVA n'existe pas et il faut dans ce cas :
***Si la gestion des budgets est active : choisir dans le champ suivant un '''Budget à attribuer''' au compte de TVA que l'on crée ou laisser '''Aucun budget'''. On peut, par exemple, sélectionner un budget ''TVA Fournisseur''
***Renseigner le champ '''Nom de compte TVA'''. Par exemple, dans le cas des factures fournisseurs provenant de France : ''TVA déductible sur autres biens et services''
***Renseigner le champ '''Compte TVA d'export'''. Par exemple, dans le cas des factures fournisseurs provenant de France : ''445660''.
*Le reste de la saisie s'effectue comme pour la [[#Saisie_d'une_facture_fournisseur_dont_le_type_de_facture_est_déjà_enregistré|saisie d'une facture fournisseur dont le type de facture est déjà enregistré]].


===Supprimer une facture fournisseur===
Pour supprimer une facture fournisseur qui aurait été saisie par erreur, il faut :
*Aller dans '''Gestion > Achats > Liste des factures fournisseurs''' ;


*Rechercher le '''numéro de pointage''' de la facture concernée en ayant pris soin au préalable de sélectionner le mois de la facture (ou l'année complète pour lister l'ensemble des factures).
;Sauvegarder le couple ID/passphrase
*Cliquer sur l'icône poubelle associée à la facture concernée.
Un couple ID/passphrase (client_id/client_secret) est généré. Ces deux clées ne sont communiquées qu'une seule fois. Elle doivent être stockées en toute sécurité et gardées confidentielles,
et Mettre ces identifiants dans le fichier '''config.clientcred.json'''.
[[File:Oauth2 client created.png]]


Le numéro de la facture supprimée n'existera plus dans la comptabilité. Il ne sera pas proposé à nouveau lors de la saisie d'une nouvelle facture. Par ailleurs, la numérotation des factures saisies a posteriori de la facture supprimée demeurera inchangée.
[[File:ConfigCredJsonScreen.png|600px]]


===Supprimer un type de facture fournisseur===
Pour supprimer un type de facture fournisseur qui aurait été saisi par erreur, il faut :


*Aller dans '''Gestion > Achats > Types de factures fournisseurs''' ;
;Télécharger les certificats crt
Les certificats signés sont téléchargeables depuis l'interface de gestion des clients OAuth2, les certificats sont disponibles dans les onglets '''Certificat d'authentification''' et '''Certificat de signature''' et les mettre dans le dossier '''/ssl''' du client OAuth2.
[[File:Oauth2 client certificates.png]]


*Sélectionner le type de fournisseur concerné dans la colonne '''Nom''' ;
;Téléchargez les certificats du serveur.
*Les certificats du CA d'OpenFlyers et de signature HTTP du serveur sont nécessaires. Ils sont téléchargeables depuis l'interface de configuration des clients OAuth2.
[[File:DownloadServerCertifScreen.png|900px]]
*Dans certains cas d'utilisation, il peut être nécessaire d'ajouter le certificat du CA d'OpenFlyers au Trust Store du système. Si cette étape n'est pas réalisée, les certificats peuvent être considérés comme invalides et peuvent ne pas être utilisables.
*Pour ajouter le certificat CA au Trust Store du système, suivre les étapes suivantes:
**Sous Linux, copier le certificat CA d'OpenFlyers dans le dossier <code>/usr/local/share/ca-certificates</code> et exécuter la commande <code>sudo update-ca-certificates</code>
**Sous Windows,
***Double-cliquer sur le certificat CA d'OpenFlyers téléchargé depuis l'interface d'enregistrement des clients OAuth2
***Cliquer sur '''Installer un certificat...'''
***Choisir l'emplacement de stockage (utilisateur ou ordinateur) et cliquer sur '''Suivant''' puis '''Suivant''' et enfin '''Terminer'''


*Cliquer sur l'icône poubelle associée à la facture concernée.
==Générer des certificats==
L'API OAuth2 implémente [https://tools.ietf.org/html/draft-cavage-http-signatures-10 HTTP Signature] et l'[[Wikipedia-en:Mutual_authentication#mTLS|authentification TLS mutuelle]]. Ces mécanismes utilisent chacun une paire certificat/clé privée différente.


==Saisir un [[Écritures comptables#Paiement_(à_un_fournisseur)|paiement à un fournisseur]]==
Pour obtenir ces certificats, il faut d'abord générer des Certificate Signing Request (CSR).
*Aller dans '''Gestion > Comptes > Flux > Saisir'''
*Sur la première ligne :
**dans la colonne '''Compte''' sélectionner le compte du fournisseur
**dans la colonne '''Débit''' saisir le montant du paiement que vous effectuez. Si vous souhaiter solder le compte du fournisseur, il vous suffit de cliquer sur l'icône représentant une balance et intitulée "Ramener le solde à zéro"
*Sur la deuxième ligne :
**dans la colonne '''Compte''' sélectionner le compte bancaire depuis lequel l'argent est débité
**cliquer sur l'icône représentant une balance avec une flèche bleue et intitulée "Equilibrer la dernière ligne"
*Dans le champ '''Commentaires''' saisir un texte libre
*Ne pas oublier de renseigner le champ '''Date comptable'''
*Cliquer sur le bouton '''Valider'''


=Bon de commande et facture clients=
La procédure est la suivante :
*[[OpenSSL#Installer-OpenSSL-dans-un-environnement-Windows|Télécharger OpenSSL pour Windows]] ou [[OpenSSL#Utiliser-Openssl-d'Apache-sous-WAMP|utiliser Openssl d'Apache sous WAMP]].
*Utiliser les deux fichiers de configuration ''sign_cert.conf'' et ''auth_cert.conf'' ci-dessous et les remplir.


==Visualiser des factures au format PDF==
;Fichier de configuration OpenSSL - sign_cert.conf
Nous conseillons de se connecter en https à votre espace http://openflyers.com/demo-fr/index.php
<pre>
[req]
default_bits      = 4096                    # taille par défaut des nouvelles clés, peut être surchargé dans la commande
encrypt_key        = no                      # chiffrer la clé générée
distinguished_name = req_distinguished_name  # pointe vers la catégorie spécifiée pour le Distinguished Name
x509_extensions    = v3_req                  # pointe vers la catégorie spécifiée pour les extensions x509
prompt            = no


OpenFlyers propose par défaut un [[Modèle de facture ODT|modèle de facture]] qui permet d'éditer des factures PDF pour les prestations suivantes :
[req_distinguished_name]
*les achats réalisés depuis le gestionnaire des ventes
C                  =                          # code à deux chiffres du pays (ex: FR)
**depuis la page du planning de réservation, cliquer sur le menu '''Comptes/Acheter'''.
ST                =                          # région/état (ex: Gironde)
*les validités qui ont été achetées/renouvelées
L                  =                          # ville (ex: Bordeaux)
*les vols.
O                  =                          # organisation (ex: OpenFlyers)
**Aller dans le menu '''Comptes/Etat/Pilote''' et se rendre dans la [[Gestion de la comptabilité côté utilisateur#Colonne-Numéro-de-pointage-ou-de-facture|colonne numéro de pointage ou de facture]]
OU                =                          # unité organisationelle (ex: IT)
CN                =                          # nom de domaine (ex: openflyers.com)


==Importer son [[Modèle de facture ODT|modèle de facture]]==
[v3_req]
keyUsage          = digitalSignature        # pour quelles opérations la clé peut-elle être utilisée</pre>


'''/!\ En important un [[Modèle de facture ODT|modèle de facture]], ce sera celui-ci qui va être utilisé pour générer les nouvelles factures au format PDF. Les factures qui ont déjà créées avant la mise en place du nouveau modèle continuera d'appliquer les anciens versions du modèle ou ceux fournis par défaut par OF'''
;Fichier de configuration OpenSSL - auth_cert.conf
<pre>[req]
default_bits      = 4096                    # taille par défaut des nouvelles clés, peut être surchargé dans la commande
encrypt_key        = no                      # chiffrer la clé générée
distinguished_name = req_distinguished_name  # pointe vers la catégorie spécifiée pour le Distinguished Name
x509_extensions    = v3_req                  # pointe vers la catégorie spécifiée pour les extensions x509
prompt            = no


Le droit [[Gestion-des-profils#Gestion-du-paramétrage|Gestion du paramétrage]] est nécessaire pour cette fonctionnalité.
[req_distinguished_name]
C                  =                          # code à deux chiffres du pays (ex: FR)
ST                =                          # région/état (ex: Gironde)
L                  =                          # ville (ex: Bordeaux)
O                  =                          # organisation (ex: OpenFlyers)
OU                =                          # unité organisationelle (ex: IT)
CN                =                          # nom de domaine (ex: openflyers.com)


*Aller dans '''Admin > Structure > Imports > Templates ODT'''.
[v3_req]
* Préparer le fichier ODT contenant tous les modèles utilisés qui doivent être organisés comme suit :
extendedKeyUsage  = clientAuth              # pour quelles opérations la clé peut-elle être utilisée</pre>
**'''page 1''' : Facture client
**'''page 2''' : Duplicata de la facture client
*Importer  le fichier
*Cocher '''Facturation client'''
*Valider


==Calcul du montant HT, de la TVA et du montant TTC==


Les différents montants sont calculés de la sorte :
Exécuter les commandes suivantes :
* Pour la TVA, on effectue la somme des écritures de débit et de crédit liés aux
*<syntaxhighlight lang="bash">openssl req -sha256 -newkey rsa -keyout sign.key -out sign_cert.csr.pem -outform PEM -config sign_cert.conf</syntaxhighlight>
** comptes de catégorie TVA
*<bash>openssl req -sha256 -newkey rsa -keyout auth.key -out auth_cert.csr.pem -outform PEM -config auth_cert.conf</bash>
** produits de type de vente TVA
* Pour le montant TTC, on effectue la somme des écritures de débit et de crédit liées aux
** comptes qui ne sont pas de catégorie TVA
** produits qui ne sont pas de type de vente TVA
* Pour le montant HT, on effectue la différence entre la TVA et le montant TTC


==Relevés de factures clients==
Ces commandes prennent chacune en entrée le fichier de configuration et génèrent une clé privée et un Certificate Signing Request.  
Une facture client est un document unique. Les PDF d'une facture déjà exportée seront générés avec la mention "Duplicata".


===Formulaire de sélection===
Une fois ces CSR obtenus :
Ce formulaire permet d'établir une sélection générale de factures clients.
*Les renseigner dans les champs prévus à cet effet lors de la [[#Enregistrer-un-client|création d'un client]] et télécharger les certificats signés depuis l'interface une fois le client créé.
*Aller dans '''Gestion > Rapports > Rapports spécifiques > Relevés de factures clients'''
*Garder la clé privée confidentielle. Une fuite poserait un risque de sécurité. Elle va de paire avec le certificat distribué par l'autorité de certification OpenFlyers.
*Les champs "A partir du" et "Jusqu'au" permettent d'établir une sélection en fonction des dates de facturation. Pour éviter un temps de chargement trop long, il est conseillé de définir une plage d'un mois maximum.
*Le champ "Compte client" permet de sélectionner les relevés de factures d'un client particulier
*Le champ "Types de vols" permet de sélectionner des factures en fonction du type de vol
*Le champ "Produits non stockés" permet de sélectionner des factures en fonction de la nature du produit
*Le champ "Validités" permet de sélectionner des factures en fonction des produits de type "validité"
*Valider pour accéder aux factures à exporter


===Tableau de factures à exporter===
==Mettre en place une connexion à l'API OpenFlyers sur un serveur mutualisé==
Ce tableau est le résultat du formulaire. Il liste des factures clients.
;Note
*Sélectionner au cas par cas les factures à exporter
La procédure ci-après est destinée à une mise en place lorsqu'il n'y a pas d'accès SSH en ligne de commande mais uniquement un accès FTP. Dans ce cas, la création des clés privées et publics est effectuée "en local". Dans la procédure suivante elle est effectuée depuis un PC sous '''Windows'''.
*Cliquer sur le bouton '''Générer PDF''' en bas du tableau pour générer le PDF des factures sélectionnées
*Cliquer sur le bouton '''Sauver dans un fichier CSV''' en bas du tableau pour exporter les factures sélectionnées au format CSV


=Gestion des règlements clients=
;Prérequis
==Saisir un encaissement==
*Posséder les accès FTP :
;Préambule
**Hôte : XXXXXXXXXXXXXXXXXX
Se référer aux [[Écritures comptables#Encaissement-(d'un-client)|mouvements associés à un encaissement client]] pour connaitre les mouvements créés lors de la saisie comptable.
**Login : XXXXXXXX
**Mot de passe :  XXXXXXXX
**Port : XX (par exemple 21)
*Télécharger Le code source du client de démonstration à disposition par OpenFlyers à cette adresse : https://openflyers.com/oauth2-demo/oauth2-demo-src.zip


;Procédure
;Procédure
* Aller dans '''Planning > Comptes > Approvisionner''' ou '''Gestion > Comptes > Encaissements > Saisir'''
*[[#Générer des certificats|Générer les certificats]] en local.
*'''Comptes clients''' :
*Remplacer les clés privées 'auth.key' et 'sign.key' présentes dans les dossiers 'ssl/AuthCodeDemo' et 'ssl/ClientCredDemo' par les clés générées.
**Si le payeur est un utilisateur de la plateforme, laisser sélectionné '''Utilisateur'''
*[[#Enregistrer un client|Enregistrer les deux clients]] :
**Sinon, sélectionner '''Non-utilisateur'''
**Le premier pour le mécanisme d'autorisation '''Authorization Code'''.
*(facultatif) '''Email client''' : si '''Non-utilisateur''' est sélectionné, saisir l'adresse email du client où sera [[Envoi-des-emails#E-mail-de-reçu|envoyé le reçu pour le paiement]]
**Le second pour le mécanisme d'autorisation '''Client Credentials'''.
*'''Encaissement de l'utilisateur''' : sélectionner le nom de l'utilisateur effectuant le paiement
 
ou
[[File:Oauth2 demo manage.png|800px]]
*'''Compte à créditer''' : sélectionner le nom du client "connu" ou à défaut "Clients extérieurs"
 
** Seuls les [[Comptabilité#Comptes_clients|comptes clients]] et les [[Comptabilité#Comptes_utilisateurs|comptes utilisateurs]] peuvent recevoir des encaissements. En effet, comptablement, cela n'a pas de sens de faire un encaissement sur d'autres types de comptes.
*Télécharger le certificat du CA OpenFlyers en cliquant sur le bouton '''Télécharger le certificat CA''' de la page de gestion.
** Il faut sélectionner le compte correspondant à celui qui a été facturé et donc débité.
*Télécharger aussi le certificat de signature du serveur en cliquant sur le bouton '''Télécharger le certificat de signature du serveur''' de la page de gestion.
*'''Type de règlement''' : sélectionner le type de règlement.
*Placer les deux certificats téléchargés à la racine du dossier '''ssl'''.
** Les types de règlement disponibles dépendent du compte à créditer pour s'assurer que [[Gestion-de-la-comptabilité-côté-utilisateur#Les-comptes-n'ont-pas-la-même-comptabilité|les comptes ont la même comptabilité]].
 
*'''Montant''' : indiquer le montant du paiement qui doit être dans l'intervalle '''[Montant minimum, Montant maximum]''' autorisé pour chaque [[Configuration-de-la-comptabilité#Types-d'encaissements|type d'encaissement]].
*Télécharger les deux certificats ''Certificat d'authentification'' et ''Certificat de signature'' du client '''Authorization Code''' et les placer dans le répertoire '''ssl/AuthCodeDemo'''.  
*'''Date du paiement''' :
*Modifier le fichier '''ssl/AuthCodeDemo/config.authcode.json''' en le remplissant de la manière suivante.
**pour les paiements par chèque on laisse en général la date du jour qui correspond "au mieux" à la date à laquelle l'encaissement sera fait.
<syntaxhighlight lang="php">
**pour les paiements en espèce on met la date à laquelle les sommes ont été reçus.
{
**pour les paiements par virement on met la date du virement.
  "client_id": "XXXXXXXXXXXXXXXX",
*'''Heure du paiement''' : l'heure est saisie automatiquement et ne peut pas être saisie manuellement
  "client_secret": "XXXXXXXXXXXXXXXX",
**Si la date du paiement correspond à la date du jour alors c'est l'heure courante qui est saisi
  "authorize_uri": "https://openflyers.com/mastructure/oauth/authorize.php",
**Autrement, l'heure est fixée à 12h00.
  "token_uri": "https://openflyers.com/mastructure/oauth/access_token.php",
*Cliquer sur le bouton '''Valider''' ou le bouton '''VALIDER ET SAISIR LE SUIVANT'''
  "resource_uri": "https://openflyers.com/mastructure/oauth/resources.php",
  "revoke_uri": "https://openflyers.com/mastructure/oauth/revoke.php",
  "auth_cert": "./ssl/AuthCodeDemo/auth_cert.crt",
  "auth_key": "./ssl/AuthCodeDemo/auth.key",
  "sign_cert": "./ssl/AuthCodeDemo/sign_cert.crt",
  "sign_key": "./ssl/AuthCodeDemo/sign.key",
  "auth_cacert": "./ssl/ca.crt",
  "sign_cert_server": "./ssl/sign_cert_server.crt"
}
</syntaxhighlight>
 


==Saisir l'encaissement d'un comité d'entreprise ou d'un groupement d'utilisateurs==
*Télécharger les deux certificats ''Certificat d'authentification'' et ''Certificat de signature'' du client '''Client Credentials''' et les placer dans le répertoire '''ssl/ClientCredDemo'''.
Dans le cas où un comité d'entreprise ou un groupement d'utilisateurs (comme la DGAC dans le cas des contrôleurs aériens) prend en charge tout ou partie du coût d'une activité générée par OpenFlyers, la plateforme est normalement configurée pour que ce soit le compte du CE ou du groupement qui soit automatiquement débité lors de la saisie de l'activité.
*Modifier le fichier '''ssl/ClientCredDemo/config.clientcred.json''' en le remplissant de la manière suivante.
<syntaxhighlight lang="php">
{
  "client_id": "XXXXXXXXXXXXXXXX",
  "client_secret": "XXXXXXXXXXXXXXXX",
  "token_uri": "https://openflyers.com/mastructure/oauth/access_token.php",
  "resource_uri": "https://openflyers.com/mastructure/oauth/resources.php",
  "revoke_uri": "https://openflyers.com/mastructure/oauth/revoke.php",
  "auth_cert": "./ssl/ClientCredDemo/auth_cert.crt",
  "auth_key": "./ssl/ClientCredDemo/auth.key",
  "sign_cert": "./ssl/ClientCredDemo/sign_cert.crt",
  "sign_key": "./ssl/ClientCredDemo/sign.key",
  "auth_cacert": "./ssl/ca.crt",
  "sign_cert_server": "./ssl/sign_cert_server.crt"
}
</syntaxhighlight>


De ce fait, saisir dans OpenFlyers un règlement provenant d'une telle entité revient à [[#Saisir_un_encaissement|saisir un encaissement d'un "non-utilisateur" sur son propre compte client]].
*Remplacer les <code>XXXXXXXXXXXXXXXX</code> des champs <code>client_id</code> et <code>client_secret</code> par les valeurs obtenues lors de [[#Enregistrer-un-client|l'enregistrement du client]].
*Remplacer <code>mastructure</code> par le nom de la structure sur laquelle la démo est testée.


==Pointer les encaissements==
*Transférer le fichier "oauth-demo" vers le serveur mutualisé :
;Introduction
**Télécharger [http://filezilla-project.org/download.php?type=client FileZilla].
Le pointage des encaissements doit être effectué à intervalle régulier comme préconisé dans le [[Bien-débuter-avec-OpenFlyers#Validation-des-écritures|workflow de validation des écritures]].
**Lancer FileZilla.
**Entrer l'URl du serveur mutualisé dans le champ '''Hôte'''.
**Entrer le login dans le champ '''Nom d'utilisateur'''.
**Entrer le mot de passe dans le champ '''Mot de passe'''.
**Entrer le port dans le champ '''Port'''.
**Cliquer sur le bouton Connexion.
**Accéder à l'emplacement du répertoire "oauth-demo" en local à gauche dans l'onglet "Site local".
**Choisir l'emplacement où placer le répértoire oauth-demo dans l'anglet '''Site distant''' à droite.
**Glisser et déposer le oauth-demo à l'emplacement choisi.
[[File:Transfer OauthDemo To Shared Server.png|800px]]
*Accéder au client OAuth-demo depuis le serveur mutualisé en utilisant l'URL du domaine du serveur : url_de_domaine_de_serveur/oauth-demo/index.php
*Modifier la valeur '''URI de redirection vers le client''' du client '''AuthCodeDemo''' précédemment créé en remplaçant l'ancienne URL par la nouvelle.


Cela doit être fait au plus tard avant de remettre l'argent en banque, que la saisie soit faite par un utilisateur final ou par un gestionnaire de la structure.
*Suivre la [[#Utiliser le client|procédure d'utilisation du client de démonstration]] pour faire fonctionner le client.


Lorsqu'il existe des encaissements non validés qui datent de plus de 30 jours, un message d'alerte de configuration s'affiche indiquant ''[[Alertes-de-configuration#Il-y-a-des-encaissements-non-pointés-qui-datent-de-plus-de-30-jours|Il y a des encaissements non pointés qui datent de plus de 30 jours]]''.


Tant qu'une écriture n'est pas validée, elle apparaît différemment dans les extraits de compte afin de bien signifier que le mouvement n'est pas validé et que le solde du compte est donc susceptible d'être modifié. Une écriture non validée peut être modifiée mais '''Une fois la validation effectuée'''  il n'est plus possible de '''revenir en arrière'''.  
'''NB''': Le certificat de signature du serveur est unique à chaque plateforme et serveur. Ainsi, si le serveur ou la plateforme est modifié, le certificat doit être renouvelé.


En cas d'erreur, la personne en charge de la tenue des comptes doit [[#Contrepasser-une-écriture|contrepasser l'écriture]] et ressaisir le règlement correct.
==Récupérer les données d'un utilisateur==
*Cliquer sur le bouton '''Récupérer les informations utilisateur''', l'identifiant de l'utilisateur s'affiche.
*Utiliser l'identifiant récupérer afin de récupérer toute information associée à cet utilisateur en [[Gestion-des-rapports#Ajouter-un-rapport|créant de nouveaux rapports personnalisés]]


Pour pointer les encaissements, il faut disposer du droit [[Gestion-des-profils#Valider-les-encaissements|Valider les encaissements]].
==Utiliser le client==
;Prérequis
Un client de démonstration est disponible pour le logiciel OpenFlyers à cette adresse : https://openflyers.com/oauth2-demo/index.php


;Procédure
Le client de démonstration se présente de la manière suivante.
Voici la description de la tâche de validation des règlements de type "Chèques" :
*Récupérer les chèques et espèces à valider
*Classer les chèques par ordre de date
*Aller dans la page '''Gestion > Comptes > Pointer > Chèques'''
Cette pages affiche les règlements saisis et qui sont en attente de validation :
*Cocher les règlements correspondants aux chèques physiquement présents
*Si des règlements ne sont pas dans la liste en attente, il faut suivre la procédure [[#Saisir-un-encaissement|Saisir un encaissement]]
*Si des saisies sont erronées, il faut les corriger puis les cocher
*Supprimer les saisies inopportunes
Une fois que tout les chèques sont pointés :
*Cliquer sur le bouton '''Éditer le bordereau de remise'''
Une nouvelle page s'affiche contenant la liste des règlements validés :
*Vérifier sa conformité
*Imprimer le bordereau de remise en banque avec la [[Navigateurs#Imprimer-avec-un-navigateur|fonction du navigateur prévue à cet effet]]
*Cliquer sur le bouton '''Valider (penser à imprimer avant)'''
A l'issue de cette procédure, il faut porter la remise en banque.


Une fois le paiement validé, [[Envoi-des-emails#E-mail-de-reçu|un email sera automatiquement envoyé à l'utilisateur avec en pièce-jointe le reçu au format PDF]].
[[File:Oauth2-client-demo.png]]


Le processus est le même pour les autres types de règlements à l'exception des [[Paiement en ligne|paiements en ligne]] qui sont automatiquement validées et qui par conséquent ne nécessitent pas de pointage.
La démonstration est composée de deux colonnes. La première, nommée '''Authorization Code''' correspond [[#Authorization Code|au mécanisme d'autorisation du même nom]]. Elle dispose d'un bouton permettant de se connecter ainsi que d'une section indiquant les informations relatives à l'état de la connexion. La seconde colonne, nommée '''Client Credentials''' correspond elle aussi [[#Client Credentials|au mécanisme d'autorisation du même nom]]. Comme pour la première colonne, les éléments qui y sont présentés sont identiques. La différence étant que le bouton de connexion n'a pas le même effet étant donné que ces deux mécanismes sont différents. Chaque mécanisme est indépendant et il est possible de se connecter à un des deux mécanismes sans se connecter à l'autre ou se connecter aux deux en même temps.


Il est possible de retrouver la liste des encaissements par type et pour la période souhaitée dans le [[Gestion-des-rapports|rapport]] '''Gestion > Comptes > Rapports > Paiements par type'''.
;Le mécanisme '''Authorization Code'''
*Cliquer sur le bouton '''Se connecter''' (ce qui redirige le navigateur vers la page de connexion du logiciel OpenFlyers).
*Renseigner les identifiants de l'administrateur pour s'y connecter.
*Nom d'utilisateur : '''admini'''.
*Mot de passe : '''azerty'''.


Le [[Gestion-des-rapports|rapport]] '''Gestion > Comptes > Rapports > Ventilation des encaissements''' permet d'avoir le tableau du total des encaissements par mois et par type.
Une fois les informations saisies, la page suivante est affichée.


'''La validation régulière des règlements permet de bloquer les modifications par les utilisateurs et facilite la tenue de la comptabilité'''.
[[File:Oauth authorize demo.png]]


=Cas particuliers=
*Cliquer sur le bouton '''Autoriser l'application''' pour autoriser la connexion (ce qui se redirige le navigateur vers la page du client de démonstration OAuth2).
==Ecritures manuelles dans le cadre [[Conseils spécifiques pour la comptabilité#Facturation_automatique_à_des_organismes_avec_ou_sans_quotas|d'une facturation automatique à un organisme tiers]]==
Normalement, le paramétrage d'une plateforme OpenFlyers est effectué pour que les écritures soient automatiquement réalisées lors de la saisie d'une activité.


Cependant, si les écritures ne se sont pas effectuées automatiquement, ne serait-ce que dans le cas où le paramétrage a été mis en place ultérieurement à la saisie d'activité et que ces saisies ont été validées, il est nécessaire d'effectuer la saisie manuelle des flux permettant d'obtenir la [[Facturation des clients#Cascade_d'écritures_comptables|situation comptable souhaitée]].
La première colonne doit afficher l'état de connexion '''Connecté''' ainsi qu'un nouveau bouton '''Récupèrer les informations utilisateurs''' qui permet de récupérer les informations de l'utilisateur connecté.


Ainsi, si l'utilisateur a été intégralement débité du montant de l'activité et qu'il faut simplement saisir une écriture permettant de créditer son compte du montant devant être débité à l'organisme tiers, il suffit de [[#Saisir-un-flux|saisir un flux]] qui va créditer son compte du montant concerné et débiter le compte de l'organisme tiers du même montant.
;Le mécanisme '''Client Credentials'''
*Cliquer sur le bouton de connexion '''Se connecter''': contrairement à celui du mécanisme '''Authorization Code''', ne redirige pas le navigateur vers la page de connexion du logiciel OpenFlyers. Le bouton de connexion utilise les identifiants du client, ici le couple clé privée/clé publique, pour initier la connexion avec le serveur d'autorisation et obtenir un jeton d'accès.


De plus, si une gestion de quota est en place, il faut également corriger le compte de quota de l'utilisateur concerné sur le même modèle d'écriture que pour l'[[Conseils spécifiques pour la comptabilité#Initialisation_du_solde_d'heures_lors_de_la_mise_en_place_des_packs|initialisation d'un quota]] mais en sens opposé (le compte quota de l'utilisateur doit être débité au lieu d'être crédité et inversement pour le compte de bilan correspondant).
Une fois la connexion établie, la seconde colonne doit afficher l'état de connexion '''Connecté''' ainsi qu'un menu déroulant '''Rapport à récupèrer''' et un nouveau bouton '''Récupèrer le rapport''' qui permet de récupérer les rapports génériques et personnalisés.


==Gestion des baptêmes==
Certaines structures utilisent le terme "Vol découverte" pour parler des baptêmes de l'air. Il est important d'avoir en tête que le grand public ne connait que le terme "Baptême de l'air" et que par conséquent ce terme est sans commune mesure plus utilisé dans les moteurs de recherche en comparaison au terme "Vol découverte".


===Utiliser le [[Configuration de la comptabilité#Gestion_des_baptêmes|paramétrage préconisé des baptêmes]]===
Le client, une fois connecté sur les deux mécanismes, se présente de la manière suivante.
On appelle dans l'exemple "Clients extérieurs" le compte client qui est utilisé pour les clients baptêmes. La dénomination peut changer d'une configuration à l'autre.
Lorsqu'un baptême est effectué :
#Saisir le vol avec comme type de vol "baptême" : le coût du vol au tarif solo est alors débité sur le compte client "Clients extérieurs".
#Encaisser le paiement du baptême sur le compte client "Clients extérieurs" (qui est accessible dans le formulaire de saisie des encaissements en sélectionnant comme type d'utilisateur "non utilisateur").
Ainsi, le compte client "Clients extérieurs" se retrouvera avec un solde correspondant à la différence entre le prix du baptême payé par le client et le prix du vol solo facturé par OpenFlyers.


Il suffit alors de passer une écriture, par exemple tous les mois, tous les trimestres ou tous les ans, entre le compte "Clients extérieurs" et un compte produit (par exemple compte produit "gain baptêmes") qui va recevoir le solde du compte client "Clients extérieurs" afin de le ramener à 0.
[[File:Oauth2 connected demo.png]]


Cas pratique :
=Troubleshooting=
*Tarif solo : 100 €/h
==500 Internal Server Error en récupérant le rapport==
*Tarif baptême : 60 €
La démo utilise les valeurs par défaut pour extraire les rapports. Une erreur 500 indique une "Erreur de syntaxe ou violation d'accès" lors de l'exécution de la requête du rapport. Cela se produit parce que le rapport n'a pas de valeurs par défaut associées, étant donné qu'il n'a jamais été visualisé dans l'interface web. Pour résoudre ce problème, il vous suffit de visualiser le rapport et de cocher la case "Mémoriser ce choix".
*Vol effectué de 30 minutes


Alors :
==Erreur "File not found."==
*En saisissant le vol de 30 minutes en tant que "baptême", le compte "clients extérieurs" sera débité du montant du tarif solo soit 50 €.
Cette erreur se produit lorsque l'URI utilisé n'existe pas sur le serveur OpenFlyers. Vérifier les URIs mis en place dans les fichiers de configuration et essayer de nouveau.
*En saisissant l'encaissement du paiement de 60 € effectué par le baptisé, le compte "clients extérieurs" sera crédité de 60 €.
*Il est résulte sur le compte "clients extérieur" un solde positif de 10 €.
A la fin de l'année, le compte "clients extérieur" aura vu son solde augmenter au fur et à mesure que les baptêmes auront été effectuée. Admettons qu'il se retrouve avec un solde positif de 1210 €. Il faut alors passer un mouvement (via la saisie d'un flux) pour débiter le compte "clients extérieurs" de 1210 € et créditer un compte produit "delta baptêmes" de 1210 €. Ce faisant le solde du compte client "clients extérieurs" est ramené à 0 ce qui est important pour montrer comptablement que personne ne doit de l'argent à l'aéro-club et le gain spécifique aux baptêmes est enregistré dans le compte produit "delta baptêmes".


==Mettre à jour un plafond d'heures ou d'argent sur une comptabilité parallèle==
==int_rsa_verify : longueur de signature incorrecte==
Certaines plateformes sont paramétrées avec une comptabilité parallèle permettant de [[Conseils-spécifiques-pour-la-comptabilité#Facturation-automatique-à-des-organismes-avec-ou-sans-quotas|gérer des quotas d'heures ou d'argent]] pour des utilisateurs du fait de leur appartenance à une entreprise, un comité d'entreprise ou un comité d'établissement public, etc.
Ce problème pourrait survenir si les fichiers ca.cert et sign_cert_server.cert ne proviennent pas du même serveur que celui du client oauth2.
La solution est :


Les utilisateurs disposent alors d'un 2ème compte qui permet de suivre l'évolution de leur utilisation du quota qui leur est attribué, par exemple annuellement. Si le montant crédité automatiquement ne correspond pas au quota auquel ils ont droit, il faut alors, pour chaque compte utilisateur concerné, saisir un flux qui permet de mettre à jour ce quota.
*D'essayer depuis le début l'étape de [[#Générer-des-certificats|génération]] et de [[#Enregistrer-un-client|configuration des certificats]] avec jsut la modification du client existant.


Pour cela, il faut d'abord identifier le compte de contre-partie qui est utilisé pour les écritures comptables qui impactent ce compte. Généralement ce compte est un compte de bilan dont le nom est du type "Encours...". Pour le trouver 2 méthodes :
;Si cela ne fonctionne pas:
#Aller sur '''Gestion > Comptes > Lister les comptes > Comptes bilan''' puis rechercher le compte concerné
#Aller sur le compte de suivi de quota d'un utilisateur concerné et identifier dans la cellule de la colonne '''Compte affecté''' et de la ligne d'une écriture comptable d'initialisation, le nom du compte de contre-partie.


Une fois ce travail d'identification effectué, il faut, pour chaque compte utilisateur concerné, saisir un flux dans '''Gestion > Comptes > Flux > Saisir''', en mettant au crédit ou au débit le compte quota de l'utilisateur concerné et en contre-partie le compte d'encours identifié précédemment. Le montant doit correspondre à la différence entre ce qu'aurait du être le plafond initial et le plafond attribué. Si le plafond final est supérieur, alors c'est un crédit sur le compte utilisateur. Sinon, c'est un débit.
*Essayez de créer [[#Enregistrer-un-client|un nouveau client]] et refaites la configuration des certificats.

Revision as of 16:41, 24 October 2025

Présentation

L'objet de cette page est de décrire l'API OpenFlyers.

Description de l'API

OpenFlyers possède une API basée sur OAuth2 qui permet à des serveurs extérieurs, dûment enregistrés, de mettre en œuvre un processus d'authentification unique (SSO) et/ou de récupération des résultats des requêtes SQL de la bibliothèque des rapports ou des rapports personnalisés sous la forme de fichiers CSV.

OAuth2 propose plusieurs mécanismes pour permettre l'authentification. Un mécanisme d'authentification détermine la séquence exacte des étapes impliquées dans le processus d'authentification d'OAuth2. OpenFlyers met à disposition deux mécanismes d'authentification :

  • Authorization Code basé sur la méthode d'authentification par code d'autorisation et qui correspond au mécanisme associé à l'authentification unique (SSO),
  • Client Credentials basé sur la méthode d'authentification avec les identifiants clients et qui est utilisé dans un contexte d'automatisme sans autorisation de l'utilisateur au préalable.

Dans les chapitres qui suivent, le terme ressource fait référence à la définition OAuth2. Une ressource dans OAuth2 est un élément qui peut être :

  • une ou des données comme des photos, des documents, des contacts ou des informations personnelles,
  • un ou plusieurs services comme des transferts de fonds, la récupération de rapports ou l'ajout d'articles sur un blog,
  • toute ressource nécessitant un accès restreint.

OpenFlyers définit plusieurs types de ressources :

OAuth2 dispose de scopes. Un scope est un privilège définit de manière explicite permettant l'accès à une ressource protégée. OpenFlyers met à disposition une liste de scopes utilisables à travers l'API.

Deux protocoles de sécurité sont présents dans l'API OpenFlyers :

  • mTLS : il permet d'authentifier le client avec un certificat TLS, en plus d'authentifier le serveur avec un certificat. Ce protocole permet d'éviter les usurpations d'identité.
  • HTTP-Signature : il permet de signer les en-têtes et le corps (lorsqu'il y en a un) des messages échangés afin d'en garantir leur intégrité.
Premiers pas - Client de démonstration

Un client de démonstration est disponible pour comprendre les mécanismes décrits ci-dessous.

L'utilisation de ce client de démonstration est décrite dans la procédure Utiliser le client de cette page.

Le client de démonstration est lui-même accessible à cette adresse : https://openflyers.com/oauth2-demo/index.php

Le code source du client de démonstration est mis à disposition par OpenFlyers à cette adresse : https://openflyers.com/oauth2-demo/oauth2-demo-src.zip

L'utilisation du code source est décrite dans la procédure Créer un client à partir du code source.

Définitions

Authentification TLS Mutuelle (mTLS)

En général dans une communication TLS, seul le serveur a l'obligation de fournir un certificat. Il est également possible pour le client de fournir un certificat. Ce principe s'appelle l'authentification mutuelle et est mise en place avec Mutual TLS (ou mTLS).

OpenFlyers associe un certificat pour l'authentification mutuelle unique à chaque client OAuth2.

Envoyer un certificat client

Côté client, le code suivant peut être utilisé pour fournir à cURL le certificat et la clé correspondante ainsi que le certificat du CA d'OpenFlyers à utiliser pour la connexion :

curl_setopt_array($request, [
    CURLOPT_SSLVERSION => CURL_SSLVERSION_TLSv1_2,
    CURLOPT_CAINFO     => $caCertificatePath,
    CURLOPT_SSLCERT    => $certificatePath,
    CURLOPT_SSLKEY     => $keyPath
]);

À noter : le certificat du CA d'OpenFlyers est nécessaire pour assurer la validité des certificats utilisés.

HTTP Signature

HTTP Signature utilise le principe de la signature numérique pour garantir l'authenticité et l'intégrité du message HTTP.

La signature est générée à l'aide d'une clé privée et vérifiée à l'aide de la clé publique correspondante ou d'un certificat contenant cette clé publique.

HTTP Signature utilise deux en-têtes HTTP :

  • Signature : contient la signature et ses métadonnées.
  • Digest : contient le corps du message haché.

Digest

Le digest est calculé comme ceci : digest = base64encode(sha256(corps du message))

Et l'en-tête est structuré de la manière suivante : Digest: SHA-256=<digest>

Un autre algorithme de hachage peut être utilisé, SHA-256 reste cependant le plus répendu.

Exemple en php
$digestHeader = 'Digest: SHA-256=' . base64_encode(hash('sha256', $postData, true));

Signature

L'en-tête HTTP de signature est structuré de la manière suivante : Signature: keyId="<keyId>",algorithm="<algo>",headers="<signed_headers>",signature="<signature>"

Le champ keyId correspond à un identifiant permettant l'identification de la clé utilisée pour vérifier la signature. Pour l'API d'OpenFlyers, sa valeur correspond à l'empreinte SHA-1 du certificat au format PEM à utiliser pour vérifier la signature.

L'algorithme algo correspond à celui utilisé pour générer la signature, exemple : rsa-sha256.

La valeur de signed_headers correspond à la liste des en-têtes inclus dans la signature séparés d'un espace. Exemple : date digest (request-target)

Pour générer la signature, une chaîne de caractères appelée signing string contenant les en-têtes au format lowercase_header_name: value séparés par une nouvelle ligne au format LF (\n) est d'abord générée. Exemple avec les en-têtes "date" et "(request-target)" :

(request-target): post /some/uri\ndate: Tue, 07 Jun 2014 20:51:35 GMT

La signature est ensuite générée comme ceci : base64encode(algo(signing string))

Exemple en php
function generateSignatureHeader(array $headersToSign, string $certificateFingerprint, string $privateKey): string
{
    // generating the signing string and header list
    $headers         = '';
    $signatureString = '';
    foreach ($headersToSign as $key => $value) {
        $normalizedHeaderKey = trim(strtolower($key));
        $headers             .= $normalizedHeaderKey . ' ';
        $signatureString     .= $normalizedHeaderKey . ': ' . trim($value) . "\n";
    }

    // trim extra whitespace
    $headers         = trim($headers);
    $signatureString = trim($signatureString);

    // signing the signing string
    $signature = '';
    openssl_sign($signatureString, $signature, $privateKey, 'RSA-SHA256');
    $signature = base64_encode($signature);

    // compiling the header line
    return "Signature: keyId=\"$certificateFingerprint\",algorithm=\"rsa-sha256\",headers=\"$headers\",signature=\"$signature\"";
}

Les variables $certificateFingerprint et $privateKey correspondent respectivement à une empreinte SHA-1 de certificat et à une clé privée, tous deux au format PEM.

La variable $headersToSign est un tableau formaté de la manière suivante :

[
    $headerName => $headerValue,
    ...
]

À noter : les en-têtes sont à signer côté client avec le certificat de signature signé par le CA d'OpenFlyers et dédié au client ainsi que la clé privée associée. Le certificat de signature dédié au client est téléchargeable depuis l'interface de gestion des clients OAuth2. Les en-têtes de la réponse du serveur quant à elles doivent être vérifiées avec le certificat de signature HTTP du serveur, téléchargeable aussi depuis l'interface de configuration des clients OAuth2.

Client OAuth2

Une fois le client OAuth2 configuré sur OpenFlyers, il faut l'utiliser avec un client créé au préalable pour communiquer avec le serveur d'autorisation et l'API.

Plusieurs bibliothèques simplifiant la création d'un client sont disponibles.

Des scripts client basiques écrits en php sont aussi fournis pour les mécanismes authorization_code et client_credentials

Authorization Code

Ce flux OAuth2 se déroule en plusieurs étapes :

  • Le client redirige le navigateur de l'utilisateur vers l'URL d'autorisation.
  • Le navigateur de l'utilisateur est redirigé vers l'URL fournie durant la demande ou durant l'enregistrement du client.
  • Le client récupère un code d'autorisation grâce à la redirection précédente, et échange ce code contre un jeton d'accès auprès du serveur d'autorisation.
  • Le client peut utiliser ce code d'accès :
    • Comme preuve d'authentification pour une solution SSO (Single Sign-On).
    • Pour accéder à des données sur le serveur distant en utilisant l'API.


   +-----------+                   +------+                +-----------+                  +-------------+                  +-----------+
   |Utilisateur|                   |Client|                |Navigateur |                  |   Serveur   |                  |  Serveur  |
   +-----+-----+                   +--+---+                +-----+-----+                  |Autorisation |                  | Ressources|
         |                            |                          |                        +------+------+                  +-----+-----+
         |                            |                          |                               |                               |
         |                            |                   Demande|d'autorisation                 |                               |
         |                            +------------------------->+------------------------------>|                               |
         |                            |                          |                               |                               |
         |                            |Authentification + Formulaire d'autorisation              |                               |
         |<---------------------------+--------------------------+<------------------------------+                               |
         |                            |                          |                               |                               |
         |                            |      Autorisation        |                               |                               |
         +----------------------------+------------------------->+------------------------------>|                               |
         |                            |                          |                               |                               |
         |                            |                      Code|d'autorisation                 |                               |
         |                            |<-------------------------+<------------------------------+                               |
         |                            |                          |                               |                               |
         |                            |                Demande de|token                          |                               |
         |                            +--------------------------+------------------------------>|                               |
         |                            |                          |                               |                               |
         |                            |                 Access (+|Refresh) token                 |                               |
         |                            |<-------------------------+-------------------------------+                               |
         |                            |                          |                               |                               |
         |                            |                          |       Requête vers API        |                               |
         |                            +--------------------------+-------------------------------+------------------------------>|
         |                            |                          |                               |                               |
         |                            |                          |                               |                               |
         |                            |                          |                               |<------------------------------+
         |                            |                          |                               |  Vérification d'autorisation  |
         |                            |                          |                               +------------------------------>|
         |                            |                          |                               |                               |
         |                            |                          |      Données/Réponse          |                               |
         |                            |<-------------------------+-------------------------------+-------------------------------+
         |                            |                          |                               |                               |


Générer les codes pour PKCE

Pour faire fonctionner le client OAuth2, il faut générer deux codes pour l'extension PKCE :

  • Un code_verifier échangé pendant la demande de jeton d'accès.
  • Un code_challenge dérivé du code_verifier et échangé pendant la demande d'autorisation.
Générer le code_verifier
function generateCodeVerifier($length = 128): string
{
    $characters = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-._~';
    $outputCode = '';

    for ($i = 0; $i < $length; $i++) {
        $index      = random_int(0, strlen($characters) - 1);
        $outputCode .= $characters[$index];
    }

    return $outputCode;
}

Cette fonction permet de générer un code_verifier avec une longueur donnée. Le code_verifier doit avoir une longueur entre 43 et 128 caractères.

Générer le code_challenge
function computeCodeChallenge(string $codeVerifier): string
{
    return strtr(rtrim(base64_encode(hash('sha256', $codeVerifier, true)), '='), '+/', '-_');
}

Cette fonction génère le code_challenge à partir du code_verifier fourni.

Demande d'autorisation

Pour initier la demande d'autorisation, rediriger le navigateur de l'utilisateur vers l'URL : GET https://openflyers.com/nom-de-plateforme/oauth/authorize.php. Remplacer nom-de-plateforme par le nom de la plateforme utilisée.

Envoyer également les paramètres suivants :

Nom Type Description
client_id string L'identifiant unique reçu pendant l'enregistrement du client.
response_type string Le type de réponse envoyée par le serveur. Doit utiliser la valeur "code" (sans guillements) pour ce mécanisme.
redirect_uri string L'URI (ou l'URL) fourni pendant l'enregistrement du client et vers lequel l'utilisateur est redirigé après la demande d'autorisation.
scope string Liste des droits demandés par le client, séparés par des espaces.
state string Chaîne de caractère aléatoire utilisée pour éviter les attaques CSRF. Fortement recommandé.
code_challenge string Code nécessaire pour le fonctionnement de l'extension PKCE.
code_challenge_method string Méthode utilisée pour générer le code_challenge. Ici, la valeur est "S256".

Demande de jeton d'accès

Après avoir répondu à la demande, l'utilisateur est redirigé vers l'URI fourni pendant l'enregistrement du client. Si la demande est acceptée, un code temporaire : code est fourni, ainsi que le paramètre state fourni pendant la demande avec la même valeur. Si la demande est refusée, un code d'erreur est renvoyé.

Si le paramètre state a une valeur différente de celle envoyée avec la demande, c'est peut-être une tentative d'attaque et il faut refuser la réponse.

Echanger ce code contre un jeton d'accès via l'URL : POST https://openflyers.com/nom-de-plateforme/oauth/access_token.php. Remplacer nom-de-plateforme par le nom de la plateforme utilisée.

Les paramètres suivants sont également nécessaires :

Nom Type Description
client_id string L'identifiant unique reçu pendant l'enregistrement du client.
client_secret string La passphrase reçue pendant l'enregistrement du client.
code string Le code temporaire reçu dans la réponse à la demande d'autorisation.
grant_type string Le mécanisme d'autorisation utilisé. Ici, sa valeur doit être authorization_code
redirect_uri string L'URI (ou l'URL) de redirection fourni pendant l'enregistrement du client.
code_verifier string Le code_verifier utilisé pour générer le code_challenge de la demande d'autorisation.

Si la requête est correcte, un jeton d'accès (Access Token) ainsi qu'un jeton de rafraîchissement (Refresh Token) sont fournis en réponse dans un objet au format JSON.

{
  "token_type": "Bearer",
  "expires_in": 3600,
  "access_token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJSU-G7fOBOYzOgioSGNIQKI2A2OxjsNBbOOoaHsqNpsQxWZse3rofExAaGKh2tXbeuz1YAVhdLUGYgq-oKRK4ONFhw2NvcRf3QPxQXZImLWw",
  "refresh_token": "a59ef39fa9bab9b95cd554f921e7f3080a34c90f23d2b8031d692b5ff2d0993dcc392d9c7f9a43242337ef144c1a5fe1d0174413ade973e1b628ac0bbfc39b23973534"
}

Script client : Authorization Code

Voici un exemple simple de client OAuth2 pour le mécanisme d'authentification par code d'autorisation (Authorization Code).

Fichier de configuration config.authcode.json :

{
  "client_id": "",
  "client_secret": "",
  "authorize_uri": "https://openflyers.com/nom-de-plateforme/oauth/authorize.php",
  "token_uri": "https://openflyers.com/nom-de-plateforme/oauth/access_token.php",
  "resource_uri": "https://openflyers.com/nom-de-plateforme/oauth/resources.php",
  "auth_cert": "/path/to/client/auth_cert.crt",
  "auth_key": "/path/to/client/auth.key",
  "sign_cert": "/path/to/client/sign_cert.crt",
  "sign_key": "/path/to/client/sign.key",
  "auth_cacert": "/path/to/ca.crt",
  "sign_cert_server": "/path/to/server/sign_cert_server.crt"
}

/path/to/client/ est le chemin d'accès vers le dossier qui contient le code source du client de démonstration.

  • Exemple sur windows: C:/wamp64/www/4.0/oauth-demo/ssl/AuthCodeDemo/.
  • Exemple sur un serveur Linux debian: ./ssl/AuthCodeDemo/.

Ce fichier de configuration doit se situer au même niveau que le script PHP dans le système de fichiers.

Script PHP :

<?php
$localTokenFile    = 'token.json';
// create token file if it does not exist
if (!file_exists($localTokenFile))
    file_put_contents($localTokenFile, '');

$config                 = json_decode(file_get_contents('config.authcode.json'), true);
$localToken             = json_decode(file_get_contents($localTokenFile), true);
$GLOBALS['config']      = $config;

// Build the baseURL, accounting for protocol, port, name and endpoint. Used for redirection
$protocol = isset($_SERVER['HTTPS']) ? 'https://' : 'http://';
$port     = ($protocol == 'https://' && $_SERVER['SERVER_PORT'] == 443)
    || ($protocol == 'http://' && $_SERVER['SERVER_PORT'] == 80) ? '' : ':' . $_SERVER['SERVER_PORT'];

$baseURL            = $protocol . $_SERVER['SERVER_NAME'] . $port . $_SERVER['PHP_SELF'];
$errorLog           = 'error_log.log';
$responseLog        = 'response_log.log';
$GLOBALS['baseURL'] = $baseURL;

//Session cookies are used to store information necessary for the authorization code flow
session_start();

/**
 * This function is used to make api calls to the Authorization Server and the Resource Server
 *
 * @param       $url
 * @param       $post
 * @param array $headers
 *
 * @return mixed
 */
function apiRequest($url, $post = null, $token = false, $auth = true, $refresh = false, $headers = array())
{
    $ch = curl_init($url);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    curl_setopt($ch, CURLOPT_HEADER, true);

    $method = 'get';

    if ($post) {
        $method   = 'post';
        $postData = http_build_query($post);
        curl_setopt($ch, CURLOPT_POST, true);
        curl_setopt($ch, CURLOPT_POSTFIELDS, $postData);

        $headersToSign['Content-Type'] = 'application/x-www-form-urlencoded';
        $headersToSign['digest']       = 'SHA-256=' . base64_encode(hash('sha256', $postData, true));
    }

    $urlComponents = parse_url($url);

    $headersToSign['(request-target)'] = $method . ' ' . $urlComponents['path'];
    $headersToSign['Host']             = $urlComponents['host'];
    $headersToSign['Date']             = gmdate('D, j M Y H:i:s T');

    // generating the signature header
    $keyId                = openssl_x509_fingerprint(file_get_contents($GLOBALS['config']['sign_cert']));
    $privateKey           = file_get_contents($GLOBALS['config']['sign_key']);
    $headers['Signature'] = generateSignatureHeader($headersToSign, $keyId, $privateKey);

    $headers['Accept']     = 'application/json';
    $headers['User-Agent'] = $GLOBALS['baseURL'];
    unset($headersToSign['(request-target)']);
    $headers               += $headersToSign;

    if ($token) {
        $headers['Authorization'] = $token['token_type'] . ' ' . $token['access_token'];
    }

    // formatting the headers
    $httpFormattedHeaders = [];
    foreach ($headers as $key => $value) {
        $httpFormattedHeaders[] = trim($key) . ': ' . trim($value);
    }

    curl_setopt($ch, CURLOPT_HTTPHEADER, $httpFormattedHeaders);
    curl_setopt($ch, CURLINFO_HEADER_OUT, true);

    // for development environment only
    //curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
    //curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
    //curl_setopt($ch, CURLOPT_VERBOSE, true);

    // defining TLS client certificates for Mutual TLS
    curl_setopt($ch, CURLOPT_SSLVERSION, CURL_SSLVERSION_TLSv1_2);
    curl_setopt($ch, CURLOPT_CAINFO, $GLOBALS['config']['auth_cacert']);
    curl_setopt($ch, CURLOPT_SSLCERT, $GLOBALS['config']['auth_cert']);
    curl_setopt($ch, CURLOPT_SSLKEY, $GLOBALS['config']['auth_key']);

    $response = curl_exec($ch);

    // logging errors and responses
    errorLog(true, $response, $ch);

    curl_close($ch);

    // in case an authentication request is executed
    if ($auth) {
        // required when a refresh token request is issued
        // because there is only one header in the response
        // while there are two for regular auth requests
        if (!$refresh) {
            list($firstResponseHeaders, $secondResponseHeaders, $responseBody) = explode("\r\n\r\n", $response, 3);
            if (!$responseBody) {
                list($secondResponseHeaders, $responseBody) = explode("\r\n\r\n", $response, 2);
            }
        } else {
            list($secondResponseHeaders, $responseBody) = explode("\r\n\r\n", $response, 2);
        }

        $responseHeadersDigest = '';
        $responseHeadersSignature = '';
        // extracting digest and signature from headers
        $responseHeadersArray = explode("\r\n", $secondResponseHeaders);
        $responseProtocol = array_shift($responseHeadersArray);

        foreach ($responseHeadersArray as $value) {
            list($responseHeadersKeys, $responseHeadersValue) = explode(": ", $value, 2);
            $responseHeaders[strtolower($responseHeadersKeys)] = $responseHeadersValue;
        }

        $responseDigestAlgo = '';
        $responseDigestKey = '';
        foreach ($responseHeaders as $key => $value) {
            if ($key === "digest") {
                $responseHeadersDigest = $value;
                // stripping SHA algorithm for later comparison
                list($responseDigestAlgo, $responseDigestKey) = explode("=", $responseHeadersDigest, 2);
            } else if ($key === "signature")
                $responseHeadersSignature = $value;
        }

        // calculating response digest
        $responseDigest = base64_encode(hash(strtolower(str_replace("-", "", $responseDigestAlgo)), $responseBody, true));

        // checking digest validity
        if ($responseDigest !== $responseDigestKey) {
            errorLog(false, false, "Digests are not the same");
            die();
        }

        // extracting variables from signature header
        $signatureArray = explode(",", $responseHeadersSignature);
        foreach ($signatureArray as $value) {
            list($signatureKey, $signatureValue) = explode("=", $value, 2);
            $signature[$signatureKey] = trim($signatureValue, '"');
        }

        // generating siging string
        $signingStringArray = explode(" ", $signature['headers']);
        $signingString = '';
        foreach ($signingStringArray as $value) {
            $signingString = $signingString . trim($value) . ": " . trim($responseHeaders[$value]) . "\n";
        }

        // trimming last '\n' character
        $signingString = trim($signingString);

        // decoding signature
        $decodedSignature = base64_decode($signature['signature']);

        // verifying signature
        $signatureVerify = openssl_verify($signingString, $decodedSignature, openssl_get_publickey(file_get_contents($GLOBALS['config']['sign_cert_server'])), 'RSA-SHA256');

        if (!$signatureVerify) {
            errorLog(false, false, "Signature is not correct");
            while (($err = openssl_error_string()))
                errorLog(false, false, $err);
            die();
        }

        return json_decode($responseBody, true);
    }

    return $response;
}

/**
 * This function logs curl responses and errors in text files
 *
 * @param mixed $response the response
 * @param mixed $request  the request handle, used to get errors
 */
function errorLog($curl, $response, $request = false)
{
    $timestamp = '[' . date('Y-m-d H:i:s') . ']:';
    global $errorLog;
    global $responseLog;

    if ($response === false) {
        if ($curl)
            file_put_contents($errorLog, $timestamp . curl_error($request) . "\n", FILE_APPEND);
        else
            file_put_contents($errorLog, $timestamp . $request . "\n", FILE_APPEND);
    } else {
        file_put_contents($responseLog, $timestamp . "\n" . $response . "\n", FILE_APPEND);
    }
}

/**
 * This function generates the full signature header line from a set of headers, a certificate and the linked private key
 *
 * @param array  $headersToSign the headers to sign, in the $key => $value format
 * @param string $certificate   the certificate linked to the used private key
 * @param string $privateKey    the private key used to sign the headers
 *
 * @return string the full signature header line
 */
function generateSignatureHeader(array $headersToSign, string $certificate, string $privateKey): string
{
    // generating the signing string and header list
    $headers         = '';
    $signatureString = '';
    foreach ($headersToSign as $key => $value) {
        $normalizedHeaderKey = trim(strtolower($key));
        $headers             .= $normalizedHeaderKey . ' ';
        $signatureString     .= $normalizedHeaderKey . ': ' . trim($value) . "\n";
    }
    $headers         = trim($headers);
    $signatureString = trim($signatureString);

    // signing the signing string
    $signature = '';
    openssl_sign($signatureString, $signature, openssl_get_privatekey($privateKey), 'RSA-SHA256');
    $signature = base64_encode($signature);

    // compiling the header line
    return "keyId=\"$certificate\",algorithm=\"rsa-sha256\",headers=\"$headers\",signature=\"$signature\"";
}

/**
 * This function takes a code_verifier and outputs the corresponding code_challenge
 *
 * @param string $codeVerifier the generated code_verifier
 *
 * @return string the computed code_challenge
 */
function computeCodeChallenge(string $codeVerifier): string
{
    return strtr(rtrim(base64_encode(hash('sha256', $codeVerifier, true)), '='), '+/', '-_');
}

/**
 * This function takes an optional string length and outputs a random code_verifier string
 *
 * @param int $length the length of the output code_verifier. Default = 128
 *
 * @return string the code_verifier
 * @throws Exception
 */
function generateCodeVerifier($length = 128): string
{
    $characters = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-._~';
    $outputCode = '';

    for ($i = 0; $i < $length; $i++) {
        $index      = random_int(0, strlen($characters) - 1);
        $outputCode .= $characters[$index];
    }

    return $outputCode;
}

/**
 * This function calculates the entropy of a given string
 *
 * @param string $string  the string for which to calculate the entropy
 * @param string $charset a string with all the usable characters. Default = 0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-._~
 *
 * @return float|int
 */
function computeEntropy(string $string, string $charset = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-._~')
{
    $chars = str_split($charset);
    $probs = [];

    foreach ($chars as $char) {
        $probs[$char] = floatval(substr_count($string, $char)) / strlen($string);
    }

    $sum = 0.0;
    foreach ($probs as $prob) {
        $sum += $prob != 0 ? $prob * log($prob, 2) : 0.0;
    }

    return -$sum;
}

/**
 * This function calculates the ideal (maximum) entropy for a string of a given length
 *
 * @param int $length length of the string. Default = 128
 *
 * @return float|int
 */
function idealEntropy(int $length = 128)
{
    $prob = 1.0 / $length;

    return -1.0 * $length * $prob * log($prob, 2);
}

/**
 * This function is used to initiate the authentication code flow.
 *
 * @param string $clientID     the client's ID
 * @param string $redirectURL  the URL where to redirect after auth
 * @param string $authorizeURL the target URL to request authorization
 *
 * @throws Exception
 */
function login(string $clientID, string $redirectURL, string $authorizeURL): void
{
    global $localTokenFile;
    file_put_contents($localTokenFile, '');

    // This unguessable string is used to prevent csrf attacks
    $_SESSION['state'] = bin2hex(random_bytes(16));

    // Generate code_verifier and code_challenge for PKCE    
    $_SESSION['code_verifier']  = generateCodeVerifier();
    $_SESSION['code_challenge'] = computeCodeChallenge($_SESSION['code_verifier']);

    // required parameters for the redirection, redirect_uri is where the browser should be redirected
    // when the user grants (or denies) access, scope are the authorizations (rights) requested
    $params = [
        'response_type'         => 'code',
        'client_id'             => $clientID,
        'redirect_uri'          => $redirectURL,
        'scope'                 => 'default.login',
        'state'                 => $_SESSION['state'],
        'code_challenge'        => $_SESSION['code_challenge'],
        'code_challenge_method' => 'S256'
    ];

    // redirecting the browser to the AS authorization endpoint to obtain the authorization code
    header('Location: ' . $authorizeURL . '?' . http_build_query($params));
}

/**
 * This function deletes the access token from the session
 *
 * @param string $baseURL
 */
function logout(string $baseURL): void
{
    global $localTokenFile;
    file_put_contents($localTokenFile, '');

    header('Location: ' . $baseURL);
}

function displayMenuLoggedIn(): void
{
    echo '<h2><a href="/">Home</a></h2>';
    echo '<h3>Logged In</h3>';

    echo '<form id="view_repos" method="post">';
    echo '<input type="hidden" id="action" name="action" value="view">';
    echo '<p><a href="javascript:{}" onclick="document.getElementById(\'view_repos\').submit(); return false;">View Repos</a></p>';
    echo '</form>';

    echo '<form id="logout" method="post">';
    echo '<input type="hidden" id="action" name="action" value="logout">';
    echo '<p><a href="javascript:{}" onclick="document.getElementById(\'logout\').submit(); return false;">Log Out</a></p>';
    echo '</form>';
}

function displayMenuLoggedOut(): void
{
    echo '<h2><a href="/">Home</a></h2>';
    echo '<h3>Not logged in</h3>';
    echo '<form id="login" method="post">';
    echo '<input type="hidden" id="action" name="action" value="login">';
    echo '<p><a href="javascript:{}" onclick="document.getElementById(\'login\').submit(); return false;">Log In</a></p>';
    echo '</form>';
}

// display client "home" page
if (!isset($_POST['action'])) {
    if (!empty($localToken['access_token'])) {
        displayMenuLoggedIn();
    } else {
        displayMenuLoggedOut();
    }
}

if (isset($_POST['action'])) {
    // Building query array for resource dumping
    $repoQueryParameters = [
        'resource_type' => 'user_information',
        'client_id' => $config['client_id']
    ];
    switch ($_POST['action']) {
        case 'login':
            login($config['client_id'], $baseURL, $config['authorize_uri']);
            break;
        case 'logout':
            logout($baseURL);
            break;
        case 'view':
            // call to the resource server to retreive user information
            $resources = apiRequest(
                $config['resource_uri'],
                $repoQueryParameters,
                $localToken,
                false
            );

            // Separating headers from body
            list($resourceHeader, $resourceBody) = explode("\r\n\r\n", $resources, 2);

            $resourceHeaders = explode("\r\n", $resourceHeader);
            $firstHeaderLine = array_shift($resourceHeaders);

            // Displaying user information
            echo '<pre>';
            echo $resourceBody;
            echo '</pre>';

            // Automatic refreshing token once access token is expired
            if (strpos($firstHeaderLine, "401")) {
                $errorBody = json_decode($resourceBody, true);
                if ($errorBody['error'] == 'access_denied' && isset($errorBody['hint'])) {
                    if ($errorBody['hint'] == 'Access token could not be verified') {
                        $token = apiRequest(
                            $config['token_uri'],
                            [
                                'grant_type'    => 'refresh_token',
                                'refresh_token' => $localToken['refresh_token'],
                                'client_id'     => $config['client_id'],
                                'client_secret' => empty($config['client_secret']) ? null : $config['client_secret']
                            ],
                            false,
                            true,
                            true
                        );

                        // We store the access token in the session so the user is "connected"
                        // Only if the request has been successfully executed
                        if (isset($token['access_token'])) {
                            file_put_contents($localTokenFile, json_encode($token, true));

                            // Redirecting the user's browser to the "home" page
                            header('Location: ' . $baseURL);
                        }
                    }
                }
            }
            break;
    }
}

// Once the browser has been redirected to the AS to ask for the user's authorization, assuming it has been granted,
// the browser will get back here with a "code" parameter in the query string
if (isset($_GET['code'])) {
    // The AS MUST redirect the browser here with the exact same state parameter we sent it before, so we check if it is indeed the same
    // to detect if the oauth flow has been tampered with
    if (!isset($_GET['state']) || $_SESSION['state'] != $_GET['state']) {
        header('Location: ' . $baseURL . '?error=invalid_state');
        die();
    }

    // We communicate directly with the AS to exchange the code received against an access token.
    // The id/secret pair is send to authenticate the client, the redirect_uri is sent to verify the code's validity
    $token = apiRequest(
        $config['token_uri'],
        [
            'grant_type'    => 'authorization_code',
            'client_id'     => $config['client_id'],
            'client_secret' => empty($config['client_secret']) ? null : $config['client_secret'],
            'redirect_uri'  => $baseURL,
            'code'          => $_GET['code'],
            'code_verifier' => $_SESSION['code_verifier']
        ]
    );

    // We store the access token in the session so the user is "connected"
    // Only if the request has been successfully executed
    if (isset($token['access_token'])) {
        file_put_contents($localTokenFile, json_encode($token, true));

        // Redirecting the user's browser to the "home" page
        header('Location: ' . $baseURL);
    } else {
        echo $token;
    }
    die();
}
Ce script a été conçu pour montrer le fonctionnement du mécanisme afin de tester l'implémentation d'un serveur et n'a pas été testé extensivement. Il est conseillé d'utiliser une solution adaptée à un environnement de production.

Client Credentials

Ce mécanisme d'autorisation est adapté pour l'automatisation. Il fonctionne de la manière suivante :

  • Le client effectue la demande de jeton d'accès au serveur d'autorisation en fournissant ses identifiants.
  • Le serveur authentifie le client avec les identifiants fournis et renvoie un jeton d'accès.
  • Le client utilise ce jeton d'accès pour accéder à des données via l'API.


   +------+                       +-------------+                  +-----------+
   |Client|                       |   Serveur   |                  |  Serveur  |
   +--+---+                       |Autorisation |                  | Ressources|
      |                           +------+------+                  +-----+-----+
      |        Authentification          |                               |
      |         + Demande token          |                               |
      +--------------------------------->|                               |
      |                                  |                               |
      |          Access token            |                               |
      |<---------------------------------+                               |
      |                                  |                               |
      |                      Requête vers|API                            |
      +----------------------------------+------------------------------>|
      |                                  |                               |
      |                                  |                               |
      |                                  |<------------------------------+
      |                                  |  Vérification d'autorisation  |
      |                                  +------------------------------>|
      |                                  |                               |
      |                         Données /|Réponse                        |
      |<---------------------------------+-------------------------------+
      |                                  |                               |


Demande de jeton d'accès

Pour obtenir un jeton d'accès, il faut effectuer la requête suivante : POST https://openflyers.com/nom-de-plateforme/oauth/access_token.php. Remplacer nom-de-plateforme par le nom de la plateforme utilisée.

Les paramètres suivants sont nécessaires :

Nom Type Description
client_id string L'identifiant unique reçu pendant l'enregistrement du client.
client_secret string La passphrase reçue pendant l'enregistrement du client.
grant_type string Le mécanisme d'autorisation utilisé. Ici, la valeur doit être client_credentials.
scope string Liste des droits demandés par le client, séparé par des espaces.
Ce mécanisme a la particularité de ne pas nécessiter d'URI de redirection, il n'est donc pas utile d'en renseigner un lors de l'enregistrement d'un client.

Si la requête est correcte, un jeton d'accès (Access Token) est fourni en réponse dans un objet au format JSON.

{
  "token_type": "Bearer",
  "expires_in": 3600,
  "access_token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJSU-G7fOBOYzOgioSGNIQKI2A2OxjsNBbOOoaHsqNpsQxWZse3rofExAaGKh2tXbeuz1YAVhdLUGYgq-oKRK4ONFhw2NvcRf3QPxQXZImLWw"
}

Script client : Client Credentials

Voici un exemple simple d'un client OAuth2 pour le mécanisme d'authentification par les identifiants client (Client Credentials).

Fichier de configuration config.clientcred.json :

{
  "client_id": "",
  "client_secret": "",
  "token_uri": "https://openflyers.com/nom-de-plateforme/oauth/access_token.php",
  "resource_uri": "https://openflyers.com/nom-de-plateforme/oauth/resources.php",
  "revoke_uri": "https://openflyers.com/nom-de-plateforme/oauth/revoke.php",
  "auth_cert": "/path/to/client/auth_cert.crt",
  "auth_key": "/path/to/client/auth.key",
  "sign_cert": "/path/to/client/sign_cert.crt",
  "sign_key": "/path/to/client/sign.key",
  "auth_cacert": "/path/to/ca.crt",
  "sign_cert_server": "/path/to/server/sign_cert_server.crt"
}

/path/to/client/ est le chemin d'accès vers le dossier qui contient le code source du client de démonstration.

  • Exemple sur windows: C:/wamp64/www/4.0/oauth-demo/ssl/ClientCredDemo/.
  • Exemple sur un serveur Linux debian: ./ssl/ClientCredDemo/.

Ce fichier de configuration doit se situer au même niveau que le script PHP dans le système de fichiers.

Script php :

<?php
$localTokenFile    = 'token.json';
// create token file if it does not exist
if (!file_exists($localTokenFile))
    file_put_contents($localTokenFile, '');

$config                 = json_decode(file_get_contents('config.clientcred.json'), true);
$localToken             = json_decode(file_get_contents($localTokenFile), true);
$GLOBALS['config']      = $config;

// Build the baseURL, accounting for protocol, port, name and endpoint. Used for redirection
$protocol = isset($_SERVER['HTTPS']) ? 'https://' : 'http://';
$port     = ($protocol == 'https://' && $_SERVER['SERVER_PORT'] == 443)
    || ($protocol == 'http://' && $_SERVER['SERVER_PORT'] == 80) ? '' : ':' . $_SERVER['SERVER_PORT'];

$baseURL            = $protocol . $_SERVER['SERVER_NAME'] . $port . $_SERVER['PHP_SELF'];
$errorLog           = 'error_log.log';
$responseLog        = 'response_log.log';
$GLOBALS['baseURL'] = $baseURL;

$replacementListItems = [
    1 => "year",
    2 => "validityTypeId",
    3 => "icao",
    4 => "profileId",
    7 => "accountingId",
    8 => "paymentType",
    9 => "startDate",
    10 => "endDate",
    11 => "occupiedSeat",
    12 => "date",
    13 => "activityTypeId",
    14 => "age",
    15 => "resourceId",
    16 => "personId",
    17 => "accountId",
    20 => "rightPlacePersonId",
    21 => "month",
    22 => "numberMonth",
    23 => "oneValidityTypeId",
];

//Session cookies are used to store information necessary for the authorization code flow
session_start();

/**
 * This function is used to make api calls to the RS
 *
 * @param       $url
 * @param       $post
 * @param array $headers
 *
 * @return mixed
 */
function apiRequest($url, $post = null, $token = false, $auth = true, $headers = array())
{
    $ch = curl_init($url);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    curl_setopt($ch, CURLOPT_HEADER, true);

    $method = 'get';

    if ($post) {
        $method   = 'post';
        $postData = http_build_query($post);
        curl_setopt($ch, CURLOPT_POST, true);
        curl_setopt($ch, CURLOPT_POSTFIELDS, $postData);

        $headersToSign['Content-Type'] = 'application/x-www-form-urlencoded';
        $headersToSign['digest']       = 'SHA-256=' . base64_encode(hash('sha256', $postData, true));
    }

    $urlComponents = parse_url($url);

    $headersToSign['(request-target)'] = $method . ' ' . $urlComponents['path'];
    $headersToSign['Host']             = $urlComponents['host'];
    $headersToSign['Date']             = gmdate('D, j M Y H:i:s T');

    // generating the signature header
    $keyId                = openssl_x509_fingerprint(file_get_contents($GLOBALS['config']['sign_cert']));
    $privateKey           = file_get_contents($GLOBALS['config']['sign_key']);
    $headers['Signature'] = generateSignatureHeader($headersToSign, $keyId, $privateKey);

    $headers['Accept']     = 'application/json';
    $headers['User-Agent'] = $GLOBALS['baseURL'];
    unset($headersToSign['(request-target)']);
    $headers               += $headersToSign;

    if ($token) {
        $headers['Authorization'] = $token['token_type'] . ' ' . $token['access_token'];
    }

    // formatting the headers
    $httpFormattedHeaders = [];
    foreach ($headers as $key => $value) {
        $httpFormattedHeaders[] = trim($key) . ': ' . trim($value);
    }

    curl_setopt($ch, CURLOPT_HTTPHEADER, $httpFormattedHeaders);
    curl_setopt($ch, CURLINFO_HEADER_OUT, true);

    // for development environment only
    //curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
    //curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
    //curl_setopt($ch, CURLOPT_VERBOSE, true);

    // defining TLS client certificates for Mutual TLS
    curl_setopt($ch, CURLOPT_SSLVERSION, CURL_SSLVERSION_TLSv1_2);
    curl_setopt($ch, CURLOPT_CAINFO, $GLOBALS['config']['auth_cacert']);
    curl_setopt($ch, CURLOPT_SSLCERT, $GLOBALS['config']['auth_cert']);
    curl_setopt($ch, CURLOPT_SSLKEY, $GLOBALS['config']['auth_key']);

    $response = curl_exec($ch);

    // logging errors and responses
    errorLog(true, $response, $ch);

    curl_close($ch);

    // in case an authentication request is executed
    if ($auth) {
        list($responseHeader, $responseBody) = explode("\r\n\r\n", $response, 2);

        $responseHeadersDigest = '';
        $responseHeadersSignature = '';
        // extracting digest and signature from headers
        $responseHeadersArray = explode("\r\n", $responseHeader);
        $responseProtocol = array_shift($responseHeadersArray);

        foreach ($responseHeadersArray as $value) {
            list($responseHeadersKeys, $responseHeadersValue) = explode(": ", $value, 2);
            $responseHeaders[strtolower($responseHeadersKeys)] = $responseHeadersValue;
        }

        $responseDigestAlgo = '';
        $responseDigestKey = '';
        foreach ($responseHeaders as $key => $value) {
            if ($key === "digest") {
                $responseHeadersDigest = $value;
                // stripping SHA algorithm for later comparison
                list($responseDigestAlgo, $responseDigestKey) = explode("=", $responseHeadersDigest, 2);
            } else if ($key === "signature")
                $responseHeadersSignature = $value;
        }

        // calculating response digest
        $responseDigest = base64_encode(hash(strtolower(str_replace("-", "", $responseDigestAlgo)), $responseBody, true));

        // checking digest validity
        if ($responseDigest !== $responseDigestKey) {
            errorLog(false, false, "Digests are not the same");
            die();
        }

        // extracting variables from signature header
        $signatureArray = explode(",", $responseHeadersSignature);
        foreach ($signatureArray as $value) {
            list($signatureKey, $signatureValue) = explode("=", $value, 2);
            $signature[$signatureKey] = trim($signatureValue, '"');
        }

        // generating siging string
        $signingStringArray = explode(" ", $signature['headers']);
        $signingString = '';
        foreach ($signingStringArray as $value) {
            $signingString = $signingString . trim($value) . ": " . trim($responseHeaders[$value]) . "\n";
        }

        // trimming last '\n' character
        $signingString = trim($signingString);

        // decoding signature
        $decodedSignature = base64_decode($signature['signature']);

        // verifying signature
        $signatureVerify = openssl_verify($signingString, $decodedSignature, openssl_get_publickey(file_get_contents($GLOBALS['config']['sign_cert_server'])), 'RSA-SHA256');

        if (!$signatureVerify) {
            errorLog(false, false, "Signature is not correct");
            while (($err = openssl_error_string()))
                errorLog(false, false, $err);
            die();
        }

        return json_decode($responseBody, true);
    }

    return $response;
}

/**
 * This function logs curl responses and errors in text files
 *
 * @param mixed $response the response
 * @param mixed $request  the request handle, used to get errors
 */
function errorLog($curl, $response, $request = false)
{
    $timestamp = '[' . date('Y-m-d H:i:s') . ']:';
    global $errorLog;
    global $responseLog;

    if ($response === false) {
        if ($curl)
            file_put_contents($errorLog, $timestamp . curl_error($request) . "\n", FILE_APPEND);
        else
            file_put_contents($errorLog, $timestamp . $request . "\n", FILE_APPEND);
    } else {
        file_put_contents($responseLog, $timestamp . "\n" . $response . "\n", FILE_APPEND);
    }
}

/**
 * This function generates the full signature header line from a set of headers, a certificate and the linked private key
 *
 * @param array  $headersToSign the headers to sign, in the $key => $value format
 * @param string $certificate   the certificate linked to the used private key
 * @param string $privateKey    the private key used to sign the headers
 *
 * @return string the full signature header line
 */
function generateSignatureHeader(array $headersToSign, string $certificate, string $privateKey): string
{
    // generating the signing string and header list
    $headers         = '';
    $signatureString = '';
    foreach ($headersToSign as $key => $value) {
        $normalizedHeaderKey = trim(strtolower($key));
        $headers             .= $normalizedHeaderKey . ' ';
        $signatureString     .= $normalizedHeaderKey . ': ' . trim($value) . "\n";
    }
    $headers         = trim($headers);
    $signatureString = trim($signatureString);

    // signing the signing string
    $signature = '';
    openssl_sign($signatureString, $signature, openssl_get_privatekey($privateKey), 'RSA-SHA256');
    $signature = base64_encode($signature);

    // compiling the header line
    return "keyId=\"$certificate\",algorithm=\"rsa-sha256\",headers=\"$headers\",signature=\"$signature\"";
}

/**
 * This function deletes the access token from the session
 *
 * @param string $baseURL
 */
function logout(string $baseURL): void
{
    global $localTokenFile;
    file_put_contents($localTokenFile, '');

    header('Location: ' . $baseURL);
}

function displayMenuLoggedIn(): void
{
    global $replacementListItems;
    echo '<h2><a href="/">Home</a></h2>';
    echo '<h3>Logged In</h3>';

    echo '<form id="view_repos" method="post">';
    echo '<label for="report_id">Report ID: </label>';
    echo '<input type="number" id="report_id" name="report_id"><br><br>';
    foreach ($replacementListItems as $value) {
        echo '<label for="' . $value . '">' . $value . ': </label>';
        echo '<input type="text" id="' . $value . '" name="' . $value . '"><br><br>';
    }
    echo '<input type="hidden" id="action" name="action" value="view">';
    echo '<p><a href="javascript:{}" onclick="document.getElementById(\'view_repos\').submit(); return false;">View Repos</a></p>';
    echo '</form>';

    echo '<form id="logout" method="post">';
    echo '<input type="hidden" id="action" name="action" value="logout">';
    echo '<p><a href="javascript:{}" onclick="document.getElementById(\'logout\').submit(); return false;">Log Out</a></p>';
    echo '</form>';
}

function displayMenuLoggedOut(): void
{
    echo '<h2><a href="/">Home</a></h2>';
    echo '<h3>Not logged in</h3>';
    echo '<form id="login" method="post">';
    echo '<input type="hidden" id="action" name="action" value="login">';
    echo '<p><a href="javascript:{}" onclick="document.getElementById(\'login\').submit(); return false;">Log In</a></p>';
    echo '</form>';
}

// display client "home" page
if (!isset($_POST['action'])) {
    if (!empty($localToken['access_token'])) {
        displayMenuLoggedIn();
    } else {
        displayMenuLoggedOut();
    }
}

if (isset($_POST['action'])) {
    $replacementList = [];
    // Building replacement list
    foreach ($replacementListItems as $key => $value) {
        $replacementList[$value] = (isset($_POST[$value]) && strlen($_POST[$value]) > 0) ? $_POST[$value] : null;
    }
    // Building query array
    $repoQueryParameters = [
        'resource_type' => 'generic_report',
        'client_id' => $config['client_id'],
        'report_id' => (isset($_POST['report_id']) && strlen($_POST['report_id']) > 0) ? $_POST['report_id'] : null,
        'replacementList' => $replacementList
    ];
    switch ($_POST['action']) {
        case 'login':
            $token = apiRequest(
                $config['token_uri'],
                [
                    'grant_type'    => 'client_credentials',
                    'client_id'     => $config['client_id'],
                    'client_secret' => empty($config['client_secret']) ? null : $config['client_secret'],
                    'scope'         => 'genericreports.readonly reports.readonly'
                ]
            );
            // We store the access token in the session so the user is "connected"
            // Only if the request has been successfully executed
            if (isset($token['access_token'])) {
                file_put_contents($localTokenFile, json_encode($token, true));

                // Redirecting the user's browser to the "home" page
                header('Location: ' . $baseURL);
            } else {
                echo $token;
            }
            break;
        case 'logout':
            logout($baseURL);
            break;
        case 'view':
            // call to the resource server
            $resources = apiRequest(
                $config['resource_uri'],
                $repoQueryParameters,
                $localToken,
                false
            );

            // Separating headers from body
            list($resourceHeader, $resourceBody) = explode("\r\n\r\n", $resources, 2);

            $resourceHeaders = explode("\r\n", $resourceHeader);
            $firstHeaderLine = array_shift($resourceHeaders);

            // Building form for download CSV button
            echo '<form method="post">';
            foreach ($_POST as $key => $value) {
                if ($key == "action") {
                    $value = "download";
                }
                echo '<input type="hidden" id="' . $key . '" name="' . $key . '" value="' . $value . '">';
            }
            if (isset($_POST['report_id']) && strlen($_POST['report_id']) > 0 && !isset(json_decode($resourceBody, true)['error'])) {
                echo '<button>Download as CSV</button>';
            }
            echo '</form>';

            // Displaying requested content
            echo '<pre>';
            echo (strpos($firstHeaderLine, "200")) ? json_decode($resourceBody) : $resourceBody;
            echo '</pre>';
            break;
        case 'download':
            // call to the resource server
            $resources = apiRequest(
                $config['resource_uri'],
                $repoQueryParameters,
                $localToken,
                false
            );

            // Separating headers from body
            list($resourceHeader, $resourceBody) = explode("\r\n\r\n", $resources, 2);
            $resourceHeaders = explode("\r\n", $resourceHeader);
            array_shift($resourceHeaders);

            // Dumping headers to insert them in current page
            foreach ($resourceHeaders as $key => $value) {
                header($value);
            }
            echo json_decode($resourceBody);
            break;
    }
}
Ce script a été conçu pour montrer le fonctionnement du mécanisme et tester l'implémentation d'un serveur, il n'a pas été testé extensivement. Il est conseillé d'utiliser une solution adaptée à un environnement de production.

Refresh Token

Refresh Token (ou jeton de rafraîchissement en français) est un mécanisme d'autorisation particulier. Il ne peut fonctionner en tant que tel, il fonctionne de pair avec Authorization Code. Lorsqu'un jeton d'accès arrive est arrivé en fin de vie, si le client y est autorisé, il peut faire une demande de renouvellement de son jeton d'accès auprès du serveur d'autorisation en présentant son jeton de rafraîchissement. Le serveur d'autorisation vérifie la validité et génère un autre jeton d'accès qu'il transmet au client, sans que l'utilisateur final n'aît à se connecter de nouveau.

Le principe de fonctionnement du rafraîchissement d'un jeton est le suivant :

  • Le jeton d'accès du client est arrivé à péremption. Le client effectue une demande de renouvellement en transmettant son Refresh Token au serveur d'autorisation.
  • Le serveur d'autorisation vérifie la validité des informations, "consomme" le jeton de rafraîchissement et génère une nouvelle paire de jetons
  • Le client reçoit sa nouvelle paire et utilise le nouveau jeton d'accès pour accéder aux ressources
   +------+                       +-------------+                  +-----------+
   |Client|                       |   Serveur   |                  |  Serveur  |
   +--+---+                       |Autorisation |                  | Ressources|
      |                           +------+------+                  +-----+-----+
      |        Authentification          |                               |
      |         + Refresh Token          |                               |
      +--------------------------------->|                               |
      |                                  |                               |
      |    Access (+ Refresh) token      |                               |
      |<---------------------------------+                               |
      |                                  |                               |
      |                      Requête vers|API                            |
      +----------------------------------+------------------------------>|
      |                                  |                               |
      |                                  |                               |
      |                                  |<------------------------------+
      |                                  |  Vérification d'autorisation  |
      |                                  +------------------------------>|
      |                                  |                               |
      |                         Données /|Réponse                        |
      |<---------------------------------+-------------------------------+
      |                                  |                               |

Demande de renouvellement d'un jeton

Pour obtenir un renouvellement de jeton d'accès, il faut effectuer la requête suivante : POST https://openflyers.com/nom-de-plateforme/oauth/access_token.php. Remplacer nom-de-plateforme par le nom de la plateforme utilisée.

Les paramètres suivants sont nécessaires :

Nom Type Description
client_id string L'identifiant client_id reçu pendant l'enregistrement du client.
client_secret string La passphrase client_secret reçue pendant l'enregistrement du client.
grant_type string Le mécanisme d'autorisation utilisé. Ici, la valeur doit être "refresh_token" (sans guillements).
scope string (OPTIONNEL) Liste des droits demandés par le client, séparés par des espaces.

Si la requête est correcte, un jeton d'accès access_token est fourni en réponse dans un objet au format JSON.

{
  "token_type": "Bearer",
  "expires_in": 3600,
  "access_token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJSU-G7fOBOYzOgioSGNIQKI2A2OxjsNBbOOoaHsqNpsQxWZse3rofExAaGKh2tXbeuz1YAVhdLUGYgq-oKRK4ONFhw2NvcRf3QPxQXZImLWw",
  "refresh_token": "a59ef39fa9bab9b95cd554f921e7f3080a34c90f23d2b8031d692b5ff2d0993dcc392d9c7f9a43242337ef144c1a5fe1d0174413ade973e1b628ac0bbfc39b23973534"
}

Liste des scopes disponibles

Un scope sur OAuth2 correspond à un droit d'accès sur une ressource particulière. Chaque scope est unique et indique de manière explicite le privilège qu'il donne. Il n'y a aucune restriction d'utilisation des scopes par rapport aux mécanismes. Chaque scope peut être utilisé avec n'importe quel mécanisme. Il n'y a que des recommandations vis à vis de leur utilisation. OpenFlyers définit une liste de scopes dédiée aux ressources accessibles par les clients. Ces scopes sont les suivants :

Nom Description
default.login Comportement par défaut lorsqu'aucun scope n'est précisé ou qu'un scope est invalide. Ce scope est recommandé pour Authorization Code.
genericreports.readonly Accéder aux rapports génériques autorisés pour le client en lecture seule. Ce scope est recommandé pour Client Credentials.
reports.readonly Accéder aux rapports personnalisés autorisés pour le client en lecture seule. Ce scope est recommandé pour Client Credentials.

Prolonger la durée de vie de la session du client de démonstration

La session du client de démonstration est initiée avec la méthode PHP session_start. Cette session peut être interrompue soit en cliquant sur le bouton de déconnexion, soit en fermant le navigateur (car les cookies associés à cette session se détruisent par défaut lorsque le navigateur est fermé).

Pour permettre à la session de rester active même après la fermeture du navigateur, il faut utiliser la méthode PHP session_set_cookie_params. Cette méthode donne la possibilité de définir une durée de vie pour les différents cookies associés à la session.

// Set the parameters for the session cookie in a PHP session in order to configure its lifetime in 30 days
$oauth2DemoSessionLifeTime = time() + (86400 * 30);
session_set_cookie_params($oauth2DemoSessionLifeTime);

NB: Cette approche n'est pas particulièrement sécurisée et peut présenter des risques de sécurité pour OpenFlyers. Par conséquent, elle doit être utilisée avec précaution.

Révocation de token

Pour initier la demande de révocation, rediriger le navigateur de l'utilisateur vers l'URL : POST https://openflyers.com/nom-de-plateforme/oauth/revoke.php. Remplacer nom-de-plateforme par le nom de la plateforme utilisée.

Envoyer également le paramètre suivant:

Nom Type Description
access_token string Le jeton d'accès (Chaîne de caractère unique permettant de prouver qu'un client OAuth a le droit d'accéder à une ressource.)
 apiRequest(
                $config['revoke_uri'],
                ['access_token' => $_SESSION['auth_token']['access_token']],
                null,
                false
            );

La demande de révocation se fait quand l'utilisateur clique sur le bouton Se déconnecter pour l'un des deux Clients Authorization Code et Client Credentials.

Si les jetons sont révoqués, l'utilisateur ne peut pas accéder à la plateforme OpenFlyers, il doit se reconnecter.

Utiliser l'API

Une fois un jeton d'accès obtenu, les données sont accessibles par une requête POST exécutée vers l'URL : https://openflyers.com/nom-de-plateforme/oauth/resources.php. Remplacer nom-de-plateforme par le nom de la plateforme utilisée. La requête POST doit respecter une structure particulière. Cette structure diffère en fonction du type de ressource souhaité et chaque ressource ne peut être accédée qu'avec le scope qui lui est associé. Le jeton d'accès doit être renseigné dans l'en-tête HTTP Authorization en y indiquant la valeur complète du jeton d'accès reçu précédé du type de jeton reçu  : Authorization: <token_type> <access_token>.

Récupérer les informations de l'utilisateur connecté

Ce type de ressource est nommé user_information. Cette ressource n'est accessible qu'avec le scope default.login. Cette ressource permet la récupération des informations de l'utilisateur connecté, en d'autre termes, l'utilisateur connecté lors de l'étape d'autorisation et correspondant à celui ayant émis la demande de jeton d'accès. À l'heure actuelle, seul l'identifiant de l'utilisateur connecté est retourné. La requête POST est construite de la manière suivante :

Nom Type Description
resource_type string Le type de ressource demandé, ici, ce champ correspond à user_information.
client_id string L'identifiant unique reçu pendant l'enregistrement du client.

Un exemple de requête complète au format JSON :

POST /nom-de-plateforme/oauth/resources.php HTTP/1.1
Content-Type: application/x-www-form-urlencoded
Host: openflyers.com
Date: Wed, 04 Aug 2021 13:57:51 GMT
Accept: application/json
User-Agent: curl/7.64.1
Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJSU
Signature: keyId="58c450d937953829c8cca3613001f865a918da07",
           algorithm="rsa-sha256",
           headers="host content-type digest",
           signature="UsTjCPLsXzmo1b8FrA18SdLgaPamCpqR7tDHBhaiBnro6RbOkoD7="
Digest: SHA-256=Bi6/9qfJXEWme2/9o7VQMyvf+MED523bWdtdi91opwk=

{
    "resource_type":"user_information",
    "client_id":"d2615fe2020ec476"
}

La réponse est retournée dans un conteneur JSON.

Récupérer les rapports génériques

Ce type de ressource est nommé generic_report. Cette ressource n'est accessible qu'avec le scope genericreports.readonly. Cette ressource permet la récupération des rapports génériques au format CSV autorisés pour le profil de l'utilisateur associé au client OAuth2 enregistré. La requête POST est construite de la manière suivante :

Nom Type Description
resource_type string Le type de ressource demandé, ici, ce champ correspond à generic_report.
client_id string L'identifiant unique reçu pendant l'enregistrement du client.
report_id int Identifiant du rapport souhaité.
replacementList array Tableau correspondant aux différents paramètres modifiables pour générer le rapport souhaité.

Un exemple de requête complète au format JSON :

POST /nom-de-plateforme/oauth/resources.php HTTP/1.1
Content-Type: application/x-www-form-urlencoded
Host: openflyers.com
Date: Wed, 04 Aug 2021 13:57:51 GMT
Accept: application/json
User-Agent: curl/7.64.1
Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJSU
Signature: keyId="58c450d937953829c8cca3613001f865a918da07",
           algorithm="rsa-sha256",
           headers="host content-type digest",
           signature="UsTjCPLsXzmo1b8FrA18SdLgaPamCpqR7tDHBhaiBnro6RbOkoD7="
Digest: SHA-256=Bi6/9qfJXEWme2/9o7VQMyvf+MED523bWdtdi91opwk=

{
    "resource_type":"generic_report",
    "client_id":"d2615fe2020ec476",
    "report_id":135,
    "replacementList":{
        "year":2018
    }
}

La réponse est un fichier CSV retourné dans un conteneur JSON.

Le report_id se trouve dans la bibliothèque des rapports dans la 1ère colonne de la ligne du rapport concerné.

Pour identifier les paramètres à transmettre dans "replacementList", il faut retrouver le rapport dans la version anglaise de la documentation.

Exemple avec le rapport "Balances of resource accounts" : le chapitre indique que les paramètres/variables nécessaires sont accountingId et endDate.

Récupérer les rapports personnalisés

Ce type de ressource est nommé report. Cette ressource n'est accessible qu'avec le scope reports.readonly. Cette ressource permet la récupération des rapports personnalisés au format CSV autorisés pour le profil de l'utilisateur associé au client OAuth2 enregistré. La requête POST est construite de la manière suivante :

Nom Type Description
resource_type string Le type de ressource demandé, ici, ce champ correspond à report.
client_id string L'identifiant unique reçu pendant l'enregistrement du client.
report_id int Identifiant du rapport souhaité.
replacementList array Tableau correspondant aux différents paramètres modifiables pour générer le rapport souhaité.

Un exemple de requête complète au format JSON :

POST /nom-de-plateforme/oauth/resources.php HTTP/1.1
Content-Type: application/x-www-form-urlencoded
Host: openflyers.com
Date: Wed, 04 Aug 2021 13:57:51 GMT
Accept: application/json
User-Agent: curl/7.64.1
Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJSU
Signature: keyId="58c450d937953829c8cca3613001f865a918da07",
           algorithm="rsa-sha256",
           headers="host content-type digest",
           signature="UsTjCPLsXzmo1b8FrA18SdLgaPamCpqR7tDHBhaiBnro6RbOkoD7="
Digest: SHA-256=Bi6/9qfJXEWme2/9o7VQMyvf+MED523bWdtdi91opwk=

{
    "resource_type":"report",
    "client_id":"d2615fe2020ec476",
    "report_id":1,
    "replacementList":{
        "year":2020
    }
}

La réponse est un fichier CSV retourné dans un conteneur JSON.

Procédures

Créer un client à partir du code source

Prérequis

Un client de démonstration est disponible pour le logiciel OpenFlyers à cette adresse : https://openflyers.com/oauth2-demo/index.php

Télécharger Le code source du client de démonstration

Le code source est mis à disposition par OpenFlyers à cette adresse : https://openflyers.com/oauth2-demo/oauth2-demo-src.zip

Le code source est structuré de la manière suivante :

Oauth2 src demo folder.png

  • Le dossier css contient tout le matériel nécessaire à la stylisation de la page web.
  • Le dossier img contient les images affichées sur la page web.
  • Le dossier ssl est le dossier contenant tous les certificats et les clé privées associées à chaque client. Il doit respecter une structure particulière décrite ci-dessous.
  • Le fichier ClientDemo.php contient la classe ClientDemo. Cette classe contient toutes les méthodes nécessaires au fonctionnement du client de démonstration OAuth2.
  • Le fichier index.php est le fichier à appeler depuis le navigateur. Ce fichier correspond au fichier qui gère les appels à la classe ClientDemo et exécute les méthodes dans l'ordre.

Le dossier ssl doit respecter la structure suivante :

Oauth2 demo tree.png

Générer les certificats

Après la génération des certificats, les clés privées 'auth.key' et 'sign.key' remplacent celles présentes dans les dossiers 'ssl/AuthCodeDemo' et 'ssl/ClientCredDemo'.

Enregistrer les clients
  • Deux clients doivent être créés :
    • Le premier pour le mécanisme d'autorisation Authorization Code,
    • Le second pour le mécanisme d'autorisation Client Credentials.

Oauth2 demo manage.png

  • Télécharger le certificat du CA OpenFlyers en cliquant sur le bouton Télécharger le certificat CA de la page de gestion. Télécharger aussi le certificat de signature du serveur en cliquant sur le bouton Télécharger le certificat de signature du serveur de la page de gestion. Placer les deux certificats téléchargés à la racine du dossier ssl.
  • Télécharger les deux certificats Certificat d'authentification et Certificat de signature du client Authorization Code et les placer dans le répertoire ssl/AuthCodeDemo.
  • Modifier le fichier ssl/AuthCodeDemo/config.authcode.json en le remplissant de la manière suivante :
{
  "client_id": "XXXXXXXXXXXXXXXX",
  "client_secret": "XXXXXXXXXXXXXXXX",
  "authorize_uri": "https://openflyers.com/mastructure/oauth/authorize.php",
  "token_uri": "https://openflyers.com/mastructure/oauth/access_token.php",
  "resource_uri": "https://openflyers.com/mastructure/oauth/resources.php",
  "revoke_uri": "https://openflyers.com/mastructure/oauth/revoke.php",
  "auth_cert": "path_to_client/ssl/AuthCodeDemo/auth_cert.crt",
  "auth_key": "path_to_client/ssl/AuthCodeDemo/auth.key",
  "sign_cert": "path_to_client/ssl/AuthCodeDemo/sign_cert.crt",
  "sign_key": "path_to_client/ssl/AuthCodeDemo/sign.key",
  "auth_cacert": "path_to_client/ssl/ca.crt",
  "sign_cert_server": "path_to_client/ssl/sign_cert_server.crt"
}
  • Remplacer les XXXXXXXXXXXXXXXX des champs client_id et client_secret par les valeurs obtenues lors de l'enregistrement du client.
  • Remplacer mastructure par le nom de la structure sur laquelle la démo est testée.
  • Remplacer path_to_client/ par le chemin d'accès vers le dossier qui contient le code source du client de démonstration.
    • Exemple sur windows: C:/wamp64/www/4.0/oauth-demo/.
    • Exemple sur un serveur Linux debian: ./.


  • Télécharger les deux certificats Certificat d'authentification et Certificat de signature du client Client Credentials et les placer dans le répertoire ssl/ClientCredDemo.
  • Modifier le fichier ssl/ClientCredDemo/config.clientcred.json en le remplissant de la manière suivante :
{
  "client_id": "XXXXXXXXXXXXXXXX",
  "client_secret": "XXXXXXXXXXXXXXXX",
  "token_uri": "https://openflyers.com/mastructure/oauth/access_token.php",
  "resource_uri": "https://openflyers.com/mastructure/oauth/resources.php",
  "revoke_uri": "https://openflyers.com/mastructure/oauth/revoke.php",
  "auth_cert": "path_to_client/ssl/ClientCredDemo/auth_cert.crt",
  "auth_key": "path_to_client/ssl/ClientCredDemo/auth.key",
  "sign_cert": "path_to_client/ssl/ClientCredDemo/sign_cert.crt",
  "sign_key": "path_to_client/ssl/ClientCredDemo/sign.key",
  "auth_cacert": "path_to_client/ssl/ca.crt",
  "sign_cert_server": "path_to_client/ssl/sign_cert_server.crt"
}
  • Remplacer les XXXXXXXXXXXXXXXX des champs client_id et client_secret par les valeurs obtenues lors de l'enregistrement du client.
  • Remplacer mastructure par le nom de la structure sur laquelle la démo est testée.
  • Remplacer path_to_client/ par le chemin d'accès vers le dossier qui contient le code source du client de démonstration.
    • Exemple sur windows: C:/wamp64/www/4.0/oauth-demo/.
    • Exemple sur un serveur Linux debian: ./.



NB: Le certificat de signature du serveur est unique à chaque plateforme et serveur. Ainsi, si le serveur ou la plateforme est modifié, le certificat doit être renouvelé.

Enregistrer un client

Pour utiliser l'API OAuth2, il faut enregistrer un client OAuth2 auprès d'OpenFlyers. Pour ceci, suivre les étapes suivantes :


Pour le mécanisme d'authentification Client Credentials
  • Créer un nouveau profil. Ce profil doit permettre de gérer les droits du client OAuth2. Choisir un nom explicite, par exemple "Client OAuth rapports".
  • Sélectionner les droits à assigner à ce profil. Ces droits limitent les données auxquelles le client OAuth2 a accès.
    • Sélectionner les droits relatifs à l'enregistrement de clients OAuth2 dans l'onglet Admin (colonne Associé aux clients OAuth2).
    • Sélectionner les rapports accessibles par le profil précédemment créé.
  • Créer un nouvel utilisateur à partir du panneau de gestion. Cet utilisateur est virtuel et représente le serveur sur lequel fonctionne le client OAuth2.
    • Des identifiant et nom explicites sont recommandés (exemple : "serv1_oauth_client")
    • Attention : tout utilisateur désactivé et associé à un client OAuth2 rend le client inactif, il faut donc changer l'utilisateur associé au client pour réactiver le client.


Pour le mécanisme d'authentification Authorization Code
  • Aller dans Admin > Utilisateurs > Profils
  • Dans l'onglet Généralités, cocher la case relative à la colonne Connexion depuis l'extérieur (OAuth2) pour le profil souhaité.


Créer un nouveau client OAuth2
  • Aller dans Admin > Transferts > Exports > API OAuth2

Oauth2 manage.png

  • Cliquer sur le bouton Ajouter + ou Ajouter un client

Oauth2 client creation.png

  • Choisir un nom pour le client.
  • Sélectionner le mécanisme d'autorisation utilisé par le client :
    • Authorization Code: permet d'utiliser OAuth2 comme solution SSO ou accéder à des données utilisateurs. Cette méthode peut être couplée avec le mécanisme de mémorisation de connexion (Refresh Token).
    • Client Credentials: permet d'utiliser OAuth2 dans un contexte d'automatisme.
  • Saisir l'URI de redirection vers le client pour le mécanisme Authorization Code.
  • Sélectionner l'utilisateur virtuel créé précédemment pour le mécanisme Client Credentials.
  • Générer deux CSR afin d'obtenir deux certificats signés et les saisir :
    • Certificate Signing Request pour le certificat d'authentification est utilisé pour l'authentification mutuelle avec mTLS (auth_cert.csr.pem).
    • Certificate Signing Request pour le certificat de signature est utilisé pour la signature des en-têtes HTTP (sign_cert.csr.pem).

PublicKeysCopyScreen.png


  • Cliquer sur Enregistrer.

NB: La validité des certificats générés s'étend sur une période de 3 années.


Sauvegarder le couple ID/passphrase

Un couple ID/passphrase (client_id/client_secret) est généré. Ces deux clées ne sont communiquées qu'une seule fois. Elle doivent être stockées en toute sécurité et gardées confidentielles, et Mettre ces identifiants dans le fichier config.clientcred.json. Oauth2 client created.png

ConfigCredJsonScreen.png


Télécharger les certificats crt

Les certificats signés sont téléchargeables depuis l'interface de gestion des clients OAuth2, les certificats sont disponibles dans les onglets Certificat d'authentification et Certificat de signature et les mettre dans le dossier /ssl du client OAuth2. Oauth2 client certificates.png

Téléchargez les certificats du serveur.
  • Les certificats du CA d'OpenFlyers et de signature HTTP du serveur sont nécessaires. Ils sont téléchargeables depuis l'interface de configuration des clients OAuth2.

DownloadServerCertifScreen.png

  • Dans certains cas d'utilisation, il peut être nécessaire d'ajouter le certificat du CA d'OpenFlyers au Trust Store du système. Si cette étape n'est pas réalisée, les certificats peuvent être considérés comme invalides et peuvent ne pas être utilisables.
  • Pour ajouter le certificat CA au Trust Store du système, suivre les étapes suivantes:
    • Sous Linux, copier le certificat CA d'OpenFlyers dans le dossier /usr/local/share/ca-certificates et exécuter la commande sudo update-ca-certificates
    • Sous Windows,
      • Double-cliquer sur le certificat CA d'OpenFlyers téléchargé depuis l'interface d'enregistrement des clients OAuth2
      • Cliquer sur Installer un certificat...
      • Choisir l'emplacement de stockage (utilisateur ou ordinateur) et cliquer sur Suivant puis Suivant et enfin Terminer

Générer des certificats

L'API OAuth2 implémente HTTP Signature et l'authentification TLS mutuelle. Ces mécanismes utilisent chacun une paire certificat/clé privée différente.

Pour obtenir ces certificats, il faut d'abord générer des Certificate Signing Request (CSR).

La procédure est la suivante :

Fichier de configuration OpenSSL - sign_cert.conf
[req]
default_bits       = 4096                     # taille par défaut des nouvelles clés, peut être surchargé dans la commande
encrypt_key        = no                       # chiffrer la clé générée
distinguished_name = req_distinguished_name   # pointe vers la catégorie spécifiée pour le Distinguished Name
x509_extensions    = v3_req                   # pointe vers la catégorie spécifiée pour les extensions x509
prompt             = no

[req_distinguished_name]
C                  =                          # code à deux chiffres du pays (ex: FR)
ST                 =                          # région/état (ex: Gironde)
L                  =                          # ville (ex: Bordeaux)
O                  =                          # organisation (ex: OpenFlyers)
OU                 =                          # unité organisationelle (ex: IT)
CN                 =                          # nom de domaine (ex: openflyers.com)

[v3_req]
keyUsage           = digitalSignature         # pour quelles opérations la clé peut-elle être utilisée
Fichier de configuration OpenSSL - auth_cert.conf
[req]
default_bits       = 4096                     # taille par défaut des nouvelles clés, peut être surchargé dans la commande
encrypt_key        = no                       # chiffrer la clé générée
distinguished_name = req_distinguished_name   # pointe vers la catégorie spécifiée pour le Distinguished Name
x509_extensions    = v3_req                   # pointe vers la catégorie spécifiée pour les extensions x509
prompt             = no

[req_distinguished_name]
C                  =                          # code à deux chiffres du pays (ex: FR)
ST                 =                          # région/état (ex: Gironde)
L                  =                          # ville (ex: Bordeaux)
O                  =                          # organisation (ex: OpenFlyers)
OU                 =                          # unité organisationelle (ex: IT)
CN                 =                          # nom de domaine (ex: openflyers.com)

[v3_req]
extendedKeyUsage   = clientAuth               # pour quelles opérations la clé peut-elle être utilisée


Exécuter les commandes suivantes :

  • openssl req -sha256 -newkey rsa -keyout sign.key -out sign_cert.csr.pem -outform PEM -config sign_cert.conf
    
  • <bash>openssl req -sha256 -newkey rsa -keyout auth.key -out auth_cert.csr.pem -outform PEM -config auth_cert.conf</bash>

Ces commandes prennent chacune en entrée le fichier de configuration et génèrent une clé privée et un Certificate Signing Request.

Une fois ces CSR obtenus :

  • Les renseigner dans les champs prévus à cet effet lors de la création d'un client et télécharger les certificats signés depuis l'interface une fois le client créé.
  • Garder la clé privée confidentielle. Une fuite poserait un risque de sécurité. Elle va de paire avec le certificat distribué par l'autorité de certification OpenFlyers.

Mettre en place une connexion à l'API OpenFlyers sur un serveur mutualisé

Note

La procédure ci-après est destinée à une mise en place lorsqu'il n'y a pas d'accès SSH en ligne de commande mais uniquement un accès FTP. Dans ce cas, la création des clés privées et publics est effectuée "en local". Dans la procédure suivante elle est effectuée depuis un PC sous Windows.

Prérequis
  • Posséder les accès FTP :
    • Hôte : XXXXXXXXXXXXXXXXXX
    • Login : XXXXXXXX
    • Mot de passe : XXXXXXXX
    • Port : XX (par exemple 21)
  • Télécharger Le code source du client de démonstration à disposition par OpenFlyers à cette adresse : https://openflyers.com/oauth2-demo/oauth2-demo-src.zip
Procédure
  • Générer les certificats en local.
  • Remplacer les clés privées 'auth.key' et 'sign.key' présentes dans les dossiers 'ssl/AuthCodeDemo' et 'ssl/ClientCredDemo' par les clés générées.
  • Enregistrer les deux clients :
    • Le premier pour le mécanisme d'autorisation Authorization Code.
    • Le second pour le mécanisme d'autorisation Client Credentials.

Oauth2 demo manage.png

  • Télécharger le certificat du CA OpenFlyers en cliquant sur le bouton Télécharger le certificat CA de la page de gestion.
  • Télécharger aussi le certificat de signature du serveur en cliquant sur le bouton Télécharger le certificat de signature du serveur de la page de gestion.
  • Placer les deux certificats téléchargés à la racine du dossier ssl.
  • Télécharger les deux certificats Certificat d'authentification et Certificat de signature du client Authorization Code et les placer dans le répertoire ssl/AuthCodeDemo.
  • Modifier le fichier ssl/AuthCodeDemo/config.authcode.json en le remplissant de la manière suivante.
{
  "client_id": "XXXXXXXXXXXXXXXX",
  "client_secret": "XXXXXXXXXXXXXXXX",
  "authorize_uri": "https://openflyers.com/mastructure/oauth/authorize.php",
  "token_uri": "https://openflyers.com/mastructure/oauth/access_token.php",
  "resource_uri": "https://openflyers.com/mastructure/oauth/resources.php",
  "revoke_uri": "https://openflyers.com/mastructure/oauth/revoke.php",
  "auth_cert": "./ssl/AuthCodeDemo/auth_cert.crt",
  "auth_key": "./ssl/AuthCodeDemo/auth.key",
  "sign_cert": "./ssl/AuthCodeDemo/sign_cert.crt",
  "sign_key": "./ssl/AuthCodeDemo/sign.key",
  "auth_cacert": "./ssl/ca.crt",
  "sign_cert_server": "./ssl/sign_cert_server.crt"
}


  • Télécharger les deux certificats Certificat d'authentification et Certificat de signature du client Client Credentials et les placer dans le répertoire ssl/ClientCredDemo.
  • Modifier le fichier ssl/ClientCredDemo/config.clientcred.json en le remplissant de la manière suivante.
{
  "client_id": "XXXXXXXXXXXXXXXX",
  "client_secret": "XXXXXXXXXXXXXXXX",
  "token_uri": "https://openflyers.com/mastructure/oauth/access_token.php",
  "resource_uri": "https://openflyers.com/mastructure/oauth/resources.php",
  "revoke_uri": "https://openflyers.com/mastructure/oauth/revoke.php",
  "auth_cert": "./ssl/ClientCredDemo/auth_cert.crt",
  "auth_key": "./ssl/ClientCredDemo/auth.key",
  "sign_cert": "./ssl/ClientCredDemo/sign_cert.crt",
  "sign_key": "./ssl/ClientCredDemo/sign.key",
  "auth_cacert": "./ssl/ca.crt",
  "sign_cert_server": "./ssl/sign_cert_server.crt"
}
  • Remplacer les XXXXXXXXXXXXXXXX des champs client_id et client_secret par les valeurs obtenues lors de l'enregistrement du client.
  • Remplacer mastructure par le nom de la structure sur laquelle la démo est testée.
  • Transférer le fichier "oauth-demo" vers le serveur mutualisé :
    • Télécharger FileZilla.
    • Lancer FileZilla.
    • Entrer l'URl du serveur mutualisé dans le champ Hôte.
    • Entrer le login dans le champ Nom d'utilisateur.
    • Entrer le mot de passe dans le champ Mot de passe.
    • Entrer le port dans le champ Port.
    • Cliquer sur le bouton Connexion.
    • Accéder à l'emplacement du répertoire "oauth-demo" en local à gauche dans l'onglet "Site local".
    • Choisir l'emplacement où placer le répértoire oauth-demo dans l'anglet Site distant à droite.
    • Glisser et déposer le oauth-demo à l'emplacement choisi.

Transfer OauthDemo To Shared Server.png

  • Accéder au client OAuth-demo depuis le serveur mutualisé en utilisant l'URL du domaine du serveur : url_de_domaine_de_serveur/oauth-demo/index.php
  • Modifier la valeur URI de redirection vers le client du client AuthCodeDemo précédemment créé en remplaçant l'ancienne URL par la nouvelle.


NB: Le certificat de signature du serveur est unique à chaque plateforme et serveur. Ainsi, si le serveur ou la plateforme est modifié, le certificat doit être renouvelé.

Récupérer les données d'un utilisateur

  • Cliquer sur le bouton Récupérer les informations utilisateur, l'identifiant de l'utilisateur s'affiche.
  • Utiliser l'identifiant récupérer afin de récupérer toute information associée à cet utilisateur en créant de nouveaux rapports personnalisés

Utiliser le client

Prérequis

Un client de démonstration est disponible pour le logiciel OpenFlyers à cette adresse : https://openflyers.com/oauth2-demo/index.php

Le client de démonstration se présente de la manière suivante.

Oauth2 client demo.png

La démonstration est composée de deux colonnes. La première, nommée Authorization Code correspond au mécanisme d'autorisation du même nom. Elle dispose d'un bouton permettant de se connecter ainsi que d'une section indiquant les informations relatives à l'état de la connexion. La seconde colonne, nommée Client Credentials correspond elle aussi au mécanisme d'autorisation du même nom. Comme pour la première colonne, les éléments qui y sont présentés sont identiques. La différence étant que le bouton de connexion n'a pas le même effet étant donné que ces deux mécanismes sont différents. Chaque mécanisme est indépendant et il est possible de se connecter à un des deux mécanismes sans se connecter à l'autre ou se connecter aux deux en même temps.

Le mécanisme Authorization Code
  • Cliquer sur le bouton Se connecter (ce qui redirige le navigateur vers la page de connexion du logiciel OpenFlyers).
  • Renseigner les identifiants de l'administrateur pour s'y connecter.
  • Nom d'utilisateur : admini.
  • Mot de passe : azerty.

Une fois les informations saisies, la page suivante est affichée.

Oauth authorize demo.png

  • Cliquer sur le bouton Autoriser l'application pour autoriser la connexion (ce qui se redirige le navigateur vers la page du client de démonstration OAuth2).

La première colonne doit afficher l'état de connexion Connecté ainsi qu'un nouveau bouton Récupèrer les informations utilisateurs qui permet de récupérer les informations de l'utilisateur connecté.

Le mécanisme Client Credentials
  • Cliquer sur le bouton de connexion Se connecter: contrairement à celui du mécanisme Authorization Code, ne redirige pas le navigateur vers la page de connexion du logiciel OpenFlyers. Le bouton de connexion utilise les identifiants du client, ici le couple clé privée/clé publique, pour initier la connexion avec le serveur d'autorisation et obtenir un jeton d'accès.

Une fois la connexion établie, la seconde colonne doit afficher l'état de connexion Connecté ainsi qu'un menu déroulant Rapport à récupèrer et un nouveau bouton Récupèrer le rapport qui permet de récupérer les rapports génériques et personnalisés.


Le client, une fois connecté sur les deux mécanismes, se présente de la manière suivante.

Oauth2 connected demo.png

Troubleshooting

500 Internal Server Error en récupérant le rapport

La démo utilise les valeurs par défaut pour extraire les rapports. Une erreur 500 indique une "Erreur de syntaxe ou violation d'accès" lors de l'exécution de la requête du rapport. Cela se produit parce que le rapport n'a pas de valeurs par défaut associées, étant donné qu'il n'a jamais été visualisé dans l'interface web. Pour résoudre ce problème, il vous suffit de visualiser le rapport et de cocher la case "Mémoriser ce choix".

Erreur "File not found."

Cette erreur se produit lorsque l'URI utilisé n'existe pas sur le serveur OpenFlyers. Vérifier les URIs mis en place dans les fichiers de configuration et essayer de nouveau.

int_rsa_verify : longueur de signature incorrecte

Ce problème pourrait survenir si les fichiers ca.cert et sign_cert_server.cert ne proviennent pas du même serveur que celui du client oauth2. La solution est :

Si cela ne fonctionne pas