Jump to content

OAuth/Consommateurs exclusifs

From mediawiki.org
This page is a translated version of the page OAuth/Owner-only consumers and the translation is 100% complete.

Les consommateurs propriétaires uniquement est une méthode d'utilisation de OAuth pour l'authentification et le contrôle des droits qui évite la complexité du protocole OAuth (processus d'attribution de l'autorisation). Il est destiné aux robots et aux outils similaires qui s'authentifient toujours avec le même compte utilisateur. Pour l'utiliser, le wiki cible doit avoir installé la version 1.27 ou supérieure de Extension:OAuth .

Il existe deux versions du protocole OAuth qui sont tout à fait différentes : OAuth 1 et OAuth 2. Si vous ne savez pas laquelle utiliser, choisissez OAuth 2.

OAuth 2

Utiliser une application avec toujours le même compte est très simple dans OAuth 2. Il suffit simplement d'enregistrer l'application via Special:OAuthConsumerRegistration/propose/oauth2 en cochant l'option owner-only.

(Dans le cas d'une ferme de wikis, la page spéciale est disponible uniquement sur le wiki central de la ferme. Dans le cas de Wikimedia, cette page est meta:Special:OAuthConsumerRegistration/propose/oauth2.)

Puis enregistrez le jeton d'accès affiché quand vous avez soumis le formulaire et signez vos requêtes à l'API en ajoutant l'entête HTTP Authorization: Bearer <jeton d'accès>.

OAuth 1

Pour fonctionner en tant que consommateur exclusif, l'application utilise quatre chaînes comme paramètres de configuration : la clé du consommateur, le code secret du consommateur, le jeton d'accès et le code secret d'accès.

L'utilisateur peut obtenir ces valeurs via Special:OAuthConsumerRegistration/propose.

L'option owner-only doit être cochée.

(Dans le cas d'une ferme de wikis, la page spéciale est disponible uniquement sur le wiki central de la ferme. Dans le cas de Wikimedia, cette page est meta:Special:OAuthConsumerRegistration/propose.)

L'application peut alors authentifier les demandes à l'API en ajoutant une entête Authorization calculée à partir de ces paramètres tels que définis dans la norme OAuth 1.0a; des bibliothèques existent dans de nombreuses langues pour aider à cela.

Certaines bibliothèques appellent cela le protocole OAuth 1.0 à deux jambes.

La bible OAuth l'appelle plus correctement à une jambe puisqu'il n'y a qu'un pas.

Certaines sources appellent la clé du consommateur client ID, le code secret du comsommateur client secret, le jeton d'accès simplement token, et le code d'accès token secret.

Les extraits de code ci-dessous supposent que l'application utilise un code secret partagé (HMAC-SHA1) pour la signature (c'est-à-dire que le champ RSA était vide lors de l'enregistrement).

PHP

En utilisant Oauthclient-php  :

use MediaWiki\OAuthClient\Consumer;
use MediaWiki\OAuthClient\Token;
use MediaWiki\OAuthClient\Request;
use MediaWiki\OAuthClient\SignatureMethod\HmacSha1;

$consumer = new Consumer( $consumerKey, $consumerSecret );
$accessToken = new Token( $accessToken, $accessSecret );
$request = Request::fromConsumerAndToken( $consumer, $accessToken, 'GET', 'https://en.wikipedia.org/w/api.php', $apiParams );
$request->signRequest( new HmacSha1(), $consumer, $accessToken );
$authorizationHeader = $request->toHeader();
// puis utiliser cette entête dans une requête, par exemple :
$ch = curl_init();
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_URL => 'https://en.wikipedia.org/w/api.php',
    CURLOPT_USERAGENT => 'My_bot',  
    CURLOPT_POST => true,
    CURLOPT_POSTFIELDS => http_build_query($apiParams),
    CURLOPT_HTTPHEADER => [$authenticationHeader],
]);
$result = curl_exec($ch);

En utilisant le package PECL :

$oauth = new OAuth( $consumerKey, $consumerSecret );
$oauth->setToken( $accessToken, $accessSecret );
// générer une entête envoyée peut-être par un objet CURL :
$authorizationHeader = 'Authorization: ' . $oauth->getRequestHeader( 'GET', 'https://en.wikipedia.org/w/api.php', $apiParams );
// ou accéder une page directement :
$oauth->fetch('https://en.wikipedia.org/w/api.php', $apiParams);

Python

En utilisant requests_oauthlib :

import requests
from requests_oauthlib import OAuth1

auth = OAuth1(consumer_key, consumer_secret, access_token, access_secret)
requests.post(url='https://en.wikipedia.org/w/api.php', data=data, auth=auth)

Perl

En utilisant Net::OAuth :

use Net::OAuth;

my $request = Net::OAuth->request( 'protected resource' )->new(
    request_method => 'POST',
    request_url => 'https://en.wikipedia.org/w/api.php',
    consumer_key => $consumer_key,
    consumer_secret => $consumer_secret,
    token => $access_token,
    token_secret => $access_secret,
    signature_method => 'HMAC-SHA1',
    timestamp => time(),
    nonce => $nonce,

    # seulement si GET ou POST sont utilisés comme application/x-www-form-urlencoded,
    # omettre extra_params si POST utilisé avec multipart/form-data.
    extra_params => \%params, 
);
$request->sign();
my $authorizationHeader = $request->to_authorization_header();

Pour générer nonce (utilisé pour rendre la requête unique), vous pouvez faire int( rand( 2**32 ) ), mais l'utilisation d'un générateur de nombres aléatoires tel que Bytes::Random::Secure est plus sécurisée :

use Bytes::Random::Secure ();

# Cet objet peut être conservé et réutilisé pendant la durée du programme
my $rand = Bytes::Random::Secure->new( NonBlocking => 1 );

# ceci génère le nonce, et remplace certains caractères par des caractères plus sûrs.
my $nonce = $rand->bytes_base64( 15 );
$nonce =~ tr!+/\n!-_!;

Awk / shell

En utilisant GNU Awk et openssl. Bibliothèque de fonctions dans Wikiget.

#
# MWOAuthGenerateHeader() - MediaWiki Generate OAuth Header
#
#   Credit: translation of PHP script https://www.mediawiki.org/wiki/OAuth/Owner-only_consumers#Algorithm
#          
function MWOAuthGenerateHeader(consumerKey, consumerSecret, accessKey, accessSecret, url, method, data,  

                               nonce,headerParams,dataArr,allParams,allParamsJoined,k,i,j,url2,
                               signatureBaseParts,signatureBaseString,hmac,header,save_sorted) {

  # trier les tableaux associatifs par chaîne d'index ascendante (dans l'ordre lexicographique)
    PROCINFO["sorted_in"] = "@ind_str_asc"

    nonce = strip(splitx(sys2varPipe(systime() randomnumber(1000000), "openssl md5"), "= ", 2))

    asplit(headerParams, "oauth_consumer_key=" consumerKey " oauth_token=" accessKey " oauth_signature_method=HMAC-SHA1 oauth_timestamp=" systime() " oauth_nonce=" nonce " oauth_version=1.0") 
    asplit(dataArr, data, "=", "&")
    concatarray(headerParams,dataArr,allParams)
    for (k in allParams) 
        allParamsJoined[i++] = k "=" allParams[k]

    url2 = urlElement(url, "scheme") "://" tolower(urlElement(url, "netloc")) urlElement(url, "path")
    asplit(signatureBaseParts, "0=" toupper(method) " 1=" url " 2=" join(allParamsJoined, 0, length(allParamsJoined) - 1, "&"))
    signatureBaseString = urlencodeawk(signatureBaseParts[0], "rawphp") "&" urlencodeawk(signatureBaseParts[1], "rawphp") "&" urlencodeawk(signatureBaseParts[2], "rawphp")
    hmac = sys2varPipe(signatureBaseString, "openssl sha1 -hmac " shquote(urlencodeawk(consumerSecret, "rawphp") "&" urlencodeawk(accessSecret, "rawphp")) " -binary")
    headerParams["oauth_signature"] = strip(sys2varPipe(hmac, "openssl base64") )

    for (k in headerParams) 
        header[j++] = urlencodeawk(k, "rawphp") "=" urlencodeawk(headerParams[k], "rawphp")

    return sprintf("%s", "Authorization: OAuth " join(header, 0, length(header) - 1, ", "))
}

Java

En utilisant ScribeJava :

import java.io.*;
import java.net.*;
import java.nio.charset.*;
import java.util.*;

import com.github.scribejava.apis.MediaWikiApi;
import com.github.scribejava.core.builder.*;
import com.github.scribejava.core.model.*;
import com.github.scribejava.core.oauth.*;

public class MediaWikiOAuth1 {

    private URL apiUrl = new URL("https://commons.wikimedia.org/w/api.php");

    private String consumerToken = "";
    private String consumerSecret = "";
    private String accessToken = "";
    private String accessSecret = "";
    private String userAgent = ""; // https://meta.wikimedia.org/wiki/User-Agent_policy

    private OAuth1AccessToken oAuthAccessToken;
    private OAuth10aService oAuthService;
    private String csrfToken;

    public MediaWikiOAuth1() throws IOException {
        oAuthService = new ServiceBuilder(consumerToken).apiSecret(consumerSecret).build(MediaWikiApi.instance());
        oAuthAccessToken = new OAuth1AccessToken(accessToken, accessSecret);

        // vérifier l'authentification
        System.out.println(queryUserInfo());
        // récupérer le jeton CSRF, obligatoire pour le téléversement à l'aide de l'API MediaWiki
        System.out.println(queryTokens());
        csrfToken = ""; // doit être extrait de la réponse JSON ci-dessus
        // téléverser un fichier à l'aide du jeton CSRF
        System.out.println(upload("wikicode", "filename.jpg", new URL("http://server/filename.jpg")));
    }

    public String queryTokens() throws IOException {
        return apiHttpGet("?action=query&meta=tokens");
    }

    public String queryUserInfo() throws IOException {
        return apiHttpGet("?action=query&meta=userinfo&uiprop=blockinfo|groups|rights|ratelimits");
    }

    private String apiHttpGet(String path) throws IOException {
        return httpGet(apiUrl.toExternalForm() + path + "&format=json");
    }

    private String apiHttpPost(Map<String, String> params) throws IOException {
        return httpPost(apiUrl.toExternalForm(), params);
    }

    private String httpGet(String url) throws IOException {
        return httpCall(Verb.GET, url, Collections.emptyMap(), Collections.emptyMap());
    }

    private String httpPost(String url, Map<String, String> params) throws IOException {
        return httpCall(Verb.POST, url, Map.of("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8"),
                params);
    }

    private String httpCall(Verb verb, String url, Map<String, String> headers, Map<String, String> params)
            throws IOException {
        OAuthRequest request = new OAuthRequest(verb, url);
        request.setCharset(StandardCharsets.UTF_8.name());
        params.forEach(request::addParameter);
        headers.forEach(request::addHeader);
        request.addHeader("User-Agent", userAgent);
        oAuthService.signRequest(oAuthAccessToken, request);
        try {
            return oAuthService.execute(request).getBody();
        } catch (InterruptedException | ExecutionException e) {
            throw new IOException(e);
        }
    }

    public String upload(String wikiCode, String filename, URL url) throws IOException {
        String apiResponse = apiHttpPost(Map.of(
                "action", "upload", 
                "comment", "", 
                "format", "json", 
                "filename", filename, 
                "ignorewarnings", "1", 
                "text", wikiCode, 
                "token", csrfToken, 
                "url", url.toExternalForm()));

        if (!apiResponse.contains("success")) {
            throw new IllegalArgumentException(apiResponse);
        }
        return apiResponse;
    }
}

Algorithme

 Authorization: OAuth oauth_consumer_key="<clé du consommateur>",
                      oauth_token="<jeton d'accès>",
                      oauth_signature_method="HMAC-SHA1",
                      oauth_signature="<base64(HMAC_SHA1(key="<code secret du consommateur>&<code secret du jeton>", text="<chaîne de base de signature>"))>",
                      oauth_timestamp="<heure actuelle en tant que horodatage Unix>",
                      oauth_nonce="<longue chaîne aléatoire>",
                      oauth_version="1.0"

où la chaîne de base de signature est : la liste concaténée avec des & de la méthode de requête, du point d'accès de la requête (l'URL complète vers api.php) et de tous les paramètres de la requête (GET, POST et Autorization, sauf oauth_signature lui-même) dans l'ordre lexicographique – l'ensemble encodé URL. Par exemple, le calcul de l'entête en PHP ressemblerait à ceci (en coupant certains morceaux tels que la manipulation des paramètres imbriqués) :

function oauthHeader( $consumerKey, $consumerSecret, $accessToken, $accessSecret, $method, $url, $data ) {
    $headerParams = [
        'oauth_consumer_key' => $consumerKey,
        'oauth_token' => $accessToken,
        'oauth_signature_method' => 'HMAC-SHA1',
        'oauth_timestamp' => time(),
        'oauth_nonce' => md5( microtime() . mt_rand() ),
        'oauth_version' => '1.0',
    ];

    $allParams = array_merge( $headerParams, $data );
    uksort( $allParams, 'strcmp' );
    $allParamsJoined = array();
    foreach ( $allParams as $key => $value ) {
        $allParamsJoined[] = rawurlencode( $key ) . '=' . rawurlencode( $value );
    }

    $urlParts = parse_url( $url );
    $url = $urlParts['scheme'] . '://' . strtolower( $urlParts['host'] ) . $urlParts['path'];

    $signatureBaseParts = [ strtoupper( $method ), $url, implode( '&', $allParamsJoined ) ];
    $signatureBaseString = implode( '&', array_map( 'rawurlencode', $signatureBaseParts ) );
    $headerParams['oauth_signature'] = base64_encode( hash_hmac( 'sha1', $signatureBaseString, rawurlencode( $consumerSecret ) . '&' . rawurlencode( $accessSecret ), true ) );

    $header = array();
    foreach ( $headerParams as $key => $value ) {
        $header[] = rawurlencode( $key ) . '=' . rawurlencode( $value );
    }
    return 'Authorization: OAuth ' . implode( ', ', $header );
}

Voir aussi