API OpenFlyers: Difference between revisions
imported>Claratte |
imported>Claratte |
||
Line 34: | Line 34: | ||
L'utilisation du code source est décrite dans le chapitre [[#Configurer-le-client-à-partir-du-code-source|Configurer le client à partir du code source]]. | L'utilisation du code source est décrite dans le chapitre [[#Configurer-le-client-à-partir-du-code-source|Configurer le client à partir du code source]]. | ||
=Authentification TLS Mutuelle (mTLS)= | =Authentification TLS Mutuelle (mTLS)= |
Revision as of 14:48, 14 November 2023
Présentation
L'objet de cette page est de décrire l'API OpenFlyers.
- Description de l'API
OpenFlyers possède une API basée sur OAuth2 qui permet à des serveurs extérieurs, dûment enregistrés, de mettre en œuvre un processus d'authentification unique (SSO) et/ou de récupération des résultats des requêtes SQL de la bibliothèque des rapports ou des rapports personnalisés sous la forme de fichiers CSV.
OAuth2 propose plusieurs mécanismes pour permettre l'authentification. Un mécanisme d'authentification détermine la séquence exacte des étapes impliquées dans le processus d'authentification d'OAuth2. OpenFlyers met à disposition deux mécanismes d'authentification :
- Authorization Code basé sur la méthode d'authentification par code d'autorisation et qui correspond au mécanisme associé à l'authentification unique (SSO),
- Client Credentials basé sur la méthode d'authentification avec les identifiants clients et qui est utilisé dans un contexte d'automatisme sans autorisation de l'utilisateur au préalable.
Dans les chapitres qui suivent, le terme ressource fait référence à la définition OAuth2. Une ressource dans OAuth2 est un élément qui peut être :
- une ou des données comme des photos, des documents, des contacts ou des informations personnelles,
- un ou plusieurs services comme des transferts de fonds, la récupération de rapports ou l'ajout d'articles sur un blog,
- toute ressource nécessitant un accès restreint.
OpenFlyers définit plusieurs types de ressources :
- les informations de l'utilisateur connecté,
- les rapports génériques ou 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 de scopes utilisables à travers l'API.
Deux protocoles de sécurité sont présents dans l'API OpenFlyers :
- mTLS : il permet d'authentifier le client avec un certificat TLS, en plus d'authentifier le serveur avec un certificat. Ce protocole permet d'éviter les usurpations d'identité.
- HTTP-Signature : il permet de signer les en-têtes et le corps (lorsqu'il y en a un) des messages échangés afin d'en garantir leur intégrité.
- Premiers pas - Client de démonstration
Un client de démonstration est disponible pour comprendre les mécanismes décrits ci-dessous.
L'utilisation de ce client de démonstration est décrite dans le chapitre Client de démonstration de cette page.
Le client de démonstration est lui-même accessible à cette adresse : https://openflyers.com/oauth2-demo/index.php
Le code source du client de démonstration est mis à disposition par OpenFlyers à cette adresse : https://openflyers.com/oauth2-demo/oauth2-demo-src.zip
L'utilisation du code source est décrite dans le chapitre Configurer le client à partir du code source.
Authentification TLS Mutuelle (mTLS)
En général dans une communication TLS, seul le serveur a l'obligation de fournir un certificat. Il est également possible pour le client de fournir un certificat. Ce principe s'appelle l'authentification mutuelle et est mise en place avec Mutual TLS (ou mTLS).
OpenFlyers associe un certificat pour l'authentification mutuelle unique à chaque client OAuth2.
- Envoyer un certificat client
Côté client, le code suivant peut être utilisé pour fournir à cURL le certificat et la clé correspondante ainsi que le certificat du CA d'OpenFlyers à utiliser pour la connexion : <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.
HTTP Signature
HTTP Signature utilise le principe de la signature numérique pour garantir l'authenticité et l'intégrité du message HTTP.
La signature est générée à l'aide d'une clé privée et vérifiée à l'aide de la clé publique correspondante ou d'un certificat contenant cette clé publique.
HTTP Signature utilise deux en-têtes HTTP :
- Signature : contient la signature et ses métadonnées.
- Digest : contient le corps du message haché.
Digest
Le digest est calculé comme ceci : digest = base64encode(sha256(corps du message))
Et l'en-tête est structuré de la manière suivante : Digest: SHA-256=<digest>
Un autre algorithme de hachage peut être utilisé, SHA-256 reste cependant le plus répendu.
- Exemple en php
<php>$digestHeader = 'Digest: SHA-256=' . base64_encode(hash('sha256', $postData, true));</php>
Signature
L'en-tête HTTP de signature est structuré de la manière suivante : Signature: keyId="<keyId>",algorithm="<algo>",headers="<signed_headers>",signature="<signature>"
Le champ keyId correspond à un identifiant permettant l'identification de la clé utilisée pour vérifier la signature. Pour l'API d'OpenFlyers, sa valeur correspond à l'empreinte SHA-1 du certificat au format PEM à utiliser pour vérifier la signature.
L'algorithme algo correspond à celui utilisé pour générer la signature, exemple : rsa-sha256
.
La valeur de signed_headers correspond à la liste des en-têtes inclus dans la signature séparés d'un espace. Exemple : date digest (request-target)
Pour générer la signature, une chaîne de caractères appelée signing string
contenant les en-têtes au format lowercase_header_name: value
séparés par une nouvelle ligne au format LF (\n
) est d'abord générée. Exemple avec les en-têtes "date" et "(request-target)" :
(request-target): post /some/uri\ndate: Tue, 07 Jun 2014 20:51:35 GMT
La signature est ensuite générée comme ceci : base64encode(algo(signing string))
- Exemple en php
<php>function generateSignatureHeader(array $headersToSign, string $certificateFingerprint, string $privateKey): string {
// generating the signing string and header list $headers = ; $signatureString = ; foreach ($headersToSign as $key => $value) { $normalizedHeaderKey = trim(strtolower($key)); $headers .= $normalizedHeaderKey . ' '; $signatureString .= $normalizedHeaderKey . ': ' . trim($value) . "\n"; }
// trim extra whitespace $headers = trim($headers); $signatureString = trim($signatureString);
// signing the signing string $signature = ; openssl_sign($signatureString, $signature, $privateKey, 'RSA-SHA256'); $signature = base64_encode($signature);
// compiling the header line return "Signature: keyId=\"$certificateFingerprint\",algorithm=\"rsa-sha256\",headers=\"$headers\",signature=\"$signature\"";
}</php> 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 : <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.
Client OAuth2
Une fois le client OAuth2 configuré sur OpenFlyers, il faut l'utiliser avec un client créé au préalable pour communiquer avec le serveur d'autorisation et l'API.
Plusieurs bibliothèques simplifiant la création d'un client sont disponibles.
Des scripts client basiques écrits en php sont aussi fournis pour les mécanismes authorization_code et client_credentials
Authorization Code
Ce flux OAuth2 se déroule en plusieurs étapes :
- Le client redirige le navigateur de l'utilisateur vers l'URL d'autorisation.
- Le navigateur de l'utilisateur est redirigé vers l'URL fourni durant la demande ou durant l'enregistrement du client.
- Le client récupère un code d'autorisation grâce à la redirection précédente, et échange ce code contre un jeton d'accès auprès du serveur d'autorisation.
- Le client peut utiliser ce code d'accès :
- Comme preuve d'authentification pour une solution SSO (Single Sign-On).
- Pour accéder à des données sur le serveur distant en utilisant l'API.
+-----------+ +------+ +-----------+ +-------------+ +-----------+ |Utilisateur| |Client| |Navigateur | | Serveur | | Serveur | +-----+-----+ +--+---+ +-----+-----+ |Autorisation | | Ressources| | | | +------+------+ +-----+-----+ | | | | | | | Demande|d'autorisation | | | +------------------------->+------------------------------>| | | | | | | | |Authentification + Formulaire d'autorisation | | |<---------------------------+--------------------------+<------------------------------+ | | | | | | | | Autorisation | | | +----------------------------+------------------------->+------------------------------>| | | | | | | | | Code|d'autorisation | | | |<-------------------------+<------------------------------+ | | | | | | | | Demande de|token | | | +--------------------------+------------------------------>| | | | | | | | | Access (+|Refresh) token | | | |<-------------------------+-------------------------------+ | | | | | | | | | Requête vers API | | | +--------------------------+-------------------------------+------------------------------>| | | | | | | | | | | | | | |<------------------------------+ | | | | Vérification d'autorisation | | | | +------------------------------>| | | | | | | | | Données/Réponse | | | |<-------------------------+-------------------------------+-------------------------------+ | | | | |
Générer les codes pour PKCE
Pour faire fonctionner le client OAuth2, il faut générer deux codes pour l'extension PKCE :
- Un code_verifier échangé pendant la demande de jeton d'accès.
- Un code_challenge dérivé du code_verifier et échangé pendant la demande d'autorisation.
- Générer le code_verifier
<php>function generateCodeVerifier($length = 128): string {
$characters = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-._~'; $outputCode = ;
for ($i = 0; $i < $length; $i++) { $index = random_int(0, strlen($characters) - 1); $outputCode .= $characters[$index]; }
return $outputCode;
}</php>
Cette fonction permet de générer un code_verifier avec une longueur donnée. Le code_verifier doit avoir une longueur entre 43 et 128 caractères.
- Générer le code_challenge
<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.
Demande d'autorisation
Pour initier la demande d'autorisation, rediriger le navigateur de l'utilisateur vers l'URL : GET https://openflyers.com/nom-de-plateforme/oauth/authorize.php
. Remplacer nom-de-plateforme
par le nom de la plateforme utilisée.
Envoyer également les paramètres suivants :
Nom | Type | Description |
---|---|---|
client_id | string | L'identifiant unique reçu pendant l'enregistrement du client. |
response_type | string | Le type de réponse envoyée par le serveur. Doit utiliser la valeur "code" (sans guillements) pour ce mécanisme. |
redirect_uri | string | L'URI (ou l'URL) fourni pendant l'enregistrement du client et vers lequel l'utilisateur est redirigé après la demande d'autorisation. |
scope | string | Liste des droits demandés par le client, séparés par des espaces. |
state | string | Chaîne de caractère aléatoire utilisée pour éviter les attaques CSRF. Fortement recommandé. |
code_challenge | string | Code nécessaire pour le fonctionnement de l'extension PKCE. |
code_challenge_method | string | Méthode utilisée pour générer le code_challenge. Ici, la valeur est "S256". |
Demande de jeton d'accès
Après avoir répondu à la demande, l'utilisateur est redirigé vers l'URI fourni pendant l'enregistrement du client. Si la demande est acceptée, un code temporaire : code est fourni, ainsi que le paramètre state fourni pendant la demande avec la même valeur. Si la demande est refusée, un code d'erreur est renvoyé.
- Si le paramètre state a une valeur différente de celle envoyée avec la demande, c'est peut-être une tentative d'attaque et il faut refuser la réponse.
Echanger ce code contre un jeton d'accès via l'URL : POST https://openflyers.com/nom-de-plateforme/oauth/access_token.php
. Remplacer nom-de-plateforme
par le nom de la plateforme utilisée.
Les paramètres suivants sont également nécessaires :
Nom | Type | Description |
---|---|---|
client_id | string | L'identifiant unique reçu pendant l'enregistrement du client. |
client_secret | string | La passphrase reçue pendant l'enregistrement du client. |
code | string | Le code temporaire reçu dans la réponse à la demande d'autorisation. |
grant_type | string | Le mécanisme d'autorisation utilisé. Ici, sa valeur doit être authorization_code |
redirect_uri | string | L'URI (ou l'URL) de redirection fourni pendant l'enregistrement du client. |
code_verifier | string | Le code_verifier utilisé pour générer le code_challenge de la demande d'autorisation. |
Si la requête est correcte, un jeton d'accès (Access Token) ainsi qu'un jeton de rafraîchissement (Refresh Token) sont fournis en réponse dans un objet au format JSON. <javascript>{
"token_type": "Bearer", "expires_in": 3600, "access_token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJSU-G7fOBOYzOgioSGNIQKI2A2OxjsNBbOOoaHsqNpsQxWZse3rofExAaGKh2tXbeuz1YAVhdLUGYgq-oKRK4ONFhw2NvcRf3QPxQXZImLWw", "refresh_token": "a59ef39fa9bab9b95cd554f921e7f3080a34c90f23d2b8031d692b5ff2d0993dcc392d9c7f9a43242337ef144c1a5fe1d0174413ade973e1b628ac0bbfc39b23973534"
}</javascript>
Script client : Authorization Code
Voici un exemple simple de client OAuth2 pour le mécanisme d'authentification par code d'autorisation (Authorization Code).
Fichier de configuration config.authcode.json : <javascript>{
"client_id": "", "client_secret": "", "authorize_uri": "https://openflyers.com/nom-de-plateforme/oauth/authorize.php", "token_uri": "https://openflyers.com/nom-de-plateforme/oauth/access_token.php", "resource_uri": "https://openflyers.com/nom-de-plateforme/oauth/resources.php", "auth_cert": "/path/to/client/auth_cert.crt", "auth_key": "/path/to/client/auth.key", "sign_cert": "/path/to/client/sign_cert.crt", "sign_key": "/path/to/client/sign.key", "auth_cacert": "/path/to/ca.crt", "sign_cert_server": "/path/to/server/sign_cert_server.crt"
}</javascript>
Où /path/to/client/
est le chemin d'accès vers le dossier qui contient le code source du client de démonstration.
- Exemple sur windows:
C:/wamp64/www/4.0/oauth-demo/ssl/AuthCodeDemo/
. - Exemple sur un serveur Linux debian:
./ssl/AuthCodeDemo/
.
Ce fichier de configuration doit se situer au même niveau que le script PHP dans le système de fichiers.
Script PHP : <php><?php $localTokenFile = 'token.json'; // create token file if it does not exist if (!file_exists($localTokenFile))
file_put_contents($localTokenFile, );
$config = json_decode(file_get_contents('config.authcode.json'), true); $localToken = json_decode(file_get_contents($localTokenFile), true); $GLOBALS['config'] = $config;
// Build the baseURL, accounting for protocol, port, name and endpoint. Used for redirection $protocol = isset($_SERVER['HTTPS']) ? 'https://' : 'http://'; $port = ($protocol == 'https://' && $_SERVER['SERVER_PORT'] == 443)
|| ($protocol == 'http://' && $_SERVER['SERVER_PORT'] == 80) ? : ':' . $_SERVER['SERVER_PORT'];
$baseURL = $protocol . $_SERVER['SERVER_NAME'] . $port . $_SERVER['PHP_SELF']; $errorLog = 'error_log.log'; $responseLog = 'response_log.log'; $GLOBALS['baseURL'] = $baseURL;
//Session cookies are used to store information necessary for the authorization code flow session_start();
/**
* This function is used to make api calls to the Authorization Server and the Resource Server * * @param $url * @param $post * @param array $headers * * @return mixed */
function apiRequest($url, $post = null, $token = false, $auth = true, $refresh = false, $headers = array()) {
$ch = curl_init($url); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); curl_setopt($ch, CURLOPT_HEADER, true);
$method = 'get';
if ($post) { $method = 'post'; $postData = http_build_query($post); curl_setopt($ch, CURLOPT_POST, true); curl_setopt($ch, CURLOPT_POSTFIELDS, $postData);
$headersToSign['Content-Type'] = 'application/x-www-form-urlencoded'; $headersToSign['digest'] = 'SHA-256=' . base64_encode(hash('sha256', $postData, true)); }
$urlComponents = parse_url($url);
$headersToSign['(request-target)'] = $method . ' ' . $urlComponents['path']; $headersToSign['Host'] = $urlComponents['host']; $headersToSign['Date'] = gmdate('D, j M Y H:i:s T');
// generating the signature header $keyId = openssl_x509_fingerprint(file_get_contents($GLOBALS['config']['sign_cert'])); $privateKey = file_get_contents($GLOBALS['config']['sign_key']); $headers['Signature'] = generateSignatureHeader($headersToSign, $keyId, $privateKey);
$headers['Accept'] = 'application/json'; $headers['User-Agent'] = $GLOBALS['baseURL']; unset($headersToSign['(request-target)']); $headers += $headersToSign;
if ($token) { $headers['Authorization'] = $token['token_type'] . ' ' . $token['access_token']; }
// formatting the headers $httpFormattedHeaders = []; foreach ($headers as $key => $value) { $httpFormattedHeaders[] = trim($key) . ': ' . trim($value); }
curl_setopt($ch, CURLOPT_HTTPHEADER, $httpFormattedHeaders); curl_setopt($ch, CURLINFO_HEADER_OUT, true);
// for development environment only //curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); //curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false); //curl_setopt($ch, CURLOPT_VERBOSE, true);
// defining TLS client certificates for Mutual TLS curl_setopt($ch, CURLOPT_SSLVERSION, CURL_SSLVERSION_TLSv1_2); curl_setopt($ch, CURLOPT_CAINFO, $GLOBALS['config']['auth_cacert']); curl_setopt($ch, CURLOPT_SSLCERT, $GLOBALS['config']['auth_cert']); curl_setopt($ch, CURLOPT_SSLKEY, $GLOBALS['config']['auth_key']);
$response = curl_exec($ch);
// logging errors and responses errorLog(true, $response, $ch);
curl_close($ch);
// in case an authentication request is executed if ($auth) { // required when a refresh token request is issued // because there is only one header in the response // while there are two for regular auth requests if (!$refresh) { list($firstResponseHeaders, $secondResponseHeaders, $responseBody) = explode("\r\n\r\n", $response, 3); if (!$responseBody) { list($secondResponseHeaders, $responseBody) = explode("\r\n\r\n", $response, 2); } } else { list($secondResponseHeaders, $responseBody) = explode("\r\n\r\n", $response, 2); }
$responseHeadersDigest = ; $responseHeadersSignature = ; // extracting digest and signature from headers $responseHeadersArray = explode("\r\n", $secondResponseHeaders); $responseProtocol = array_shift($responseHeadersArray);
foreach ($responseHeadersArray as $value) { list($responseHeadersKeys, $responseHeadersValue) = explode(": ", $value, 2); $responseHeaders[strtolower($responseHeadersKeys)] = $responseHeadersValue; }
$responseDigestAlgo = ; $responseDigestKey = ; foreach ($responseHeaders as $key => $value) { if ($key === "digest") { $responseHeadersDigest = $value; // stripping SHA algorithm for later comparison list($responseDigestAlgo, $responseDigestKey) = explode("=", $responseHeadersDigest, 2); } else if ($key === "signature") $responseHeadersSignature = $value; }
// calculating response digest $responseDigest = base64_encode(hash(strtolower(str_replace("-", "", $responseDigestAlgo)), $responseBody, true));
// checking digest validity if ($responseDigest !== $responseDigestKey) { errorLog(false, false, "Digests are not the same"); die(); }
// extracting variables from signature header $signatureArray = explode(",", $responseHeadersSignature); foreach ($signatureArray as $value) { list($signatureKey, $signatureValue) = explode("=", $value, 2); $signature[$signatureKey] = trim($signatureValue, '"'); }
// generating siging string $signingStringArray = explode(" ", $signature['headers']); $signingString = ; foreach ($signingStringArray as $value) { $signingString = $signingString . trim($value) . ": " . trim($responseHeaders[$value]) . "\n"; }
// trimming last '\n' character $signingString = trim($signingString);
// decoding signature $decodedSignature = base64_decode($signature['signature']);
// verifying signature $signatureVerify = openssl_verify($signingString, $decodedSignature, openssl_get_publickey(file_get_contents($GLOBALS['config']['sign_cert_server'])), 'RSA-SHA256');
if (!$signatureVerify) { errorLog(false, false, "Signature is not correct"); while (($err = openssl_error_string())) errorLog(false, false, $err); die(); }
return json_decode($responseBody, true); }
return $response;
}
/**
* This function logs curl responses and errors in text files * * @param mixed $response the response * @param mixed $request the request handle, used to get errors */
function errorLog($curl, $response, $request = false) {
$timestamp = '[' . date('Y-m-d H:i:s') . ']:'; global $errorLog; global $responseLog;
if ($response === false) { if ($curl) file_put_contents($errorLog, $timestamp . curl_error($request) . "\n", FILE_APPEND); else file_put_contents($errorLog, $timestamp . $request . "\n", FILE_APPEND); } else { file_put_contents($responseLog, $timestamp . "\n" . $response . "\n", FILE_APPEND); }
}
/**
* This function generates the full signature header line from a set of headers, a certificate and the linked private key * * @param array $headersToSign the headers to sign, in the $key => $value format * @param string $certificate the certificate linked to the used private key * @param string $privateKey the private key used to sign the headers * * @return string the full signature header line */
function generateSignatureHeader(array $headersToSign, string $certificate, string $privateKey): string {
// generating the signing string and header list $headers = ; $signatureString = ; foreach ($headersToSign as $key => $value) { $normalizedHeaderKey = trim(strtolower($key)); $headers .= $normalizedHeaderKey . ' '; $signatureString .= $normalizedHeaderKey . ': ' . trim($value) . "\n"; } $headers = trim($headers); $signatureString = trim($signatureString);
// signing the signing string $signature = ; openssl_sign($signatureString, $signature, openssl_get_privatekey($privateKey), 'RSA-SHA256'); $signature = base64_encode($signature);
// compiling the header line return "keyId=\"$certificate\",algorithm=\"rsa-sha256\",headers=\"$headers\",signature=\"$signature\"";
}
/**
* This function takes a code_verifier and outputs the corresponding code_challenge * * @param string $codeVerifier the generated code_verifier * * @return string the computed code_challenge */
function computeCodeChallenge(string $codeVerifier): string {
return strtr(rtrim(base64_encode(hash('sha256', $codeVerifier, true)), '='), '+/', '-_');
}
/**
* This function takes an optional string length and outputs a random code_verifier string * * @param int $length the length of the output code_verifier. Default = 128 * * @return string the code_verifier * @throws Exception */
function generateCodeVerifier($length = 128): string {
$characters = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-._~'; $outputCode = ;
for ($i = 0; $i < $length; $i++) { $index = random_int(0, strlen($characters) - 1); $outputCode .= $characters[$index]; }
return $outputCode;
}
/**
* This function calculates the entropy of a given string * * @param string $string the string for which to calculate the entropy * @param string $charset a string with all the usable characters. Default = 0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-._~ * * @return float|int */
function computeEntropy(string $string, string $charset = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-._~') {
$chars = str_split($charset); $probs = [];
foreach ($chars as $char) { $probs[$char] = floatval(substr_count($string, $char)) / strlen($string); }
$sum = 0.0; foreach ($probs as $prob) { $sum += $prob != 0 ? $prob * log($prob, 2) : 0.0; }
return -$sum;
}
/**
* This function calculates the ideal (maximum) entropy for a string of a given length * * @param int $length length of the string. Default = 128 * * @return float|int */
function idealEntropy(int $length = 128) {
$prob = 1.0 / $length;
return -1.0 * $length * $prob * log($prob, 2);
}
/**
* This function is used to initiate the authentication code flow. * * @param string $clientID the client's ID * @param string $redirectURL the URL where to redirect after auth * @param string $authorizeURL the target URL to request authorization * * @throws Exception */
function login(string $clientID, string $redirectURL, string $authorizeURL): void {
global $localTokenFile; file_put_contents($localTokenFile, );
// This unguessable string is used to prevent csrf attacks $_SESSION['state'] = bin2hex(random_bytes(16));
// Generate code_verifier and code_challenge for PKCE $_SESSION['code_verifier'] = generateCodeVerifier(); $_SESSION['code_challenge'] = computeCodeChallenge($_SESSION['code_verifier']);
// required parameters for the redirection, redirect_uri is where the browser should be redirected // when the user grants (or denies) access, scope are the authorizations (rights) requested $params = [ 'response_type' => 'code', 'client_id' => $clientID, 'redirect_uri' => $redirectURL, 'scope' => 'default.login', 'state' => $_SESSION['state'], 'code_challenge' => $_SESSION['code_challenge'], 'code_challenge_method' => 'S256' ];
// redirecting the browser to the AS authorization endpoint to obtain the authorization code header('Location: ' . $authorizeURL . '?' . http_build_query($params));
}
/**
* This function deletes the access token from the session * * @param string $baseURL */
function logout(string $baseURL): void {
global $localTokenFile; file_put_contents($localTokenFile, );
header('Location: ' . $baseURL);
}
function displayMenuLoggedIn(): void {
echo '
<a href="/">Home</a>
'; echo '
Logged In
';
echo '<form id="view_repos" method="post">'; echo '<input type="hidden" id="action" name="action" value="view">';
echo '
<a href="javascript:{}" onclick="document.getElementById(\'view_repos\').submit(); return false;">View Repos</a>
';
echo '</form>';
echo '<form id="logout" method="post">'; echo '<input type="hidden" id="action" name="action" value="logout">';
echo '
<a href="javascript:{}" onclick="document.getElementById(\'logout\').submit(); return false;">Log Out</a>
';
echo '</form>';
}
function displayMenuLoggedOut(): void {
echo '
<a href="/">Home</a>
'; echo '
Not logged in
';
echo '<form id="login" method="post">'; echo '<input type="hidden" id="action" name="action" value="login">';
echo '
<a href="javascript:{}" onclick="document.getElementById(\'login\').submit(); return false;">Log In</a>
';
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 '
'; echo $resourceBody; echo '
';
// 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 : POST https://openflyers.com/nom-de-plateforme/oauth/access_token.php
. Remplacer nom-de-plateforme
par le nom de la plateforme utilisée.
Les paramètres suivants sont nécessaires :
Nom | Type | Description |
---|---|---|
client_id | string | L'identifiant unique reçu pendant l'enregistrement du client. |
client_secret | string | La passphrase reçue pendant l'enregistrement du client. |
grant_type | string | Le mécanisme d'autorisation utilisé. Ici, la valeur doit être client_credentials. |
scope | string | Liste des droits demandés par le client, séparé par des espaces. |
- Ce mécanisme a la particularité de ne pas nécessiter d'URI de redirection, il n'est donc pas utile d'en renseigner un lors de l'enregistrement d'un client.
Si la requête est correcte, un jeton d'accès (Access Token) est fourni en réponse dans un objet au format JSON. <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ù /path/to/client/
est le chemin d'accès vers le dossier qui contient le code source du client de démonstration.
- Exemple sur windows:
C:/wamp64/www/4.0/oauth-demo/ssl/ClientCredDemo/
. - Exemple sur un serveur Linux debian:
./ssl/ClientCredDemo/
.
Ce fichier de configuration doit se situer au même niveau que le script PHP dans le système de fichiers.
Script php : <php><?php $localTokenFile = 'token.json'; // create token file if it does not exist if (!file_exists($localTokenFile))
file_put_contents($localTokenFile, );
$config = json_decode(file_get_contents('config.clientcred.json'), true); $localToken = json_decode(file_get_contents($localTokenFile), true); $GLOBALS['config'] = $config;
// Build the baseURL, accounting for protocol, port, name and endpoint. Used for redirection $protocol = isset($_SERVER['HTTPS']) ? 'https://' : 'http://'; $port = ($protocol == 'https://' && $_SERVER['SERVER_PORT'] == 443)
|| ($protocol == 'http://' && $_SERVER['SERVER_PORT'] == 80) ? : ':' . $_SERVER['SERVER_PORT'];
$baseURL = $protocol . $_SERVER['SERVER_NAME'] . $port . $_SERVER['PHP_SELF']; $errorLog = 'error_log.log'; $responseLog = 'response_log.log'; $GLOBALS['baseURL'] = $baseURL;
$replacementListItems = [
1 => "year", 2 => "validityTypeId", 3 => "icao", 4 => "profileId", 7 => "accountingId", 8 => "paymentType", 9 => "startDate", 10 => "endDate", 11 => "occupiedSeat", 12 => "date", 13 => "activityTypeId", 14 => "age", 15 => "resourceId", 16 => "personId", 17 => "accountId", 20 => "rightPlacePersonId", 21 => "month", 22 => "numberMonth", 23 => "oneValidityTypeId",
];
//Session cookies are used to store information necessary for the authorization code flow session_start();
/**
* This function is used to make api calls to the RS * * @param $url * @param $post * @param array $headers * * @return mixed */
function apiRequest($url, $post = null, $token = false, $auth = true, $headers = array()) {
$ch = curl_init($url); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); curl_setopt($ch, CURLOPT_HEADER, true);
$method = 'get';
if ($post) { $method = 'post'; $postData = http_build_query($post); curl_setopt($ch, CURLOPT_POST, true); curl_setopt($ch, CURLOPT_POSTFIELDS, $postData);
$headersToSign['Content-Type'] = 'application/x-www-form-urlencoded'; $headersToSign['digest'] = 'SHA-256=' . base64_encode(hash('sha256', $postData, true)); }
$urlComponents = parse_url($url);
$headersToSign['(request-target)'] = $method . ' ' . $urlComponents['path']; $headersToSign['Host'] = $urlComponents['host']; $headersToSign['Date'] = gmdate('D, j M Y H:i:s T');
// generating the signature header $keyId = openssl_x509_fingerprint(file_get_contents($GLOBALS['config']['sign_cert'])); $privateKey = file_get_contents($GLOBALS['config']['sign_key']); $headers['Signature'] = generateSignatureHeader($headersToSign, $keyId, $privateKey);
$headers['Accept'] = 'application/json'; $headers['User-Agent'] = $GLOBALS['baseURL']; unset($headersToSign['(request-target)']); $headers += $headersToSign;
if ($token) { $headers['Authorization'] = $token['token_type'] . ' ' . $token['access_token']; }
// formatting the headers $httpFormattedHeaders = []; foreach ($headers as $key => $value) { $httpFormattedHeaders[] = trim($key) . ': ' . trim($value); }
curl_setopt($ch, CURLOPT_HTTPHEADER, $httpFormattedHeaders); curl_setopt($ch, CURLINFO_HEADER_OUT, true);
// for development environment only //curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); //curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false); //curl_setopt($ch, CURLOPT_VERBOSE, true);
// defining TLS client certificates for Mutual TLS curl_setopt($ch, CURLOPT_SSLVERSION, CURL_SSLVERSION_TLSv1_2); curl_setopt($ch, CURLOPT_CAINFO, $GLOBALS['config']['auth_cacert']); curl_setopt($ch, CURLOPT_SSLCERT, $GLOBALS['config']['auth_cert']); curl_setopt($ch, CURLOPT_SSLKEY, $GLOBALS['config']['auth_key']);
$response = curl_exec($ch);
// logging errors and responses errorLog(true, $response, $ch);
curl_close($ch);
// in case an authentication request is executed if ($auth) { list($responseHeader, $responseBody) = explode("\r\n\r\n", $response, 2);
$responseHeadersDigest = ; $responseHeadersSignature = ; // extracting digest and signature from headers $responseHeadersArray = explode("\r\n", $responseHeader); $responseProtocol = array_shift($responseHeadersArray);
foreach ($responseHeadersArray as $value) { list($responseHeadersKeys, $responseHeadersValue) = explode(": ", $value, 2); $responseHeaders[strtolower($responseHeadersKeys)] = $responseHeadersValue; }
$responseDigestAlgo = ; $responseDigestKey = ; foreach ($responseHeaders as $key => $value) { if ($key === "digest") { $responseHeadersDigest = $value; // stripping SHA algorithm for later comparison list($responseDigestAlgo, $responseDigestKey) = explode("=", $responseHeadersDigest, 2); } else if ($key === "signature") $responseHeadersSignature = $value; }
// calculating response digest $responseDigest = base64_encode(hash(strtolower(str_replace("-", "", $responseDigestAlgo)), $responseBody, true));
// checking digest validity if ($responseDigest !== $responseDigestKey) { errorLog(false, false, "Digests are not the same"); die(); }
// extracting variables from signature header $signatureArray = explode(",", $responseHeadersSignature); foreach ($signatureArray as $value) { list($signatureKey, $signatureValue) = explode("=", $value, 2); $signature[$signatureKey] = trim($signatureValue, '"'); }
// generating siging string $signingStringArray = explode(" ", $signature['headers']); $signingString = ; foreach ($signingStringArray as $value) { $signingString = $signingString . trim($value) . ": " . trim($responseHeaders[$value]) . "\n"; }
// trimming last '\n' character $signingString = trim($signingString);
// decoding signature $decodedSignature = base64_decode($signature['signature']);
// verifying signature $signatureVerify = openssl_verify($signingString, $decodedSignature, openssl_get_publickey(file_get_contents($GLOBALS['config']['sign_cert_server'])), 'RSA-SHA256');
if (!$signatureVerify) { errorLog(false, false, "Signature is not correct"); while (($err = openssl_error_string())) errorLog(false, false, $err); die(); }
return json_decode($responseBody, true); }
return $response;
}
/**
* This function logs curl responses and errors in text files * * @param mixed $response the response * @param mixed $request the request handle, used to get errors */
function errorLog($curl, $response, $request = false) {
$timestamp = '[' . date('Y-m-d H:i:s') . ']:'; global $errorLog; global $responseLog;
if ($response === false) { if ($curl) file_put_contents($errorLog, $timestamp . curl_error($request) . "\n", FILE_APPEND); else file_put_contents($errorLog, $timestamp . $request . "\n", FILE_APPEND); } else { file_put_contents($responseLog, $timestamp . "\n" . $response . "\n", FILE_APPEND); }
}
/**
* This function generates the full signature header line from a set of headers, a certificate and the linked private key * * @param array $headersToSign the headers to sign, in the $key => $value format * @param string $certificate the certificate linked to the used private key * @param string $privateKey the private key used to sign the headers * * @return string the full signature header line */
function generateSignatureHeader(array $headersToSign, string $certificate, string $privateKey): string {
// generating the signing string and header list $headers = ; $signatureString = ; foreach ($headersToSign as $key => $value) { $normalizedHeaderKey = trim(strtolower($key)); $headers .= $normalizedHeaderKey . ' '; $signatureString .= $normalizedHeaderKey . ': ' . trim($value) . "\n"; } $headers = trim($headers); $signatureString = trim($signatureString);
// signing the signing string $signature = ; openssl_sign($signatureString, $signature, openssl_get_privatekey($privateKey), 'RSA-SHA256'); $signature = base64_encode($signature);
// compiling the header line return "keyId=\"$certificate\",algorithm=\"rsa-sha256\",headers=\"$headers\",signature=\"$signature\"";
}
/**
* This function deletes the access token from the session * * @param string $baseURL */
function logout(string $baseURL): void {
global $localTokenFile; file_put_contents($localTokenFile, );
header('Location: ' . $baseURL);
}
function displayMenuLoggedIn(): void {
global $replacementListItems;
echo '
<a href="/">Home</a>
'; echo '
Logged In
';
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">
'; foreach ($replacementListItems as $value) { echo '<label for="' . $value . '">' . $value . ': </label>'; echo '<input type="text" id="' . $value . '" name="' . $value . '">
'; } echo '<input type="hidden" id="action" name="action" value="view">';
echo '
<a href="javascript:{}" onclick="document.getElementById(\'view_repos\').submit(); return false;">View Repos</a>
';
echo '</form>';
echo '<form id="logout" method="post">'; echo '<input type="hidden" id="action" name="action" value="logout">';
echo '
<a href="javascript:{}" onclick="document.getElementById(\'logout\').submit(); return false;">Log Out</a>
';
echo '</form>';
}
function displayMenuLoggedOut(): void {
echo '
<a href="/">Home</a>
'; echo '
Not logged in
';
echo '<form id="login" method="post">'; echo '<input type="hidden" id="action" name="action" value="login">';
echo '
<a href="javascript:{}" onclick="document.getElementById(\'login\').submit(); return false;">Log In</a>
';
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 '
'; echo (strpos($firstHeaderLine, "200")) ? json_decode($resourceBody) : $resourceBody; echo '
';
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. Lorsqu'un jeton d'accès arrive est arrivé en fin de vie, si le client y est autorisé, il peut faire une demande de renouvellement de son jeton d'accès auprès du serveur d'autorisation en présentant son jeton de rafraîchissement. Le serveur d'autorisation vérifie la validité et génère un autre jeton d'accès qu'il transmet au client, sans que l'utilisateur final n'aît à se connecter de nouveau.
Le principe de fonctionnement du rafraîchissement d'un jeton est le suivant :
- Le jeton d'accès du client est arrivé à péremption. Le client effectue une demande de renouvellement en transmettant son Refresh Token au serveur d'autorisation.
- Le serveur d'autorisation vérifie la validité des informations, "consomme" le jeton de rafraîchissement et génère une nouvelle paire de jetons
- Le client reçoit sa nouvelle paire et utilise le nouveau jeton d'accès pour accéder aux ressources
+------+ +-------------+ +-----------+ |Client| | Serveur | | Serveur | +--+---+ |Autorisation | | Ressources| | +------+------+ +-----+-----+ | Authentification | | | + Refresh Token | | +--------------------------------->| | | | | | Access (+ Refresh) token | | |<---------------------------------+ | | | | | Requête vers|API | +----------------------------------+------------------------------>| | | | | | | | |<------------------------------+ | | Vérification d'autorisation | | +------------------------------>| | | | | Données /|Réponse | |<---------------------------------+-------------------------------+ | | |
Demande de renouvellement d'un jeton
Pour obtenir un renouvellement de jeton d'accès, il faut effectuer la requête suivante : POST https://openflyers.com/nom-de-plateforme/oauth/access_token.php
. Remplacer nom-de-plateforme
par le nom de la plateforme utilisée.
Les paramètres suivants sont nécessaires :
Nom | Type | Description |
---|---|---|
client_id | string | L'identifiant client_id reçu pendant l'enregistrement du client. |
client_secret | string | La passphrase client_secret reçue pendant l'enregistrement du client. |
grant_type | string | Le mécanisme d'autorisation utilisé. Ici, la valeur doit être "refresh_token" (sans guillements). |
scope | string | (OPTIONNEL) Liste des droits demandés par le client, séparés par des espaces. |
Si la requête est correcte, un jeton d'accès access_token est fourni en réponse dans un objet au format JSON. <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 :
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. |
Révocation de token
Pour initier la demande de révocation, rediriger le navigateur de l'utilisateur vers l'URL : POST https://openflyers.com/nom-de-plateforme/oauth/revoke.php
. Remplacer nom-de-plateforme
par le nom de la plateforme utilisée.
Envoyer également le paramètre suivant:
Nom | Type | Description |
---|---|---|
access_token | string | Le jeton d'accès (Chaîne de caractère unique permettant de prouver qu'un client OAuth a le droit d'accéder à une ressource.) |
<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 : https://openflyers.com/nom-de-plateforme/oauth/resources.php
. Remplacer nom-de-plateforme
par le nom de la plateforme utilisée. La requête POST doit respecter une structure particulière. Cette structure diffère en fonction du type de ressource souhaité et chaque ressource ne peut être accédée qu'avec le scope qui lui est associé. Le jeton d'accès doit être renseigné dans l'en-tête HTTP Authorization en y indiquant la valeur complète du jeton d'accès reçu précédé du type de jeton reçu : Authorization: <token_type> <access_token>
.
Récupérer les informations de l'utilisateur connecté
Ce type de ressource est nommé user_information. Cette ressource n'est accessible qu'avec le scope default.login. Cette ressource permet la récupération des informations de l'utilisateur connecté, en d'autre termes, l'utilisateur connecté lors de l'étape d'autorisation et correspondant à celui ayant émis la demande de jeton d'accès. À l'heure actuelle, seul l'identifiant de l'utilisateur connecté est retourné. La requête POST est construite de la manière suivante :
Nom | Type | Description |
---|---|---|
resource_type | string | Le type de ressource demandé, ici, ce champ correspond à user_information. |
client_id | string | L'identifiant unique reçu pendant l'enregistrement du client. |
Un exemple de requête complète au format JSON : <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 :
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 :
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.
Troubleshooting
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.