-1

I apologize if this has been asked or has a similar questions; I have been searching for days using different search terms and have come up empty.

I have a python script that works perfectly fine as expected. I have it installed on my windows machine and run the script locally. What I want to do is somehow convert this to a PHP request; I assumed cURL would be the option selected but I am open to suggestions. I have made a couple attempts on my end, but I keep getting an authentication error. I'm sure it's something simple; but maybe not.

Here is my python script:

import requests
from datetime import datetime
import hashlib
import hmac
import base64
import json
import time

def main():    
    api_key = 'abcd'
    secret_key = 'hashedSecret'
    host = 'https://myapidomain.com/'
    uri = 'api/1.0/Projects/8030854199/items'

    http_verb = 'GET'
    content_type = 'application/json'
    timestamp = gen_iso_timestamp()
    auth_header_value = gen_auth_string(api_key, secret_key, uri, http_verb, content_type, timestamp)
    full_url = f'{host}{uri}'
    headers = {'att-api-timestamp': timestamp, 'authorization': auth_header_value, 'Content-Type': content_type, 'Accept': content_type}
    
    r = requests.get(full_url, headers=headers)
    if r.status_code == 200:
        with open(f'data_dump/8030854199.json', 'w+') as json_file:
            json_file.write(r.text)
    elif r.status_code != 200:
        error_code = f'{r.status_code}'
        with open(f'data_dump/error.txt', 'w+') as json_file:
            json_file.write(error_code)


def gen_auth_string(api_key, secret_key, uri, http_verb, content_type, timestamp):
    message = f'{http_verb}\n{content_type}\n{uri}\n{timestamp}'
    canonical_request = bytes(message, 'ASCII')
    secret_bytes = bytes(secret_key, 'ASCII')
    digest = hmac.new(secret_bytes, msg=canonical_request, digestmod=hashlib.sha256).digest()
    signature = base64.b64encode(digest).decode()
    return f'AttAPI {api_key}:{signature}'


def gen_iso_timestamp():
    # generate a timestamp in this format 2018-12-31T23:55:55+00:00
    return f'{datetime.utcnow().replace(microsecond=0).isoformat()}+00:00'


if __name__ == "__main__":
    main()

Here was my test PHP code:

<?php

function genTimestamp(){ return gmdate(DATE_ATOM); }
function genAuthString($secret_key, $uri, $timestamp){ 
    $message = "GET"."\n"."application/json"."\n".$uri."\n".$timestamp;
    return hash_hmac('sha256',$message,$secret_key,true);
}

$api_key = 'abcd';
$secret_key = 'hashedSecret';
$host = 'https://myapidomain.com/';
$uri = 'api/1.0/Projects/8030854199/items';
$timestamp = genTimestamp();
// for testing: 
//    echo $timestamp."<br />";

$curl = curl_init();

curl_setopt($curl, CURLOPT_URL, $host.$uri);
curl_setopt($curl, CURLOPT_CONNECTTIMEOUT, 0);
curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
curl_setopt($curl, CURLOPT_CUSTOMREQUEST, 'GET');
curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, false);
curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false);
// for testing: 
//    echo genAuthString($secret_key, $uri, $timestamp)."<br />"
curl_setopt($curl, CURLOPT_HTTPHEADER, array(
    'att-api-timestamp: '.$timestamp,
    'authorization: '.genAuthString($secret_key, $uri, $timestamp),
    'Content-Type: application/json',
    'Accept: application/json'
));

// for testing: 
//    echo "Executing... <br />";
$result = curl_exec($curl);
if (curl_errno($curl)) {
    // for testing: 
    echo 'Error:' . curl_error($curl) . "<br />";
}
curl_close($curl);
echo $result;

?>

Note that the timestamp must be stored in a variable to be hashed in the HMAC and sent as a header or else it definitely won't authenticate.

Any guidance or suggestions would be greatly appreciated!

Thanks

tjmac93
  • 11
  • 4
  • Check out curlify that will convert your python requests into a curl statement. I've used it to debug my API calls. – WombatPM Jun 30 '21 at 20:22
  • I like the idea! My concern is with the whole timestamp piece. I can generate the cURL request once; but for subsequent requests, the timestamp would be different. So, if my understanding is correct, I would have to use curlify each time to generate the curl; which defeats my purpose. I would just use my working python script at that point. My goal is to be able to access the data from the API call within PHP and use the data from there. Right now, that code above connects except that I get an authentication error. I really think it has something to do with the genAuthString function. – tjmac93 Jun 30 '21 at 22:09
  • If I could get a working HMAC hashing function in PHP that I can pass using the PHP/cURL code above, I think it would successfully authenticate. Just can't figure out the correct authentication hashing function. – tjmac93 Jun 30 '21 at 22:10

3 Answers3

1

If you're asking for just this api call to work then I would recommend just recreating the request instead of trying to translate it. You can set up your environment variables and such and then just download the cURL code using a tool like Postman.

https://www.postman.com/

If you're asking for a way to programmatically go-between, I have never seen anything do that.

Cfomodz
  • 532
  • 4
  • 17
  • My biggest issue I believe is with the HMAC hashing. I believe that is where my PHP code is incorrect. I am VERY new to Python; which is why I was hoping someone could look at the Python code that I know works and help me make same result in a PHP function. I'm also willing to do Postman; I would just need guidance on setting up that same authorization piece. – tjmac93 Jun 30 '21 at 19:59
  • It sounds like this would be the best solution. I still don't know how to properly build the genAuthString function in PHP so that it properly hashes the same way that the python function does it. Or even in Postman for that matter. Again, the python script correctly creates the authorization string I need. I am unsuccessful when trying the way I posted above. Thoughts? – tjmac93 Jul 01 '21 at 05:16
1

I had a colleague point out my issue. For anyone interested..

First my issue with this script specifically:

I had 2 issues causing it to fail. The first was that I was missing the prepended part of the authorization string in the cURL header:

'authorization: AttAPI '.$api_key.':'.genAuthString($secret_key, $uri, $timestamp),

The second issue was that, according to the python script and documentation, it needed to be base64 encoded. So the return line of the genAuthString() should have been:

return base64_encode(hash_hmac('sha256',$message,$secret_key,true));

EDIT:
Second, here is a better answer for anyone who might want to run the python script from within PHP: This thread gave the answer. For me specifically, the passthru command worked the best. https://stackoverflow.com/a/19736525/16351932

But I also learned that PHP for me wasn't calling the right virtual environment where my configured Python App was running so I had to tell PHP to switch to that virtual environment first.

tjmac93
  • 11
  • 4
0

I know it's hard to re-code. If you want to make http requests without problem in PHP, I suggest you try Guzzle

https://docs.guzzlephp.org/en/stable/

glitchcl
  • 318
  • 2
  • 9