|
|
Line 1: |
Line 1: |
| | __TOC__ |
| | |
| =Présentation= | | =Présentation= |
| L'objet de cette page est de décrire l'API OpenFlyers. | | L'objet de cette page est de décrire le protocole d'interfaçage avec toute solution d'armoire à clés tiers. Pour les armoires à clés commercialisées par OpenFlyers, il faut se référer à la [[Contrôle des accès|documentation sur le contrôle d'accès]]. |
|
| |
|
| ;Description de l'API
| | Le protocole repose sur le principe que l'interfaçage est réalisé par un logiciel de contrôle de l'armoire à clé sur un PC ou une solution embarquée et qui dispose d'un accès à internet lui permettant de communiquer avec OpenFlyers. La communication est synchrone. |
| 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.
| |
|
| |
|
| 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 :
| | =Interface utilisateur OpenFlyers= |
| *[[#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)]],
| | L'utilisateur ouvre un vol dans OpenFlyers : |
| *[[#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.
| |
|
| |
|
| 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 :
| | [[File:Menu-contextuel-ouverture-de-vol.png]] |
| *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 [[#Utiliser-l'API|types de ressources]] :
| |
| *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.
| |
|
| |
|
| 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.
| | Il se retrouve sur le formulaire de saisie du vol en mode Départ en vol: |
|
| |
|
| Deux protocoles de sécurité sont présents dans l'API OpenFlyers :
| | [[File:Formulaire-ouverture-de-vol.png]] |
| *[[#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é.
| |
|
| |
|
| ;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|Utiliser le client]] de cette page.
| | Lorsqu'il clique sur le bouton de validation, 3 cas peuvent se présenter : |
| | *Une alerte ou plusieurs alertes rouges (bloquantes), s'affichent : l'utilisateur n'a pas la possibilité de surpasser ses alertes, il ne peut alors récupérer la clé : |
|
| |
|
| Le client de démonstration est lui-même accessible à cette adresse : https://openflyers.com/oauth2-demo/index.php
| | [[File:Alertes-bloquantes-en-ouverture-vol.png]] |
|
| |
|
| Le code source du client de démonstration est mis à disposition par OpenFlyers à cette adresse : https://openflyers.com/oauth2-demo/oauth2-demo-src.zip
| | *Une alerte ou plusieurs alertes oranges (non-bloquantes), s'affichent : l'utilisateur a la possibilité de surpasser ses alertes, et ainsi de récupérer la clé : |
|
| |
|
| 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]].
| | [[File:Alertes-non-bloquantes-en-ouverture-vol.png]] |
|
| |
|
| =Définitions=
| | *Aucune alerte ne s'affiche, l'utilisateur se retrouve alors directement sur la page lui indiquant : |
| ==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 [[Wikipedia-en:Mutual_authentication#mTLS|mTLS]]).
| |
|
| |
|
| OpenFlyers associe un certificat pour l'authentification mutuelle unique à chaque client OAuth2.
| | [[File:Message_autorisation_libération_clé.png]] |
|
| |
|
| ;Envoyer un certificat client
| | Une fois qu'il a cliqué sur '''LIBÉRER LA CLÉ''', il dispose de X secondes. Cette durée est paramétrable par OpenFlyers. |
| 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 :
| |
| <php>curl_setopt_array($request, [
| |
| CURLOPT_SSLVERSION => CURL_SSLVERSION_TLSv1_2,
| |
| CURLOPT_CAINFO => $caCertificatePath,
| |
| CURLOPT_SSLCERT => $certificatePath,
| |
| CURLOPT_SSLKEY => $keyPath
| |
| ]);</php>
| |
|
| |
|
| '''À noter''' : le certificat du CA d'OpenFlyers est nécessaire pour assurer la validité des certificats utilisés. | | *Lorsqu'il rentre de vol, l'utilisateur n'a plus qu'a retourner sur le formulaire de saisie de vol et à fermer son vol. La clé doit etre remise en place sur l'armoire pour fermer le vol. |
|
| |
|
| ==HTTP Signature== | | =Interface administrateur OpenFlyers= |
| HTTP Signature utilise le principe de la signature numérique pour garantir l'authenticité et l'intégrité du message HTTP.
| | ==Activation et configuration de la gestion des armoires à clé== |
| | *'''Admin > Structure > Structure > Paramétrage''' |
| | *Dans la section du formulaire '''Contrôle d'accès''', sélectionner '''Activé(e)''' pour les champs '''Contrôle d'accès''' et '''Gestion armoire à clés'''. |
| | [[File:Config_parametres_OF_controle_acces 1.jpg]] |
| | *Cliquer sur le bouton '''Enregistrer''' |
| | *Un nouveau champ s'affich |
| | *Sélectionner le '''Logiciel de contrôle de l'armoire à clés''' selon votre armoire à clés |
| | **Pour armoire FlogBox : [[File:Config_parametres_OF_controle_acces 4.jpg]] |
| | **Pour armoire Deister : [[File:Config_parametres_OF_controle_acces 3.jpg]] |
|
| |
|
| 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.
| | Les Logiciels OpenKey et PyOpenKey sont des protocoles propriétaires d'OF, pour les configurer voir [[Interfaçage-OpenFlyers-et-armoire-à-clés#Interface-administrateur-OpenFlyers | Interfaçage armoire à clés OF]] |
|
| |
|
| HTTP Signature utilise deux en-têtes HTTP :
| | ==Attribution des clés aux ressources/aéronefs== |
| *Signature : contient la signature et ses métadonnées.
| | ''Dans le cas où c'est le logiciel OpenFlyers qui gère les clés.'' |
| *Digest : contient le corps du message haché. | | *'''Admin > Types de ressources > Actives''' |
| | [[File:Liste_des_aéronefs.png]] |
|
| |
|
| ===Digest===
| | Pour chaque aéronef concerné, il suffit d'attribuer un numéro et un nom de clé dans les champs correspondants. La validation de la saisie se fait automatiquement en cliquant hors du champ de saisie. |
| 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>
| | =Protocole de dialogue XML-RPC entre OpenFlyers et le logiciel de gestion de l'armoire à clé= |
| | Le dialogue avec le serveur XML-RPC d'OpenFlyers doit s'effectuer en '''HTTPS'''. Les commandes accessibles sont listées par un appel à ActionOnDemand.php |
| | https://openflyers.com/yourURL/actionOnDemand.php |
|
| |
|
| Un autre algorithme de hachage peut être utilisé, SHA-256 reste cependant le plus répendu.
| | ==Demande de libération d'une clé== |
|
| |
|
| ;Exemple en php
| | ===Cas avec gestion des clés par OpenFlyers=== |
| <php>$digestHeader = 'Digest: SHA-256=' . base64_encode(hash('sha256', $postData, true));</php>
| |
|
| |
|
| ===Signature===
| | *Lorsque qu'une clé doit être libérée, le navigateur envoie un message au logiciel de contrôle de l'armoire à clé par le protocole HTTPS sous la forme suivante : |
| 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>
| | https://127.0.0.1:4080/?sessid=e5f01p2oqh2vb36arisr8k5j87&timeOut=10000&key=1&resource=2&person=12;action='releaseKey' |
|
| |
|
| 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'adresse IP (ici 127.0.0.1) et le port (ici 4080) sont fonction de la [[#Activation_et_configuration_de_la_gestion_des_armoires_à_clé|configuration de l'armoire à clé dans OpenFlyers]]. |
| | :*sessid contient le numéro de session en cours |
| | :*timeout correspond au temps d'attente pour la prise d'une clé en millisecondes |
| | :*key contient le numéro de la clé concernée |
| | :*resource contient le numéro de la ressource concernée |
| | :*person contient le numéro de l'utilisateur qui fait la demande |
| | :*action est un mot clé désignant l'objet de la commande |
| | :**release_key: ordre de libérer la clé |
| | :**open_door: ordre d'ouvrir la porte |
| | :**init_tags: ordre de lire les tags des clés |
| | *Le logiciel de contrôle de l'armoire à clé doit alors envoyer une demande d'ordre au serveur OpenFlyers à l'adresse suivante https://openflyers.com/structure/ où il faut remplacer openflyers.com/structure par l'adresse de la plateforme OpenFlyers concernée, avec comme commande XML_RPC '''checkCommand ("e5f01p2oqh2vb36arisr8k5j87",int(key))'''. |
|
| |
|
| L'algorithme ''algo'' correspond à celui utilisé pour générer la signature, exemple : <code>rsa-sha256</code>.
| | *Le serveur OpenFlyers vérifie alors l'action demandée : |
| | Pour release_key, il est vérifié : |
| | #Que la clé est au bon format et existe. Lorsque la clé est validée, on passe à l'étape suivante sinon on retourne 0 |
| | #Que l'utilisateur faisant la demande est bien celui qui est connecté. Lorsque c'est le cas, on passe à l'étape suivante sinon on retourne 0 |
| | #Ensuite si l'utilisateur qui a passé la demande : |
| | #*A le droit '''Gestion des clés''' (administrateur) alors on libère la clé sans condition (cela permet de libérer la clé sans contrôle) et on retourne 1 |
| | #*N'a pas le droit '''Gestion des clés''' (pilote) alors on vérifie s'il existe un vol ouvert qui remplit la double condition : |
| | #**Vol attribué à l'utilisateur |
| | #**Aéronef du vol associé à la demande de libération de la clé |
| | #**Lorsque la double condition est remplie, on retourne 1 sinon 0 |
|
| |
|
| 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>
| | *Le serveur retourne ensuite la réponse : |
| | **1 dans le cas d'autorisation de libération de clé |
| | **0 dans le cas contraire |
|
| |
|
| 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)" :
| | ====Exemple de script en Python d'utilisation de la commande checkCommand avec gestion des clés==== |
| | <python># load library |
| | from twisted.web.xmlrpc import Proxy |
| | from twisted.internet import reactor, ssl |
| | |
| | def printValue(value): |
| | print repr(value) |
| | reactor.stop() |
| | |
| | def printError(error): |
| | print 'error', error |
| | reactor.stop() |
| | |
| | # URL of the XML-RPC server |
| | proxy = Proxy('https://yourURL.xx/actionOnDemand.php') |
| | |
| | # init array to send |
| | sessid = "" |
| | key_num = 1 |
| | |
| | # send to the XML-RPC server |
| | proxy.callRemote('checkCommand', sessid, key_num).addCallbacks(printValue, printError) |
| | reactor.run()</python> |
|
| |
|
| <pre>(request-target): post /some/uri\ndate: Tue, 07 Jun 2014 20:51:35 GMT</pre>
| | Ce test peut être complété (pour obtenir une réponse "1") en simulant une armoire à clé à l'aide d'un script PHP de la façon suivante : |
| | *Installer un serveur local (par exemple [https://www.wampserver.com/ wampserver] sous Windows) de façon à avoir le port 127.0.0.1 qui lui est attribué |
| | *Configurer la plateforme OpenFlyers de manière à gérer l'armoire à clé avec les éléments suivants : |
| | **'''IP du PC contenant le logiciel de contrôle''' : 127.0.0.1 |
| | **'''Port du PC contenant le logiciel de contrôle''' : 80 |
| | *Configurer la plateforme OpenFlyers de manière à gérer les clés |
| | *A la racine du répertoire www, mettre le script index.php suivant (on suppose que ce script est appelé par défaut lorsqu'on utilise l'URL https://127.0.0.1 ) : |
| | <php><?php |
| | file_put_contents('test.txt', 'TEST', FILE_APPEND ); |
| | file_put_contents('test.txt', print_r($_REQUEST, true), FILE_APPEND ); |
| | ?></php> |
| | *Toujours à la racine du répertoire www, créer un fichier test.txt |
| | Le script PHP écrira dans le fichier test.txt les variables transmises lors de la demande d'ouverture de l'armoire à clé : |
| | <pre>TESTArray |
| | ( |
| | [sessid] => j2eo92215nbef09borb74jftm1 |
| | [key] => 1 |
| | [resource] => 1 |
| | [person] => 1 |
| | )</pre> |
| | *Il suffit alors de recopier la valeur de '''sessid''' et de '''key''' dans le script python de ce début de paragraphe et de le tester : il devrait renvoyer 1. |
|
| |
|
| La signature est ensuite générée comme ceci : <code>base64encode(algo(signing string))</code>
| | ===Cas sans gestion des clés par OpenFlyers=== |
|
| |
|
| ;Exemple en php
| | *Lorsque qu'une demande de vérification doit être réalisée, le navigateur envoie un message au logiciel de contrôle de l'armoire à clé par le protocole HTTPS sous la forme suivante : |
| <php>function generateSignatureHeader(array $headersToSign, string $certificateFingerprint, string $privateKey): string
| | https://127.0.0.1:4080/?sessid=e5f01p2oqh2vb36arisr8k5j87&resource=2&person=12 |
| {
| |
| // 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
| | :*L'adresse IP (ici 127.0.0.1) et le port (ici 4080) sont fonction de la [[#Activation_et_configuration_de_la_gestion_des_armoires_à_clé|configuration de l'armoire à clé dans OpenFlyers]]. |
| $headers = trim($headers);
| | :*sessid contient le numéro de session en cours |
| $signatureString = trim($signatureString);
| | :*resource contient le numéro de la ressource concernée |
| | :*person contient le numéro de l'utilisateur qui fait la demande |
| | *Le logiciel de contrôle de l'armoire à clé doit alors envoyer une demande d'ordre au serveur OpenFlyers à l'adresse suivante https://openflyers.com/structure/actionOnDemand.php où il faut remplacer openflyers.com/structure par l'adresse de la plateforme OpenFlyers concernée, avec comme commande XML_RPC '''checkCommand ("e5f01p2oqh2vb36arisr8k5j87")'''. |
|
| |
|
| // signing the signing string
| | *Le serveur OpenFlyers vérifie alors : |
| $signature = '';
| | #Que l'utilisateur faisant la demande est bien celui qui est connecté. Lorsque c'est le cas, on passe à l'étape suivante sinon on retourne 0 |
| openssl_sign($signatureString, $signature, $privateKey, 'RSA-SHA256');
| | #On vérifie s'il existe un vol ouvert qui remplit la double condition : |
| $signature = base64_encode($signature);
| | #*Vol attribué à l'utilisateur |
| | #*Aéronef du vol associé à la demande de libération de la clé |
| | #*Lorsque la double condition est remplie, on retourne 1 sinon 0 |
|
| |
|
| // compiling the header line
| | *Le serveur retourne ensuite la réponse : |
| return "Signature: keyId=\"$certificateFingerprint\",algorithm=\"rsa-sha256\",headers=\"$headers\",signature=\"$signature\"";
| | **1 dans le cas d'autorisation |
| }</php>
| | **0 dans le cas contraire |
| 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 :
| | ====Exemple de script en Python d'utilisation de la commande checkCommand sans gestion des clés==== |
| <php>[
| |
| $headerName => $headerValue,
| |
| ...
| |
| ]</php>
| |
|
| |
|
| '''À 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. | | <python># load library |
| | from twisted.web.xmlrpc import Proxy |
| | from twisted.internet import reactor, ssl |
| | |
| | def printValue(value): |
| | print repr(value) |
| | reactor.stop() |
| | |
| | def printError(error): |
| | print 'error', error |
| | reactor.stop() |
| | |
| | # URL of the XML-RPC server |
| | proxy = Proxy('https://yourURL.xx/actionOnDemand.php') |
| | |
| | # init array to send |
| | sessid = "" |
| | |
| | # send to the XML-RPC server |
| | proxy.callRemote('checkCommand', sessid).addCallbacks(printValue, printError) |
| | reactor.run()</python> |
|
| |
|
| =Client OAuth2=
| | Ce test peut être complété (pour obtenir une réponse "1") en simulant une armoire à clé à l'aide d'un script PHP de la façon suivante : |
| 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.
| | *Installer un serveur local (par exemple [https://www.wampserver.com/ wampserver] sous Windows) de façon à avoir le port 127.0.0.1 qui lui est attribué |
| | *Configurer la plateforme OpenFlyers de manière à gérer l'armoire à clé avec les éléments suivants : |
| | **'''IP du PC contenant le logiciel de contrôle''' : 127.0.0.1 |
| | **'''Port du PC contenant le logiciel de contrôle''' : 80 |
| | *Configurer la plateforme OpenFlyers de manière à ne pas gérer les clés |
| | *A la racine du répertoire www, mettre le script index.php suivant (on suppose que ce script est appelé par défaut lorsqu'on utilise l'URL https://127.0.0.1 ) : |
| | <php><?php |
| | file_put_contents('test.txt', 'TEST', FILE_APPEND ); |
| | file_put_contents('test.txt', print_r($_REQUEST, true), FILE_APPEND ); |
| | ?></php> |
| | *Toujours à la racine du répertoire www, créer un fichier test.txt |
| | Le script PHP écrira dans le fichier test.txt les variables transmises lors de la demande d'ouverture de l'armoire à clé : |
| | <pre>TESTArray |
| | ( |
| | [sessid] => j2eo92215nbef09borb74jftm1 |
| | [resource] => 1 |
| | [person] => 1 |
| | )</pre> |
| | *Il suffit alors de recopier la valeur de '''sessid''' dans le script python de ce début de paragraphe et de le tester : il devrait renvoyer 1. |
|
| |
|
| Plusieurs [https://oauth.net/code/ bibliothèques] simplifiant la création d'un client sont disponibles.
| | ==Modification de l'état d'une clé== |
| | ''Applicable dans le cas où le protocole PyOpenKeys2 gère les clés.'' |
| | *Le logiciel de contrôle de l'armoire à clés doit envoyer un ordre '''notify([1,0,1,1,1,1,0,0], "passphrase")''' avec comme paramètre un tableau chronologique des clés ayant pour valeur leur état |
| | ''Applicable dans le cas où le protocole PyOpenKeys3 gère les clés.'' |
| | *Le logiciel de contrôle de l'armoire à clés doit envoyer un ordre '''notify2([[1,1],[2,0],[3,1],[4,1],[5,1],[6,1],[7,0],[8,0]], "passphrase")''' avec comme paramètre un tableau de tableau avec le numéro de la clé suivi de son état |
| | **1 = interrupteur de présence de clé fermé = clé absente de l'armoire |
| | **0 = interrupteur de présence de clé ouvert = clé présente dans l'armoire |
| | |
|
| |
|
| 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'']]
| | ===Exemple de script en Python pour la commande notify=== |
| | Nécessite les librairies Twisted et OpenSSL |
|
| |
|
| ==Authorization Code==
| | <python># load library |
| Ce flux OAuth2 se déroule en plusieurs étapes :
| | from twisted.web.xmlrpc import Proxy |
| *Le client redirige le navigateur de l'utilisateur vers l'URL d'autorisation.
| | from twisted.internet import reactor, ssl |
| *Le navigateur de l'utilisateur est redirigé vers l'URL fournie durant la demande ou durant l'enregistrement du client.
| | import random |
| *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.
| |
|
| |
|
| | def printValue(value): |
| | print repr(value) |
| | reactor.stop() |
|
| |
|
| +-----------+ +------+ +-----------+ +-------------+ +-----------+
| | def printError(error): |
| |Utilisateur| |Client| |Navigateur | | Serveur | | Serveur |
| | print 'error', error |
| +-----+-----+ +--+---+ +-----+-----+ |Autorisation | | Ressources| | | reactor.stop() |
| | | | +------+------+ +-----+-----+
| |
| | | | | |
| |
| | | 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 | |
| |
| | |<-------------------------+-------------------------------+-------------------------------+
| |
| | | | | |
| |
|
| |
|
| | # URL of the XML-RPC server |
| | proxy = Proxy('https://yourURL.xx/actionOnDemand.php') |
|
| |
|
| | # init passphrase |
| | passphrase = 'SDjklsdiuQSIPO23879ZERK2098ZL2908DFZLK' |
|
| |
|
| ===Générer les codes pour PKCE=== | | # Initiate number of key |
| Pour faire fonctionner le client OAuth2, il faut générer deux codes pour l'extension PKCE :
| | KEY_NUMBER = 16 |
| *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''
| | # init array to send |
| <php>function generateCodeVerifier($length = 128): string
| | state = random.randint(0,1) |
| {
| |
| $characters = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-._~';
| |
| $outputCode = '';
| |
|
| |
|
| for ($i = 0; $i < $length; $i++) { | | if KEY_NUMBER > 8: |
| $index = random_int(0, strlen($characters) - 1); | | # Test notify2 |
| $outputCode .= $characters[$index]; | | status = [[d+1,0] for d in range(KEY_NUMBER)] |
| }
| | for i in range(KEY_NUMBER): |
| | status[i] = [i+1,state] |
| | # Alternate 0 and 1 for test |
| | state = 0 if state == 1 else 1 |
| | else: |
| | # Test notify |
| | for i in range(KEY_NUMBER): |
| | status.append(state) |
| | # Alternate 0 and 1 for test |
| | state = 0 if state == 1 else 1 |
|
| |
|
| return $outputCode; | | # send to the XML-RPC server |
| }</php>
| | print "Status sequence : %s" % status |
| | if KEY_NUMBER<9: |
| | proxy.callRemote('notify', status, passphrase).addCallbacks(printValue, printError) |
| | else: |
| | proxy.callRemote('notify2', status, passphrase).addCallbacks(printValue, printError) |
| | reactor.run() |
| | </python> |
| | ''Nota : to test change the number of key : up to 8 the software use '''notify()''', beyond it uses '''notify2()''' |
|
| |
|
| 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.
| | Cela retournera 0 en cas d'anomalie ou si la passphase transmise n'est pas correcte, 1 si la table des clés a été correctement mise à jour. |
|
| |
|
| ;Générer le ''code_challenge''
| | ==Fermeture automatique d'un vol== |
| <php>function computeCodeChallenge(string $codeVerifier): string
| |
| {
| |
| return strtr(rtrim(base64_encode(hash('sha256', $codeVerifier, true)), '='), '+/', '-_');
| |
| }</php>
| |
|
| |
|
| Cette fonction génère le ''code_challenge'' à partir du ''code_verifier'' fourni.
| | Le logiciel de contrôle de l'armoire à clé doit envoyer un ordre '''closeFlight("passphrase", "id de ressource")''' avec comme paramètre un passphrase et l'id qui va entraîner la fermeture du vol effectué sur cette ressource si un vol avait été ouvert et que la "Fermeture automatique des |
| | vols au retour de la clé" soit activée. La commande retournera comme réponse une structure JSON pour déterminer si la fermeture du vol s'est bien réalisée ou non et qu'il n'y a pas eu d'erreur. |
|
| |
|
| | Réponse quand la vérification du passphrase est incorrecte, ce qui a entraîné la non-fermeture du vol : |
| | <javascript>{[ |
| | ack : 0 |
| | ]}</javascript> |
|
| |
|
| ===Demande d'autorisation===
| | Réponse quand la vérification du passphrase est correcte et que la demande de fermeture du vol s'est bien réalisée : |
| 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.
| | <javascript>{[ |
| | ack : 1 |
| | ]}</javascript> |
|
| |
|
| Envoyer également les paramètres suivants :
| | Réponse quand la vérification du passphrase est correcte et que la demande de fermeture du vol ne s'est pas bien réalisée suite à une erreur : |
| {| class="wikitable"
| | <javascript>{[ |
| !Nom!!Type!!Description
| | ack : 1, |
| |-
| | error : 'Message d\'erreur ...' |
| |client_id||string||L'identifiant unique reçu pendant l'enregistrement du client.
| | ]}</javascript> |
| |-
| |
| |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=== | | ===Script d'exemple pour closeFlight=== |
| 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é.
| | <python># load library |
| ;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.
| | from twisted.web.xmlrpc import Proxy |
| | from twisted.internet import reactor, ssl |
| | import random |
|
| |
|
| 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.
| | def printValue(value): |
| | print repr(value) |
| | reactor.stop() |
|
| |
|
| Les paramètres suivants sont également nécessaires :
| | def printError(error): |
| {| class="wikitable"
| | print 'error', error |
| !Nom!!Type!!Description
| | reactor.stop() |
| |-
| |
| |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.
| | # URL of the XML-RPC server |
| <javascript>{
| | proxy = Proxy('https://yourURL.xx/actionOnDemand.php') |
| "token_type": "Bearer",
| |
| "expires_in": 3600,
| |
| "access_token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJSU-G7fOBOYzOgioSGNIQKI2A2OxjsNBbOOoaHsqNpsQxWZse3rofExAaGKh2tXbeuz1YAVhdLUGYgq-oKRK4ONFhw2NvcRf3QPxQXZImLWw",
| |
| "refresh_token": "a59ef39fa9bab9b95cd554f921e7f3080a34c90f23d2b8031d692b5ff2d0993dcc392d9c7f9a43242337ef144c1a5fe1d0174413ade973e1b628ac0bbfc39b23973534"
| |
| }</javascript>
| |
|
| |
|
| ===Script client : Authorization Code=== | | # Id of resource which needs a flight closing |
| Voici un exemple simple de client OAuth2 pour le mécanisme d'authentification par code d'autorisation (Authorization Code).
| | resource_id = 1 |
|
| |
|
| Fichier de configuration ''config.authcode.json'' :
| | # init passphrase |
| <javascript>{
| | passphrase = 'SDjklsdiuQSIPO23879ZERK2098ZL2908DFZLK' |
| "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"
| |
| }</javascript>
| |
| 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.
| | # send to the XML-RPC server |
| | proxy.callRemote('closeFlight', resource_id, passphrase).addCallbacks(printValue, printError) |
| | reactor.run()</python> |
|
| |
|
| Script PHP :
| | ==Exemple de script en Python de logiciel de contrôle d'une armoire== |
| <php><?php | | <python>#!/usr/bin/python |
| $localTokenFile = 'token.json';
| | import xmlrpclib, time, dummy_proto, hashlib |
| // create token file if it does not exist
| | from twisted.internet import reactor, task, threads, ssl |
| if (!file_exists($localTokenFile))
| | from twisted.application import internet, service |
| file_put_contents($localTokenFile, '');
| | from twisted.internet.protocol import Protocol, ClientCreator, ReconnectingClientFactory |
| | from twisted.web import resource, server |
|
| |
|
| $config = json_decode(file_get_contents('config.authcode.json'), true);
| | # Port from which the OF client contact OpenKeys service |
| $localToken = json_decode(file_get_contents($localTokenFile), true);
| | SERVICE_PORT=4080 |
| $GLOBALS['config'] = $config;
| | # IP address of the OpenKeys service |
| | SERVICE_HOST="192.168.0.200" |
|
| |
|
| // Build the baseURL, accounting for protocol, port, name and endpoint. Used for redirection
| | # IP address of the key cabinet |
| $protocol = isset($_SERVER['HTTPS']) ? 'https://' : 'http://';
| | KEYS_ADDR='192.168.0.201' |
| $port = ($protocol == 'https://' && $_SERVER['SERVER_PORT'] == 443)
| | # Port from which OpenKeys service contacts the key cabinet |
| || ($protocol == 'http://' && $_SERVER['SERVER_PORT'] == 80) ? '' : ':' . $_SERVER['SERVER_PORT'];
| | KEYS_PORT=6002 |
| | #Time to release the key |
| | KEY_RELEASE_DURATION=15 |
|
| |
|
| $baseURL = $protocol . $_SERVER['SERVER_NAME'] . $port . $_SERVER['PHP_SELF'];
| | # sha224 passwords |
| $errorLog = 'error_log.log';
| | PASSWORDS=('847bed9bc354e7e47bc5350a3b3aaf6124f5748224a3c7ad79586c3c') |
| $responseLog = 'response_log.log';
| |
| $GLOBALS['baseURL'] = $baseURL;
| |
|
| |
|
| //Session cookies are used to store information necessary for the authorization code flow
| | # init passphrase |
| session_start();
| | passphrase = 'SDjklsdiuQSIPO23879ZERK2098ZL2908DFZLK' |
| | |
| /**
| |
| * 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);
| | OF_XMLRPC_URL="https://openflyers.com/structure/actionOnDemand.php" |
|
| |
|
| $headersToSign['(request-target)'] = $method . ' ' . $urlComponents['path'];
| | DEBUG=False |
| $headersToSign['Host'] = $urlComponents['host'];
| |
| $headersToSign['Date'] = gmdate('D, j M Y H:i:s T');
| |
|
| |
|
| // generating the signature header
| | class dummyProtocol(Protocol): |
| $keyId = openssl_x509_fingerprint(file_get_contents($GLOBALS['config']['sign_cert']));
| | def __init__(self, rpc_server): |
| $privateKey = file_get_contents($GLOBALS['config']['sign_key']);
| | self.rpc_server=rpc_server |
| $headers['Signature'] = generateSignatureHeader($headersToSign, $keyId, $privateKey);
| | self.lc = None |
|
| |
|
| $headers['Accept'] = 'application/json';
| | def connectionMade(self): |
| $headers['User-Agent'] = $GLOBALS['baseURL'];
| | if not self.lc: |
| unset($headersToSign['(request-target)']);
| | # update status every 10 minutes |
| $headers += $headersToSign;
| | self.lc = task.LoopingCall(self.updateStatus) |
| | self.lc.start(600) |
|
| |
|
| if ($token) {
| | def dataReceived(self, data): |
| $headers['Authorization'] = $token['token_type'] . ' ' . $token['access_token'];
| | msg=dummy_proto.dummy_message.newFromData(data) |
| }
| | if DEBUG: msg.display() |
| | try: |
| | if type(msg)==dummyproto.dummy_ONOFF_Control_Response: |
| | response = self.rpc_server.notify(msg.getKeysStatus(),passphrase) |
| | except Exception, err: |
| | if DEBUG: print "error: ", err |
| | pass # ignore |
|
| |
|
| // formatting the headers
| | def updateStatus(self): |
| $httpFormattedHeaders = [];
| | msg = dummy_proto.dummy_State_Request() |
| foreach ($headers as $key => $value) {
| | self.transport.write(msg.build_message()) |
| $httpFormattedHeaders[] = trim($key) . ': ' . trim($value);
| | |
| }
| | def send(self, dummy_data): |
| | self.transport.write(dummy_data.build_message()) |
|
| |
|
| curl_setopt($ch, CURLOPT_HTTPHEADER, $httpFormattedHeaders);
| | class dummyClientFactory(ReconnectingClientFactory): |
| curl_setopt($ch, CURLINFO_HEADER_OUT, true);
| | def __init__(self, rpc_server): |
| | self.rpc_server = rpc_server |
|
| |
|
| // for development environment only
| | def buildProtocol(self, addr): |
| //curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
| | self.resetDelay() |
| //curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
| | self.protocol = dummyProtocol(self.rpc_server) |
| //curl_setopt($ch, CURLOPT_VERBOSE, true);
| | return self.protocol |
|
| |
|
| // defining TLS client certificates for Mutual TLS
| | def clientConnectionLost(self, connector, reason): |
| curl_setopt($ch, CURLOPT_SSLVERSION, CURL_SSLVERSION_TLSv1_2);
| | ReconnectingClientFactory.clientConnectionLost(self, connector, reason) |
| curl_setopt($ch, CURLOPT_CAINFO, $GLOBALS['config']['auth_cacert']);
| | connector.connect() |
| curl_setopt($ch, CURLOPT_SSLCERT, $GLOBALS['config']['auth_cert']);
| |
| curl_setopt($ch, CURLOPT_SSLKEY, $GLOBALS['config']['auth_key']);
| |
|
| |
|
| $response = curl_exec($ch);
| | def clientConnectionFailed(self, connector, reason): |
| | if DEBUG: print 'Connection failed. Reason:', reason |
| | ReconnectingClientFactory.clientConnectionFailed(self, connector, reason) |
|
| |
|
| // logging errors and responses
| | # blocking method ! must be run in a new thread |
| errorLog(true, $response, $ch);
| | def release_key(self, key_num, timeOur): |
| | m = dummy_proto.dummy_ONOFF_Control() |
| | m.setON(key_num) |
| | self.protocol.send(m) |
| | time.sleep(timeOut) |
| | m.setOFF(key_num) |
| | self.protocol.send(m) |
| | return key_num |
|
| |
|
| curl_close($ch);
| | class WebResource(resource.Resource): |
| | def __init__(self, rpc_server, dummy_client_factory): |
| | self.rpc_server = rpc_server |
| | self.dummy_client_factory = dummy_client_factory |
| | resource.Resource.__init__(self) |
| | self.keys_webcontrol_state = [0,0,0,0,0,0,0,0,0,0] |
| | |
| | def getChild(self, name, request): |
| | return self |
|
| |
|
| // in case an authentication request is executed
| | def render(self, request): |
| if ($auth) {
| | reponse = 0 # NOK par defaut |
| // required when a refresh token request is issued
| | key_num = 0 |
| // because there is only one header in the response
| | try: |
| // while there are two for regular auth requests
| | sessid = request.args.get('sessid', [""])[0] |
| if (!$refresh) {
| | password = request.args.get('password', [""])[0] |
| list($firstResponseHeaders, $secondResponseHeaders, $responseBody) = explode("\r\n\r\n", $response, 3);
| | key_num = int(request.args.get('key', ["0"])[0]) |
| if (!$responseBody) {
| | response = 0 |
| list($secondResponseHeaders, $responseBody) = explode("\r\n\r\n", $response, 2);
| | if password: |
| }
| | if hashlib.sha224(password).hexdigest() in PASSWORDS: |
| } else {
| | timeOut = KEY_RELEASE_DURATION |
| list($secondResponseHeaders, $responseBody) = explode("\r\n\r\n", $response, 2);
| | response_XMLRPC = 1 |
| }
| | else: |
| | timeOut = int(request.args.get('timeout', ["0"])[0]) |
| | response = self.rpc_server.checkCommand(sessid, key_num) |
| | except Exception, err: |
| | if DEBUG: print "error: ", err |
| | return "NOK:bad request" |
| | |
| | if response == 1: |
| | # Don't try to release a key that is being released |
| | if self.keys_webcontrol_state[key_num-1]: return "OK:already released" |
| | self.keys_webcontrol_state[key_num-1] = 1 |
| | d = threads.deferToThread(self.dummy_client_factory.release_key, key_num, timeOut) |
| | d.addCallback(self.unset_webcontrol_state) |
| | return "OK:releasing key..." |
| | else: |
| | return "NOK:permission refused" |
|
| |
|
| $responseHeadersDigest = '';
| | def unset_webcontrol_state(self, key_num): |
| $responseHeadersSignature = '';
| | self.keys_webcontrol_state[key_num-1] = 0 |
| // extracting digest and signature from headers
| |
| $responseHeadersArray = explode("\r\n", $secondResponseHeaders);
| |
| $responseProtocol = array_shift($responseHeadersArray);
| |
|
| |
|
| foreach ($responseHeadersArray as $value) {
| | class OpenKeysService(service.Service): |
| list($responseHeadersKeys, $responseHeadersValue) = explode(": ", $value, 2);
| | def __init__(self): |
| $responseHeaders[strtolower($responseHeadersKeys)] = $responseHeadersValue;
| | rpc_server=xmlrpclib.Server(OF_XMLRPC_URL); |
| }
| | self.dummy_client_factory = dummyClientFactory(rpc_server) |
| | self.web_resource = WebResource(rpc_server, self.dummy_client_factory) |
| | |
| | def getdummyClientFactory(self): |
| | return self.dummy_client_factory |
|
| |
|
| $responseDigestAlgo = '';
| | def getWebResource(self): |
| $responseDigestKey = '';
| | return self.web_resource |
| 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
| | application = service.Application('openkeys') |
| $responseDigest = base64_encode(hash(strtolower(str_replace("-", "", $responseDigestAlgo)), $responseBody, true));
| | f = OpenKeysService() |
| | serviceCollection = service.IServiceCollection(application) |
| | internet.TCPClient(KEYS_ADDR, KEYS_PORT, f.getdummyClientFactory() |
| | ).setServiceParent(serviceCollection) |
| | internet.TCPServer(SERVICE_PORT, server.Site(f.getWebResource()) |
| | ).setServiceParent(serviceCollection)</python> |
|
| |
|
| // checking digest validity
| | ==Maquette de script actionOnDemand.php côté serveur recevant les appels du logiciel de contrôle de l'armoire== |
| if ($responseDigest !== $responseDigestKey) {
| | Ce script nécessite la bibliothèque PEAR [http://pear.php.net/package/XML_RPC2 XML_RPC2]. |
| errorLog(false, false, "Digests are not the same");
| |
| die();
| |
| }
| |
|
| |
|
| // extracting variables from signature header
| | Pour les tests, il suffit de modifier la valeur de la variable $weDontWant. |
| $signatureArray = explode(",", $responseHeadersSignature);
| |
| foreach ($signatureArray as $value) {
| |
| list($signatureKey, $signatureValue) = explode("=", $value, 2);
| |
| $signature[$signatureKey] = trim($signatureValue, '"');
| |
| }
| |
|
| |
|
| // generating siging string
| | <php><?php |
| $signingStringArray = explode(" ", $signature['headers']);
| | include 'XML/RPC2/Server.php'; |
| $signingString = '';
| |
| foreach ($signingStringArray as $value) {
| |
| $signingString = $signingString . trim($value) . ": " . trim($responseHeaders[$value]) . "\n";
| |
| }
| |
| | |
| // trimming last '\n' character
| |
| $signingString = trim($signingString);
| |
|
| |
|
| // decoding signature
| | class OpenKeysGateway { |
| $decodedSignature = base64_decode($signature['signature']);
| | /** |
| | | * Update the status of the keys |
| // verifying signature | | * |
| $signatureVerify = openssl_verify($signingString, $decodedSignature, openssl_get_publickey(file_get_contents($GLOBALS['config']['sign_cert_server'])), 'RSA-SHA256'); | | * @param array $status Status of keys |
| | | * @return integer 1 if ok, 0 else |
| if (!$signatureVerify) {
| | */ |
| errorLog(false, false, "Signature is not correct");
| | public static function notify($status) { |
| while (($err = openssl_error_string())) | | $logmsg = ""; |
| errorLog(false, false, $err); | | foreach ($status as $key_num_from_zero => $key_pres) { |
| die(); | | if (!is_numeric($key_pres) || intval($key_pres)!=$key_pres || $key_pres < 0 || $key_pres > 1) continue; |
| | if (!is_numeric($key_num_from_zero) || intval($key_num_from_zero)!=$key_num_from_zero |
| | || $key_num_from_zero < 0 || $key_num_from_zero > 9) continue; |
| | $logmsg .= "".($key_num_from_zero+1).":".$key_pres.","; |
| } | | } |
| | | file_put_contents('test.txt', "key notify :".$logmsg, FILE_APPEND ); |
| return json_decode($responseBody, true); | | return 1; |
| }
| |
| | |
| 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();
| |
| }</php>
| |
|
| |
| ;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 : <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 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.
| |
| |}
| |
|
| |
| ;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.
| |
| <javascript>{
| |
| "token_type": "Bearer",
| |
| "expires_in": 3600,
| |
| "access_token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJSU-G7fOBOYzOgioSGNIQKI2A2OxjsNBbOOoaHsqNpsQxWZse3rofExAaGKh2tXbeuz1YAVhdLUGYgq-oKRK4ONFhw2NvcRf3QPxQXZImLWw"
| |
| }</javascript>
| |
|
| |
| ===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'' :
| |
| <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"
| |
| }</javascript>
| |
| 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>.
| |
|
| |
| Ce fichier de configuration doit se situer au même niveau que le script PHP dans le système de fichiers.
| |
|
| |
| Script php :
| |
| <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://';
| | * Check if user is able to release the key 'key_num' |
| $port = ($protocol == 'https://' && $_SERVER['SERVER_PORT'] == 443)
| | * |
| || ($protocol == 'http://' && $_SERVER['SERVER_PORT'] == 80) ? '' : ':' . $_SERVER['SERVER_PORT'];
| | * @param string $sessid PHPSESSID of a connected user |
| | | * @param integer $key_num number of the key to release |
| $baseURL = $protocol . $_SERVER['SERVER_NAME'] . $port . $_SERVER['PHP_SELF'];
| | * @return integer 0:NOK, 1:OK |
| $errorLog = 'error_log.log';
| | */ |
| $responseLog = 'response_log.log';
| | public static function checkCommand($sessid, $key_num) { |
| $GLOBALS['baseURL'] = $baseURL;
| | /* sanitize input */ |
| | | if (!is_numeric($key_num) || intval($key_num)!=$key_num || $key_num < 1 || $key_num > 10) { |
| $replacementListItems = [
| | return 0; |
| 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 = ''; | | $weDontWant = 1; |
| $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
| | if ($weDontWant) { |
| $responseDigest = base64_encode(hash(strtolower(str_replace("-", "", $responseDigestAlgo)), $responseBody, true));
| | file_put_contents('test.txt', "$key_num has no associated airborne aircraft", FILE_APPEND ); |
| | | return 0; |
| // checking digest validity
| |
| if ($responseDigest !== $responseDigestKey) { | |
| errorLog(false, false, "Digests are not the same"); | |
| die(); | |
| } | | } |
| | | else { |
| // extracting variables from signature header
| | file_put_contents('test.txt', "granted key: ".$key_num.":permission granted", FILE_APPEND ); |
| $signatureArray = explode(",", $responseHeadersSignature);
| | return 1; |
| 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
| | $options = array( |
| {
| | 'autoDocument' => true, |
| 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;
| |
| }
| |
| }</php>
| |
| | |
| ;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|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 : <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 nécessaires :
| |
| {| 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.
| |
| |}
| |
| | |
| Si la requête est correcte, un jeton d'accès ''access_token'' est fourni en réponse dans un objet au format JSON.
| |
| <javascript>{
| |
| "token_type": "Bearer",
| |
| "expires_in": 3600,
| |
| "access_token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJSU-G7fOBOYzOgioSGNIQKI2A2OxjsNBbOOoaHsqNpsQxWZse3rofExAaGKh2tXbeuz1YAVhdLUGYgq-oKRK4ONFhw2NvcRf3QPxQXZImLWw",
| |
| "refresh_token": "a59ef39fa9bab9b95cd554f921e7f3080a34c90f23d2b8031d692b5ff2d0993dcc392d9c7f9a43242337ef144c1a5fe1d0174413ade973e1b628ac0bbfc39b23973534"
| |
| }</javascript>
| |
| | |
| ==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 :
| |
| {| 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é).
| |
| | |
| 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.
| |
| <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);</php>
| |
| | |
| 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 : <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.
| |
| | |
| Envoyer également le paramètre suivant:
| |
| {| class="wikitable"
| |
| !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.)
| |
| |}
| |
| <php> apiRequest(
| |
| $config['revoke_uri'],
| |
| ['access_token' => $_SESSION['auth_token']['access_token']],
| |
| null,
| |
| false
| |
| );</php>
| |
| | |
| 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 : <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>.
| |
| | |
| ===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 :
| |
| | |
| {| class="wikitable"
| |
| !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 :
| |
| <javascript>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"
| |
| }</javascript>
| |
| | |
| 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 :
| |
| | |
| {| class="wikitable"
| |
| !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 :
| |
| <javascript>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
| |
| }
| |
| }</javascript>
| |
| | |
| La réponse est un fichier CSV retourné dans un conteneur JSON.
| |
| | |
| ===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 :
| |
| | |
| {| class="wikitable"
| |
| !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 :
| |
| <javascript>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
| |
| }
| |
| }</javascript>
| |
| | |
| 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 :
| |
| | |
| [[File:Oauth2 src demo folder.png|800px]]
| |
| | |
| * 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 :
| |
| | |
| [[File:Oauth2 demo tree.png]]
| |
| | |
| ;[[#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'.
| |
| | |
| ;[[#Enregistrer un client|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'''.
| |
| | |
| [[File:Oauth2 demo manage.png|800px]]
| |
| | |
| *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.
| |
| <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"
| |
| }</php>
| |
| | |
| *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>.
| |
| | |
| | |
| *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.
| |
| <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": "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"
| |
| }</php>
| |
| | |
| *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>.
| |
| | |
| | |
| *Suivre la [[#Utiliser le client|procédure d'utilisation du client de démonstration]] pour faire fonctionner le client.
| |
| | |
| | |
| '''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 [[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".
| |
| | |
| *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'''
| |
| [[File:Oauth2 manage.png|800px]]
| |
| *Cliquer sur le bouton Ajouter '''+''' ou '''Ajouter un client'''
| |
| [[File:Oauth2 client creation.png|800px]]
| |
| *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-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).
| |
| | |
| [[File:PublicKeysCopyScreen.png|900px]]
| |
| | |
| | |
| *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'''.
| |
| [[File:Oauth2 client created.png]]
| |
| | |
| [[File:ConfigCredJsonScreen.png|600px]]
| |
| | |
| | |
| ;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]]
| |
| | |
| ;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'''
| |
| | |
| ==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.
| |
| | |
| Pour obtenir ces certificats, il faut d'abord générer des Certificate Signing Request (CSR).
| |
| | |
| 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.
| |
| | |
| ;Fichier de configuration OpenSSL - sign_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
| |
| | |
| [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</pre>
| |
| | |
| ;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
| |
| | |
| [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</pre>
| |
| | |
| | |
| Exécuter les commandes suivantes :
| |
| *<bash>openssl req -sha256 -newkey rsa -keyout sign.key -out sign_cert.csr.pem -outform PEM -config sign_cert.conf</bash>
| |
| *<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 [[#Enregistrer-un-client|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 des certificats|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 un client|Enregistrer les deux clients]] :
| |
| **Le premier pour le mécanisme d'autorisation '''Authorization Code'''.
| |
| **Le second pour le mécanisme d'autorisation '''Client Credentials'''.
| |
| | |
| [[File:Oauth2 demo manage.png|800px]]
| |
| | |
| *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.
| |
| <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": "./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"
| |
| }</php>
| |
| | |
| | |
| *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.
| |
| <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"
| |
| }</php>
| |
| | |
| *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.
| |
| | |
| *Transférer le fichier "oauth-demo" vers le serveur mutualisé :
| |
| **Télécharger [http://filezilla-project.org/download.php?type=client 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.
| |
| [[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.
| |
| | |
| *Suivre la [[#Utiliser le client|procédure d'utilisation du client de démonstration]] pour faire fonctionner le client.
| |
| | |
| | |
| '''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 [[Gestion-des-rapports#Ajouter-un-rapport|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.
| |
| | |
| [[File:Oauth2-client-demo.png]]
| |
| | |
| 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.
| |
| | |
| ;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.
| |
| | |
| [[File: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.
| |
| | |
| [[File: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 :
| |
|
| |
|
| *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.
| | // dirty hack to get things work ! |
| | $GLOBALS['HTTP_RAW_POST_DATA'] = file_get_contents('php://input'); |
|
| |
|
| ;Si cela ne fonctionne pas: | | $server = XML_RPC2_Server::create('OpenKeysGateway', $options); |
| | $server->handleCall(); |
|
| |
|
| *Essayez de créer [[#Enregistrer-un-client|un nouveau client]] et refaites la configuration des certificats.
| | ?></php> |