On around line 400 of commerce_braintree.module, in commerce_braintree_get_feedback() function, it prepares a query string to check against a hashed value for security with Braintree. However, included in this query string is Drupal's native "q" parameter. This was causing a failure in communicating with Braintree API.

Here is the code I added to fix it. It's hack-y, but it works (and someone better than me can surely improve on it). The first 3 lines are in the original module; the next lines (beginning with the comments) are what I added.

  if (isset($_SERVER['QUERY_STRING'])) {
    $feedback = $_SERVER['QUERY_STRING'];
  }
  // Find the string length of $_GET parameter 'q' from the query string
  // Then add 3 characters, for the 'q=' and the '&' character before 'http_status'
  // Then remove that number of characters from the $feedback variable. 
  // The thing is, if we pass the whole query sring including 'q', it messes Braintree up.
  $before_http_status = strlen($_GET['q']) + 3;
  $feedback = substr($feedback, $before_http_status);

Great module! Seems to work so far with this fix.

Support from Acquia helps fund testing for Drupal Acquia logo

Comments

visuaLatte’s picture

For more information, here is the query string that the module originally sent to the Braintree API:
q=checkout/34/payment/return/450i3jcn8CDjeockrLyAZLbSYHMVlUdN308whLfhqLQ&http_status=200&id=dyr5j598bcy3vkmd&kind=create_transaction

And here's the shorter version it expected (which the code above provides):
http_status=200&id=dyr5j598bcy3vkmd&kind=create_transaction

Haza’s picture

Version: » 7.x-1.x-dev
Status: Active » Needs work

Tested with and without clean urls, with the latest version of the API.
With your code, I get a fatal error during the transaction.

Notice: Undefined index: http_status in Braintree_TransparentRedirect::parseAndValidateQueryString() (line 257 of /media/nfsdrive/d7/www/sites/all/modules/contrib/commerce_braintree/braintree_php/lib/Braintree/TransparentRedirect.php).

Notice: Undefined index: http_status in Braintree_TransparentRedirect::parseAndValidateQueryString() (line 262 of /media/nfsdrive/d7/www/sites/all/modules/contrib/commerce_braintree/braintree_php/lib/Braintree/TransparentRedirect.php).

Braintree_Exception_Unexpected: Unexpected HTTP_RESPONSE # in Braintree_Util::throwStatusCodeException() (line 77 of /media/nfsdrive/d7/www/sites/all/modules/contrib/commerce_braintree/braintree_php/lib/Braintree/Util.php).
Haza’s picture

Status: Needs work » Postponed (maintainer needs more info)

When you confirm the transaction, You call Braintree_TransparentRedirect::confirm() that use a validation function to parse the query string.

The function look like (I remove some parts) (in TransparentRedirect.php) :

   public static function parseAndValidateQueryString($queryString)
    {
        // parse the params into an array
        parse_str($queryString, $params);
        // remove the hash
        $queryStringWithoutHash = null;
        if(preg_match('/^(.*)&hash=[a-f0-9]+$/', $queryString, $match)) {
            $queryStringWithoutHash = $match[1];
        }
        (...)
        // recreate the hash and compare it
        if(self::_hash($queryStringWithoutHash) == $params['hash']) {
            return $params;
        } else {
            throw new Braintree_Exception_ForgedQueryString();
        }
    }

It first get the query without the hash, and then try to recompose the hash with the query it has and compare to the hash that is stored. Both need to be the same. This is used to avoid forged queries.

So that really look odd that you need to change the query. If the query string is altered, it should trigger an exception.

When you said "

This was causing a failure in communicating with Braintree API.

", can you be more explicit ?

What kind of error message do you have ?

Thanks

visuaLatte’s picture

Status: Postponed (maintainer needs more info) » Active

The error I got was due to Drupal's 'q' parameter was being included in the full query string, which was being hashed. In comment #1, I show (from my PHP error log) what the query string being passed to the Braintree API looked like.

I think the reason for your error is because I didn't wrap my code in a conditional. Try this:

if ($_GET['q']):
  $before_http_status = strlen($_GET['q']) + 3;
  $feedback = substr($feedback, $before_http_status);
endif;

Maybe your setup and mine differed, and yours didn't include the 'q' parameter in the query string. When I left out the conditional statement, it removed 3 characters from the query string, resulting in the 'http_status' parameter being changed to 'p_status'.

Nathan

visuaLatte’s picture

The error message I received was the error thrown by the code throw new Braintree_Exception_ForgedQueryString();.

Drupal failed (took me to the error page) and in my Watchdog log, I saw that ForgedQueryString error.

Essentially, the Braintree API is looking for, and hashing, the shorter query string (beginning with 'http_status') from comment #1 above. Drupal, however, in my installation at least, was sending the full long query string, beginning with 'q'. If we check for the 'q' parameter, and, if found, strip it out, we should end up with the short query string.

When I did this, it worked.

Haza’s picture

I also do have the q= in my query string (but not at the beginning).

On my env, it looks like that :

http_status=200&id=5mwtkkym65t8xrf4&kind=create_transaction&q=checkout/21/payment/return/Q92eohZ8u92bzv7tUKhVtgiEAYikz6qppJCtJ7n8nEI&hash=dc33763d0505be6d8b769ae536702641ff8ef617

And I did not get an exception from braintree with that.

visuaLatte’s picture

That must be the difference.

Since some may have it first and some last, should we add some conditional code that makes sure the query string does not begin with 'q'?

Something like this, so it executes my code ONLY if the query string begins with 'q':

if (substr($feedback, 0, 2) == 'q='):
  $before_http_status = strlen($_GET['q']) + 3;
  $feedback = substr($feedback, $before_http_status);
endif;
visuaLatte’s picture

Update: I updated to your latest committed version, and re-added the code from #7 above. It works for me. Let me know what works for your setup.

dankobiaka’s picture

Status: Active » Needs review
FileSize
486 bytes

I'm just using a regular expression to strip out the "q" parameter. It works.

/**
 * Return the query string that contains Braintree response, after a request.
 */
function commerce_braintree_get_feedback() {
  $feedback = FALSE;
  if (isset($_SERVER['QUERY_STRING'])) {
    $feedback = $_SERVER['QUERY_STRING'];
    // remove "q" parameter from query string
    $feedback = preg_replace("/&q(=[^&]*)?|^q(=[^&]*)?&?/", "", $feedback);
  }
  return $feedback;
}

Patch is attached.

Haza’s picture

Status: Needs review » Needs work

I still do have the same issue as before ...

Clean url disabled, using the patch from #9, on the payment step :
Braintree_Exception_ForgedQueryString: in Braintree_TransparentRedirect::parseAndValidateQueryString() (line 269 of /core/profiles/modules/contrib/commerce_braintree/braintree_php/lib/Braintree/TransparentRedirect.php).

And this is how looks my $feedback variable from commerce_braintree_get_feedback(), just before and after the preg_replace();

before => 'http_status=200&id=pbqcdr5jfpw473q7&kind=create_transaction&q=checkout/621/payment/return/Beg9zcva_N-BUoqpYjmXX091vWZOM3sQklf-18v58Dc&hash=2f85dd49975afea05238ec1fb3abc5d7b2e154e5'
after => 'http_status=200&id=pbqcdr5jfpw473q7&kind=create_transaction&hash=2f85dd49975afea05238ec1fb3abc5d7b2e154e5'
dankobiaka’s picture

Can we get this issue resolved? The before and after in #10 looks perfect to me.

Haza’s picture

As I said, I can't commit commit something that doesn't solve things for everyone. As said in #10, the patch from #9 leads me to a Braintree_Exception_ForgedQueryString

dankobiaka’s picture

Status: Needs work » Needs review
FileSize
572 bytes

Just need to check for clean URLs before applying regular expression.

Patch attached.

I've confirmed this fix works with clean URLs enabled and disabled.

pcoucke’s picture

The patch from #13 fixed it for me.

pcoucke’s picture

Issue summary: View changes

Added sentence

jessepinho’s picture

Issue summary: View changes

I would recommend stripping the 'q=' variable regardless of the clean_urls setting, since some servers (like Pantheon) still have the 'q=' part in $_SERVER['QUERY_STRING'] with clean URLs enabled.

My solution was the following:

<?php
function commerce_braintree_get_feedback() {
  $feedback = FALSE;
  if (isset($_SERVER['QUERY_STRING'])) {
    $feedback = $_SERVER['QUERY_STRING'];
    if (preg_match('/^q=([^&]*)&?(.*)$/', $feedback, $match)) {
      $feedback = $match[2];
    }
  }
  return $feedback;
}
?>

However, Haza apparently had the q parameter in the middle of the query string, which this regex wouldn't fix. Any ideas what determines the order of the parameters in the query string?

Lukas von Blarer’s picture

Should we get this committed? Is this issue present in 7.x-2.x as well? At least the patch still applies.

nyleve101’s picture

It would be great to get this committed. I had to use the patch to get it to work for 7.x-2.x.

Haza’s picture

As I already said, some people needs to use this patch to get the module working, while some other people gets an error if they use the patch.

At this point, we can't commit that like it is right now if it will introduce crashes for some peple.

Lukas von Blarer’s picture

Status: Needs review » Needs work

In that case it needs work.

bmar777’s picture

the patch did not work for me

here is the error log

Suhosin-Patch configured -- resuming normal operations
[Sun Jun 28 00:20:57 2015] [notice] caught SIGTERM, shutting down
[Sun Jun 28 00:20:59 2015] [notice] ModSecurity for Apache/2.6.3 (http://www.modsecurity.org/) configured.
[Sun Jun 28 00:20:59 2015] [notice] ModSecurity: APR compiled version="1.4.6"; loaded version="1.4.6"
[Sun Jun 28 00:20:59 2015] [notice] ModSecurity: PCRE compiled version="8.12"; loaded version="8.12 2011-01-15"
[Sun Jun 28 00:20:59 2015] [notice] ModSecurity: LUA compiled version="Lua 5.1"
[Sun Jun 28 00:20:59 2015] [notice] ModSecurity: LIBXML compiled version="2.7.8"
[Sun Jun 28 00:21:00 2015] [notice] Apache/2.2.22 (Ubuntu) PHP/5.3.10-1ubuntu3.18 with Suhosin-Patch configured -- resuming normal operations
JimSmith’s picture

#13 worked for me. Thank you.

blazey’s picture

Version: 7.x-1.x-dev » 7.x-2.0-beta1
Status: Needs work » Needs review
FileSize
540 bytes

Hello,

Attached path solves the issue and is platform agnostic.

rjdjohnston’s picture

#13 worked for me. Thanks!

peezy’s picture

#22 works for me... thanks @blazey.

rbayliss’s picture

Unfortunately #22 requires that the q parameter is first, which might not always be the case. We can just use drupal_get_query_parameters(), which automatically strips out $_GET['q'].

arlingtonvoicellc’s picture

Status: Needs review » Needs work

None of the patches are working for me. I'm hitting same wall as OP. When you submit the credit card info using the drop-in UI, it immediately takes me to a page that says "Website encountered unexpected error."

arlingtonvoicellc’s picture

Disregard my last comment. I figured out the problem. I removed the "billing information" pane from the checkout form. Apparently you can't do that or it throws an error. Added it back and it works fine.

rbayliss’s picture

Status: Needs work » Needs review
mglaman’s picture

Assigned: Unassigned » mglaman

#25 is the best approach and uses proper API methods to get query params, excluding Drupal's q. Reviewing

  • mglaman committed 316b8a8 on 7.x-2.x authored by rbayliss
    Issue #1886950 by dankobiaka, rbayliss, blazey, visuaLatte, Haza,...
mglaman’s picture

Version: 7.x-2.0-beta1 » 7.x-2.x-dev
Assigned: mglaman » Unassigned
Status: Needs review » Fixed

Thanks, everyone.

Status: Fixed » Closed (fixed)

Automatically closed - issue fixed for 2 weeks with no activity.