1

I'm trying to access Gravity Forms entry data via their REST API v2 using Python, but can't figure out how to properly authenticate. Should I use basic authentication or OAuth1? I've never successfully used either so example code would be most helpful.

I've tried basic authentication which seems to be the default for the requests module.

import requests

url = 'https://<INSERT DOMAIN HERE>/wp-json/gf/v2/entries'
auth = (<CONSUMER KEY>, <CONSUMER SECRET>)
r = requests.get(url, auth=auth)
print(r.status_code)

When basic auth didn't work, I also tried OAuth1 using requests_oathlib and this post as a guideline, but I can't get that to work either. I'm not sure what the different keys/tokens/secrets are or how to get them. I have a "consumer key" and a "consumer secret" from the Gravity Forms REST API section of the WordPress dashboard, but that's it.

import requests
from requests_oauthlib import OAuth1

url = 'https://<INSERT DOMAIN HERE>/wp-json/gf/v2/entries'
auth = OAuth1('YOUR_APP_KEY', 'YOUR_APP_SECRET', 'USER_OAUTH_TOKEN', 'USER_OAUTH_TOKEN_SECRET')
r = requests.get(url, auth=auth)
print(r.status_code)

I've also tried following the docs for requests_oauthlib here, but I'm not sure which urls to use where or which path to take (session vs helper).

Gravity Forms REST API documentation says that basic authentication is acceptable as long as requests are sent using HTTPS whereas HTTP requests must use OAuth1.0a. I can't get either to work. However, I know I'm close because I can get the Postman application to work using the "Consumer Key" and "Consumer Secret" with OAuth1 and a HMAC-SHA1 "Signature Method."

I'm expecting a 200 response status code, but no matter what type of authentication I use, I keep getting a 401 response status code.

grove80904
  • 419
  • 2
  • 5
  • 14

2 Answers2

1

I'm not 100% sure about all the nuances with this, but after extensive research and trial/error, I got this to work for myself. Please feel free to add suggestions.

import requests
import time
import random
import string
import oauthlib.oauth1.rfc5849.signature as oauth
from urllib.parse import quote_plus
import os
import json
import datetime


def create_nonce(N = 32): # randomly generated 32 character (recommended) string
    result = ''.join(random.choices(string.ascii_letters + string.digits, k = N))
    return result


def create_signature(httpMethod, url, urlSuffix, nonce, timestamp, consumerKey, signatureMethod, version):
    consumerSecret = <INSERT_YOUR_CONSUMER_SECRET_HERE>
    # https://stackoverflow.com/a/39494701/5548564
    # In case of http://example.org/api?a=1&b=2 - the uri_query value would be "a=1&b=2".
    uri_query = urlSuffix
    # The oauthlib function 'collect_parameters' automatically ignores irrelevant header items like 'Content-Type' or
    # 'oauth_signature' in the 'Authorization' section.
    headers = {
        "Authorization": (
            f'OAuth realm="", '
            f'oauth_nonce={nonce}, '
            f'oauth_timestamp={timestamp}, '
            f'oauth_consumer_key={consumerKey}, '
            f'oauth_signature_method={signatureMethod}, '
            f'oauth_version={version}')}
    # There's no POST data here - in case it was: x=1 and y=2, then the value would be '[("x","1"),("y","2")]'.
    data = []
    params = oauth.collect_parameters(uri_query=uri_query,
                                      body=data,
                                      headers=headers,
                                      exclude_oauth_signature=True,
                                      with_realm=False)
    norm_params = oauth.normalize_parameters(params)
    base_string = oauth.construct_base_string(httpMethod, url, norm_params)
    signature = oauth.sign_hmac_sha1(base_string, consumerSecret, '')
    return quote_plus(signature)


def send_request():
    url = <INSERT_URL_HERE>
    urlSuffix = "_labels=1"  # Addes array at end of json results including a map to the field labels
    httpMethod = "GET"
    consumerKey = <INSERT_CONSUMER_KEY_HERE>
    signatureMethod = "HMAC-SHA1"
    timestamp = str(int(time.time()))
    nonce = create_nonce()
    version = "1.0"
    signature = create_signature(httpMethod, url, urlSuffix, nonce, timestamp, consumerKey, signatureMethod, version)
    queryString = {"oauth_consumer_key": consumerKey,
                   "oauth_signature_method": signatureMethod,
                   "oauth_timestamp": timestamp,
                   "oauth_nonce": nonce,
                   "oauth_version": version,
                   "oauth_signature": signature}
    headers = {'User-Agent': "Testing/0.1",
               'Accept': "*/*",
               'Host': "<INSERT_YOUR_DOMAIN_HERE>",
               'Accept-Encoding': "gzip, deflate",
               'Connection': "keep-alive"}
    if urlSuffix:
        url = url + "?" + urlSuffix
    r = requests.request(httpMethod, url, headers=headers, params=queryString)
    if r.status_code == 200:
        dict = json.loads(r.text)
        return dict
    else:
        print(r.status_code)
        return None


response = send_request()
grove80904
  • 419
  • 2
  • 5
  • 14
1

For anyone who still needs this, through trial and error I found that they changed the expected auth location to be in the query instead of the headers as you'd find in most python auth libraries. You can change it like this:

import oauthlib
from requests_oauthlib import OAuth1Session

consumer_key = "banana"
client_secret = "also banana"

session = OAuth1Session(consumer_key, client_secret=client_secret, signature_type=oauthlib.oauth1.SIGNATURE_TYPE_QUERY)

url = 'https:// YOUR URL /wp-json/gf/v2/entries'
r = session.get(url)
print(r.content)
  • I can't edit, but you have a typo. `consumer_secret` should be `client_secret`: `session = OAuth1Session(consumer_key, client_secret=client_secret, signature_type=oauthlib.oauth1.SIGNATURE_TYPE_QUERY)` – morgantaschuk Sep 15 '22 at 20:43