themattharris / tmhOAuth

An OAuth 1.0A library written in PHP
Apache License 2.0
855 stars 335 forks source link

GetAccessToken don't work with Google OAuth1 API (the token is double encoded) #106

Open hiteule opened 11 years ago

hiteule commented 11 years ago

Hi,

The getAccessToken don't work with the google oauth1 api. The api return this error : signature_invalid base_string:POST&https%3A%2F%2Fwww.google.com%2Faccounts%2FOAuthGetAccessToken&oauth_consumer_key%3D42424242.apps.googleusercontent.com%26oauth_nonce%3Daf0b971774829b8530ab20c5b21b3f97%26oauth_signature_method%3DHMAC-SHA1%26oauth_timestamp%3D1355148916%26oauth_token%3D4%252FKO4KYSdwSWvHOywPd7aY-8kWEwBl%26oauth_verifier%3DY5u4TXakuzCV7PySd62jVkzI%26oauth_version%3D1.0

The problem is located in the encoding of the token which is encoded two times. This problem occurs on the google oauth1 api because the token contains a "/".

You can reproduce this bug with this endpoint : https://www.google.com/accounts/OAuthGetRequestToken https://www.google.com/accounts/OAuthAuthorizeToken https://www.google.com/accounts/OAuthGetAccessToken

Bye

themattharris commented 11 years ago

Section 3.4.1.3.2 of the OAuth 1.0a specification (http://tools.ietf.org/html/rfc5849#section-3.4.1.3.2) states that the values should be encoded and then concatenated to form the base string.

also, looking at googles GData documentation shows the same %252F component in the oauth_token (ref: https://developers.google.com/gdata/articles/oauth#Signing)

can you share the exact error Google responds with when you use tmhOAuth to make requests?

hiteule commented 11 years ago

Hi,

The exact error of Google Api when I make the GetAccessToken request is :

signature_invalid base_string:POST&https%3A%2F%2Fwww.google.com%2Faccounts%2FOAuthGetAccessToken&oauth_consumer_key%3D885116161353.apps.googleusercontent.com%26oauth_nonce%3D3009d7b73288f87b253b167e811699fc%26oauth_signature_method%3DHMAC-SHA1%26oauth_timestamp%3D1361267168%26oauth_token%3D4%252FfzZ-jsCiqkuMKoRuRWwKV4tUPppY%26oauth_verifier%3D7HXQDLk4Rl4EMS6VsByS2Yo8%26oauth_version%3D1.0

And the token I use to make this request is :

4/fzZ-jsCiqkuMKoRuRWwKV4tUPppY

Here you can find a sample code to reproduce this issue :

<?php
session_start();

require_once('./tmhOAuth.php');

$tmh = new tmhOAuth(array(
    'consumer_key'    => '42.apps.googleusercontent.com',
    'consumer_secret' => 'xXXX/42YyYyYzZzZz',
));

if (isset($_GET['oauth_verifier'])) {
    $tmh->config['user_token']  = $_SESSION['tmh']['oauth_token'];
    $tmh->config['user_secret'] = $_SESSION['tmh']['oauth_token_secret'];

    // Here the token value is : 4/fzZ-jsCiqkuMKoRuRWwKV4tUPppY
    echo '<pre>';var_dump($tmh->config['user_token']);echo '</pre>';

    $code = $tmh->request(
        'POST',
        'https://www.google.com/accounts/OAuthGetAccessToken',
        array('oauth_verifier' => $_GET['oauth_verifier'])
    );

    // At this point the Google API respond an error like this :
    //
    // signature_invalid base_string:POST&https%3A%2F%2Fwww.google.com%2Faccounts%2FOAuthGetAccessToken
    // &oauth_consumer_key%3D885116161353.apps.googleusercontent.com%26oauth_nonce%3D3009d7b73288f87b253b167e811699fc%26
    // oauth_signature_method%3DHMAC-SHA1%26oauth_timestamp%3D1361267168%26oauth_token%3D4%252FfzZ-jsCiqkuMKoRuRWwKV4tUPppY%26
    // oauth_verifier%3D7HXQDLk4Rl4EMS6VsByS2Yo8%26oauth_version%3D1.0

    echo '<pre>';var_dump($tmh->response['response']);echo '</pre>';die;
} elseif (isset($_GET['authenticate'])) {
    $code = $tmh->request(
        'POST',
        'https://www.google.com/accounts/OAuthGetRequestToken',
        array(
            'oauth_callback' => 'http://' . $_SERVER['HTTP_HOST'] . $_SERVER['PHP_SELF'],
            'scope'          => 'http://www.google.com/m8/feeds/',
        )
    );

    if ($code == 200) {
        $_SESSION['tmh'] = $tmh->extract_params($tmh->response['response']);
        $url = 'https://www.google.com/accounts/OAuthAuthorizeToken?oauth_token=' . $_SESSION['tmh']['oauth_token'];
        header('Location: ' . $url);
    }
}

echo '<a href="?authenticate">GO</a>';

When I patch the tmhOAuth lib to encode the token one time, the api work perfectly. This is my quick evil patch :

--- a/trunk/include/skyobjects/external/tmhOAuth/tmhOAuth.php
+++ b/trunk/include/skyobjects/external/tmhOAuth/tmhOAuth.php
@@ -266,5 +266,5 @@
    * @return void prepared values are stored in class variables
    */
-  private function prepare_params($params) {
+  private function prepare_params($params, $encodeAccessToken = true) {
     // do not encode multipart parameters, leave them alone
     if ($this->config['multipart']) {
@@ -288,6 +288,8 @@
     // encode. Also sort the signed parameters from the POST parameters
     foreach ($this->signing_params as $k => $v) {
-      $k = $this->safe_encode($k);
-      $v = $this->safe_encode($v);
+      if ($k != 'oauth_token' || $encodeAccessToken == true) {
+        $k = $this->safe_encode($k);
+        $v = $this->safe_encode($v);
+      }
       $_signing_params[$k] = $v;
       $kv[] = "{$k}={$v}";
@@ -367,8 +369,8 @@
    * @param string $useauth whether to use authentication when making the request.
    */
-  private function sign($method, $url, $params, $useauth) {
+  private function sign($method, $url, $params, $useauth, $encodeAccessToken = true) {
     $this->prepare_method($method);
     $this->prepare_url($url);
-    $this->prepare_params($params);
+    $this->prepare_params($params, $encodeAccessToken);

     // we don't sign anything is we're not using auth
@@ -411,4 +413,14 @@
     return $this->curlit();
   }
+
+  public function requestAccessToken($method, $url, $params=array(), $useauth=true, $multipart=false) { 
+    $this->config['multipart'] = $multipart;
+    
+    $this->create_nonce();
+    $this->create_timestamp();
+    
+    $this->sign($method, $url, $params, $useauth, false);
+    return $this->curlit();
+  }