Validate X-Hub-Signature-256 meta / whatsapp webhook request - python

The bounty expires in 12 hours. Answers to this question are eligible for a +50 reputation bounty.
Gurkenkönig wants to draw more attention to this question.
I can't manage to validate the X-Hub-Signature-256 for my meta / whatsapp webhook in flask successfully.
Can anyone tell me where the error is or provide me with a working example?
import base64
import hashlib
import hmac
import os
from dotenv import load_dotenv
from flask import Flask, jsonify, request
from werkzeug.middleware.proxy_fix import ProxyFix
load_dotenv()
API_SECRET = os.environ.get('API_SECRET')
app = Flask(__name__)
app.wsgi_app = ProxyFix(app.wsgi_app, x_for=1, x_host=1)
def verify_webhook(data, hmac_header):
hmac_recieved = str(hmac_header).removeprefix('sha256=')
digest = hmac.new(API_SECRET.encode('utf-8'), data,
digestmod=hashlib.sha256).digest()
computed_hmac = base64.b64encode(digest)
return hmac.compare_digest(computed_hmac, hmac_recieved.encode('utf-8'))
#app.route("/whatsapp", methods=["GET", "POST"])
def whatsapp_webhook():
if request.method == "POST":
try:
data = request.get_data()
if not verify_webhook(data, request.headers.get('X-Hub-Signature-256')):
return "", 401
except Exception as e:
print(e)
return "", 500
return jsonify({"status": "success"}, 200)

This seems to work:
def verify_webhook(data, hmac_header):
hmac_recieved = str(hmac_header).removeprefix('sha256=')
digest = hmac.new(API_SECRET.encode('utf-8'), data.encode('utf-8'), hashlib.sha256).hexdigest()
return hmac.compare_digest(hmac_recieved, digest)
If you run this test:
import base64
import hashlib
import hmac
import os
API_SECRET = 'very_secret_key'
def verify_webhook(data, hmac_header):
hmac_recieved = str(hmac_header).removeprefix('sha256=')
digest = hmac.new(API_SECRET.encode('utf-8'), data.encode('utf-8'), hashlib.sha256).hexdigest()
return hmac.compare_digest(hmac_recieved, digest)
print(verify_webhook('Good morning', 'sha256=63e447ebe2bb46cb621972656087950c9d3a437caa61ece01f18094cc99f5a16'))
It returns True as expected:
$ python3 xhub_new.py
True

Related

Shopify Webhooks Hmac Python verification fails

I am trying to verify the webhook received from Shopify but the Hmac verification fails.
def verify_webhook(data, hmac_header):
digest = hmac.new(SECRET.encode('utf-8'), data, hashlib.sha256).digest()
computed_hmac = base64.b64encode(digest)
return hmac.compare_digest(computed_hmac, hmac_header.encode('utf-8'))
#app.route('/productCreation', methods=['POST'])
def productCreation():
data = request.data
verified = verify_webhook(
data, request.headers.get('X-Shopify-Hmac-SHA256'))
if(verified):
return ("Success", 200)
return("Integrity error", 401)
Getting error as
hash = hmac.new(SECRET.encode('utf-8'), body.encode('utf-8'), hashlib.sha256)
AttributeError: 'bytes' object has no attribute 'encode'
Can anyone help with this? I am developing a Flask app for this.
You´re getting a attribute error, means your body object is not a string (encode() is working for string like objects) as mentioned in the error message it is a 'bytes' like object. Remove the
.encode('utf-8') from body.
Update March 2022
It seems like Shopify has updated their Flask example with the same changes I suggested below.
Original answer
I got the same error when using Shopify's Flask example
from flask import Flask, request, abort
import hmac
import hashlib
import base64
app = Flask(__name__)
SECRET = 'hush'
def verify_webhook(data, hmac_header):
digest = hmac.new(SECRET, data.encode('utf-8'), hashlib.sha256).digest()
computed_hmac = base64.b64encode(digest)
return hmac.compare_digest(computed_hmac, hmac_header.encode('utf-8'))
#app.route('/webhook', methods=['POST'])
def handle_webhook():
data = request.get_data()
verified = verify_webhook(data, request.headers.get('X-Shopify-Hmac-SHA256'))
if not verified:
abort(401)
# process webhook payload
# ...
return ('', 200)
To get it to work I had to modify verify_webhook by:
Encoding SECRET since hmac.new() expects the key in bytes and not as a string.
Not encoding data since Flask's response.get_data already returns an encoded bytestring.
The final code
from flask import Flask, request, abort
import hmac
import hashlib
import base64
app = Flask(__name__)
SECRET = 'hush'
def verify_webhook(data, hmac_header):
digest = hmac.new(SECRET.encode('utf-8'), data, hashlib.sha256).digest()
computed_hmac = base64.b64encode(digest)
return hmac.compare_digest(computed_hmac, hmac_header.encode('utf-8'))
#app.route('/webhook', methods=['POST'])
def handle_webhook():
data = request.get_data()
verified = verify_webhook(data, request.headers.get('X-Shopify-Hmac-SHA256'))
if not verified:
abort(401)
# process webhook payload
# ...
return ('', 200)

I cant get my flask application authenticated through google and apis usable

im quite new to coding but this is barely documented so i need some help.
Im building a flask application but I cant get the google auth flow working.
Im using Pycharm and python version 3.9
My issues are :
I cant find any beginner tutorial that explains how to go through the Auth flow.
I dont understand how to interact with google APis through flask. (i want to use the android-management-api)
I do understand that i need to create a service object but that only works when i can authenticate the google flow and this is where im already stuck for 5 days now.
I already followed the instructions from realpython and MattButton.
when trying these instructions i keep getting errors.
now im getting:
Error: While importing 'app', an ImportError was raised:
Traceback (most recent call last):
File "C:\000 Projects\Applications\PY\flaskProjects\MDM\venv\lib\site-packages\flask\cli.py", line 256, in locate_app
__import__(module_name)
File "C:\000 Projects\Applications\PY\flaskProjects\MDM\app.py", line 5, in <module>
from flask_oauth import OAuth
File "C:\000 Projects\Applications\PY\flaskProjects\MDM\venv\lib\site-packages\flask_oauth.py", line 13, in <module>
from urlparse import urljoin
ModuleNotFoundError: No module named 'urlparse'
Process finished with exit code 2
Can somebody explain me what im doing wrong
my code is below:
from flask import Flask, redirect, url_for, session, request, jsonify, render_template
from datetime import datetime
from flask import Flask, redirect, url_for, session
from flask_oauth import OAuth
import urllib.parse
# You must configure these 3 values from Google APIs console
# https://code.google.com/apis/console
GOOGLE_CLIENT_ID = ''
GOOGLE_CLIENT_SECRET = ''
REDIRECT_URI = '/authorized' # one of the Redirect URIs from Google APIs console
SECRET_KEY = 'development key'
DEBUG = True
app = Flask(__name__)
app.debug = DEBUG
app.secret_key = SECRET_KEY
oauth = OAuth()
google = oauth.remote_app('google',
base_url='https://www.google.com/accounts/',
authorize_url='https://accounts.google.com/o/oauth2/auth',
request_token_url=None,
request_token_params={'scope': 'https://www.googleapis.com/auth/androidmanagement',
'response_type': 'code'},
access_token_url='https://accounts.google.com/o/oauth2/token',
access_token_method='POST',
access_token_params={'grant_type': 'authorization_code'},
consumer_key=GOOGLE_CLIENT_ID,
consumer_secret=GOOGLE_CLIENT_SECRET)
#app.route('/')
def index():
access_token = session.get('access_token')
if access_token is None:
return redirect(url_for('login'))
access_token = access_token[0]
from urllib.request import Request, urlopen
from urllib.error import URLError
headers = {'Authorization': 'OAuth '+access_token}
req = Request('https://www.googleapis.com/oauth2/v1/userinfo',
None, headers)
try:
res = urlopen(req)
except URLError as e:
if e.code == 401:
# Unauthorized - bad token
session.pop('access_token', None)
return redirect(url_for('login'))
return res.read()
return res.read()
#app.route('/login')
def login():
callback = url_for('authorized', _external=True)
return google.authorize(callback=callback)
#app.route(REDIRECT_URI)
#google.authorized_handler
def authorized(resp):
access_token = resp['access_token']
session['access_token'] = access_token, ''
return redirect(url_for('index'))
#google.tokengetter
def get_access_token():
return session.get('access_token')
#app.route('/home')
def home():
return render_template(
'index.html',
title='Home Page',
year=datetime.now().year,
)
#app.route('/devices')
def devices():
return render_template(
'devices.html',
title='Devices',
year=datetime.now().year,
)
#app.route('/policies')
def policies():
return render_template(
'policies.html',
title='Policies',
year=datetime.now().year,
)
#app.route('/enterprises')
def enterprises():
return render_template(
'enterprises.html',
title='Enterprises',
year=datetime.now().year,
)
#app.route('/contact')
def contact():
# """Renders the contact page."""
return render_template(
'contact.html',
title='Contact',
year=datetime.now().year,
message='ICS-Vertex'
)
#app.route('/about')
def about():
return render_template(
'about.html',
title='About',
year=datetime.now().year,
message='Your application description page.'
)
if __name__ == '__main__':
app.run()
Solved!
I found out that there were 2 conflicting libraries that both used the requests name
Flask.requests
Requests

Shopify Webhook HMAC Validation With Flask

I'm trying to verify that the Webhook received is coming from Shopify. They have this doc, but it doesn't work (getting type errors).
Here's what I have so far. It produces no errors, but the verify_webhook function always returns false.
from flask import Flask, request, abort
import hmac
import hashlib
import base64
app = Flask(__name__)
SECRET = '...'
def verify_webhook(data, hmac_header):
digest = hmac.new(SECRET.encode('utf-8'), data, hashlib.sha256).digest()
genHmac = base64.b64encode(digest)
return hmac.compare_digest(genHmac, hmac_header.encode('utf-8'))
#app.route('/', methods=['POST'])
def hello_world(request):
print('Received Webhook...')
data = request.get_data()
hmac_header = request.headers.get('X-Shopify-Hmac-SHA256')
verified = verify_webhook(data, hmac_header)
if not verified:
return 'Integrity of request compromised...', 401
print('Verified request...')
if __name__ == '__main__':
app.run()
What am I doing wrong?
Answer:
from flask import Flask, request, abort
import hmac
import hashlib
import base64
app = Flask(__name__)
SECRET = '...'
def verify_webhook(data, hmac_header):
digest = hmac.new(SECRET.encode('utf-8'), data, hashlib.sha256).digest()
genHmac = base64.b64encode(digest)
return hmac.compare_digest(genHmac, hmac_header.encode('utf-8'))
#app.route('/', methods=['POST'])
def hello_world(request):
print('Received Webhook...')
data = request.data # NOT request.get_data() !!!!!
hmac_header = request.headers.get('X-Shopify-Hmac-SHA256')
verified = verify_webhook(data, hmac_header)
if not verified:
return 'Integrity of request compromised...', 401
print('Verified request...')
if __name__ == '__main__':
app.run()
Issue was in the data = request.get_data() line.

AWS Chalice, can't get image from POST request

I'm trying to invoke my sagemaker model using aws chalice, a lambda function, and an API Gateaway.
I'm attempting to send the image over POST request but I'm having problem receiving it on the lambda function.
My code looks like:
from chalice import Chalice
from chalice import BadRequestError
import base64
import os
import boto3
import ast
import json
app = Chalice(app_name='foo')
app.debug = True
#app.route('/', methods=['POST'], content_types=['application/json'])
def index():
body = ''
try:
body = app.current_request.json_body # <- I suspect this is the problem
return {'response': body}
except Exception as e:
return {'error': str(e)}
It's just returning
<Response [200]> {'error': 'BadRequestError: Error Parsing JSON'}
As I mentioned before, my end goal is to receive my image and make a sagemaker request with it. But I just can't seem to read the image.
My python test client looks like this:
import base64, requests, json
def test():
url = 'api_url_from_chalice'
body = ''
with open('b1.jpg', 'rb') as image:
f = image.read()
body = base64.b64encode(f)
payload = {'data': body}
headers = {'Content-Type': 'application/json'}
r = requests.post(url, data=payload, headers=headers)
print(r)
r = r.json()
# r = r['response']
print(r)
test()
Please help me, I spent way to much time trying to figure this out
That error message is because you're not sending a JSON body over to your Chalice app. One way to check this is by using the .raw_body property to confirm:
#app.route('/', methods=['POST'], content_types=['application/json'])
def index():
body = ''
try:
#body = app.current_request.json_body # <- I suspect this is the problem
return {'response': app.current_request.raw_body.decode()}
except Exception as e:
return {'error': str(e)}
You'll see that the body is form-encoded and not JSON.
$ python client.py
<Response [200]>
{'response': 'data=c2FkZmFzZGZhc2RmYQo%3D'}
To fix this, you can use the json parameter in the requests.post() call:
r = requests.post(url, json=payload, headers=headers)
We can then confirm we're getting a JSON body in your chalice app:
$ python client.py
<Response [200]>
{'response': '{"data": "c2FkZmFzZGZhc2RmYQo="}'}
So I was able to figure it out with the help of an aws engineer (i got lucky I suppose). I'm including the complete lambda function. Nothing changed on the client.
from chalice import Chalice
from chalice import BadRequestError
import base64
import os
import boto3
import ast
import json
import sys
from chalice import Chalice
if sys.version_info[0] == 3:
# Python 3 imports.
from urllib.parse import urlparse, parse_qs
else:
# Python 2 imports.
from urlparse import urlparse, parse_qs
app = Chalice(app_name='app_name')
app.debug = True
#app.route('/', methods=['POST'])
def index():
parsed = parse_qs(app.current_request.raw_body.decode())
body = parsed['data'][0]
print(type(body))
try:
body = base64.b64decode(body)
body = bytearray(body)
except e:
return {'error': str(e)}
endpoint = "object-detection-endpoint_name"
runtime = boto3.Session().client(service_name='sagemaker-runtime', region_name='us-east-2')
response = runtime.invoke_endpoint(EndpointName=endpoint, ContentType='image/jpeg', Body=body)
print(response)
results = response['Body'].read().decode("utf-8")
results = results['predictions']
results = json.loads(results)
results = results['predictions']
return {'result': results}

Verifying HMAC from Microsoft Teams bot in Python Flask

I am trying to build a Microsoft Teams chat bot using Flask, following the instructions on how to build custom bots. However I am unable to verify the HMAC auth which I really want for security.
Based on guides and documentation I've found I am using the following minimial testing app trying to calculate a HMAC for the incoming request. (Bot name and description DevBot and the key/security_token below for testing).
#!/usr/bin/python
# coding=utf-8
from flask import Flask, request, jsonify
import hmac, hashlib, base64, json
app = Flask(__name__)
#app.route('/', methods=['GET', 'POST'])
def webhook():
if request.method == 'POST':
# Authenticate
security_token = b"O5XHU8OSzwx8w9YiM0URkR/Ij4TZZiZUwz7Swc+1hZE="
request_data = request.get_data()
digest = hmac.new(security_token, msg=request_data, digestmod=hashlib.sha256).digest()
signature = base64.b64encode(digest).decode()
# TODO: Verify signature = Authorization header HMAC here
return jsonify({
'type' : 'message',
'text' : "Auth header: {0} <br>Calculated HMAC: {1}".format(request.headers.get('Authorization'), signature),
})
elif request.method == 'GET':
return "Hello World"
if __name__ == '__main__':
app.run(debug=True)
Upon sending the message #DevBot test I get the following hashes back in the reply from the bot, but they aren't matching as expected:
Auth header: HMAC LuDmz97y/Z2KWLIZ1WZASz3HlOEtDCwk5/lL/fK8GqM=
Calculated HMAC: eaxTdJSLuU3Z4l94bxFiWvsBhjNG9SPxwq/UHeR7KcA=
Any ideas or pointers? I've been trying all sorts of stuff with encoding but I have a feeling that Flask might be doing something that modifies the request body or something?
edit 1: small clarification
edit 2: full Flask app example
edit 3: sample bot details, input and output examples
After lots of trial and error and trying to reproduce the C# code example from MS I managed to solve it myself. Here's the solution:
#!/usr/bin/python
# coding=utf-8
from flask import Flask, request, jsonify
import hmac, hashlib, base64, json
app = Flask(__name__)
#app.route('/', methods=['GET', 'POST'])
def webhook():
if request.method == 'POST':
# Reply
data = request.get_json()
channel = data['channelId']
message_type = data['type']
sender = data['from']['name']
message_format = data['textFormat']
message = data['text']
# Authenticate
security_token = b"O5XHU8OSzwx8w9YiM0URkR/Ij4TZZiZUwz7Swc+1hZE="
request_data = request.get_data()
digest = hmac.new(base64.b64decode(security_token), msg=request_data, digestmod=hashlib.sha256).digest()
signature = base64.b64encode(digest).decode()
# TODO: verify that HMAC header == signature
return jsonify({
'type' : 'message',
'text' : "auth header: {0} <br>hmac: {1}".format(request.headers.get('Authorization').split(' ')[1], signature),
})
elif request.method == 'GET':
return "Hello World"
if __name__ == '__main__':
app.run(debug=True)
Another option rather than interfacing directly with Microsoft Teams may be to use the Microsoft Bot Connector API.
https://docs.botframework.com/en-us/restapi/connector/
I have a bot working with Microsoft Teams using https://github.com/Grungnie/microsoftbotframework which is validating the JWT that is sent from Microsoft.

Categories

Resources