How can I validate payloads from GitHub webhooks in Python 3? - python-3.x

I've realized auto-deploying of my Flask app on the server by Github webhooks, but I cannot adapt Ruby script specified in the manual to Python 3 to validate the POST-request. I've tried this:
from flask import Flask, request
from hmac import HMAC, compare_digest
from hashlib import sha1
app = Flask(__name__)
def verify_signature(req):
received_sign = req.headers.get('X-Hub-Signature').split('sha1=')[-1].strip()
secret = 'my_secret_string'.encode()
expected_sign = HMAC(key=secret, msg=req.data, digestmod=sha1).hexdigest()
return compare_digest(received_sign, expected_sign)
#app.route('/webhook', methods=['POST', 'GET'])
def webhook():
if request.method == 'POST':
if verify_signature(request):
do_smth()
return 'Successfully', 200
return 'Forbidden', 403
return 'Not allowed', 405
Also I tried other variants with sha1(...).hexdigest() and compare_digest() from secrets package, but received signature always differs.
What do I do wrong?

Use SHA-256 instead of SHA-1
Instead of SHA-1 GitHub recommends using the more secure SHA-256.
Changed your code respectively for convenience.
from flask import Flask, request
from hmac import HMAC, compare_digest
from hashlib import sha256
app = Flask(__name__)
def verify_signature(req):
received_sign = req.headers.get('X-Hub-Signature-256').split('sha256=')[-1].strip()
secret = 'my_secret_string'.encode()
expected_sign = HMAC(key=secret, msg=req.data, digestmod=sha256).hexdigest()
return compare_digest(received_sign, expected_sign)
#app.route('/webhook', methods=['POST', 'GET'])
def webhook():
if request.method == 'POST':
if verify_signature(request):
do_smth()
return 'Successfully', 200
return 'Forbidden', 403
return 'Not allowed', 405

I've updated the code. Now it works well.

Related

How to return 401 in flask despite authorization

I am new to flask and I want to return 401 error, I am not using authentication, I am simply using a parameter api_key to check whether the user is authenticated.
I've tried abort(401) and return false, but it returns error 400 'Bad Request', is this the default way it returns a 401. Or is there a different way to do this?
My Code:
import flask
from flask import jsonify, request, Response
from flask_cors import CORS, cross_origin
from GoogleNews import GoogleNews
googlenews = GoogleNews()
googlenews.set_lang('en')
googlenews.set_period('1d')
googlenews.set_encode('utf-8')
app=flask.Flask(__name__)
cors = CORS(app)
app.config['CORS_HEADERS'] = 'Content-Type'
#cross_origin()
#app.route('/', methods=["GET"])
def return_news():
API_KEY = request.args['api_key']
if API_KEY == "xyz":
googlenews.get_news('TECHNOLOGY')
return jsonify(googlenews.result(True)[0:4])
else:
return Response(response="Unauthorized", status=401)
Thankyou
instead, try flask.Response object.
from flask import Flask, Response
app = Flask(__name__)
#app.route("/abort")
def abort():
return Response(response="Unauthorized", status=401)
app.run()
without the actual code it is hard to tell, but I think it is not using the flask abort() but the python internal abort() function.
Check the code below:
import flask
app = flask.Flask("aborter")
#app.route("/abort")
def abort():
return flask.abort(401)
if __name__ == '__main__':
app.run()
Happy Coding

Python3 PyTest Flask JWT throws HTTP 422 Unprocessable Entity

I am using Python 3.9 and the Flask-JWT-Extended PyPi package in my application. I am writing some test cases and when I POST to the endpoint I am testing with a proper-looking Bearer token, I get an HTTP 422 'Unprocessable Entity'. Google is not turning up an answer. How can I fix this?
# Do the Bearer login
data = {
'username': app.username,
'password': app.password,
}
tokenResponse = client.post("/login", json=data)
assert tokenResponse.content_type == 'application/json'
assert tokenResponse.json['access_token']
And shortly after, in the same test method, I try to POST to the actual endpoint:
print("~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~")
print({"Authorization": f"JWT {tokenResponse.json['access_token']}"})
print("~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~")
response = client.post(endpoint, buffered=False,
content_type='multipart/form-data',
data=data,headers={"Authorization": f"JWT {tokenResponse.json['access_token']}"})
Here is the token printout:
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
{'Authorization': 'JWT eyJ0eXAiOiJKV1QilCJhbGciOiJIUzI1NiJ9.eyJpYXQiOjE2MTI0Nzk5NzAsIm5iZiI6MTYxMjQ3OTk3MCwianRpIjoiYTQyZjU1NmUtYjQ2MS00NTNiLThkM2ItYjk1MmIzYzAwZjc0IiwiZXhwIjoxNjeyNDgwMDMwLCJpZGVudGl0eSI6IlNlbnNvbml4QXBpVXNlciIsImZyZXNoIjpmYWxzZSwidHlwZSI6ImFjY2VzcyJ9.IYrgg2e9VxhLFH0_kwQbmoHKI1wKsKfm3cpK3XZmqyY'}
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Here is the traceback.
print("~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~")
print({"Authorization": f"JWT {tokenResponse.json['access_token']}"})
print("~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~")
response = client.post(endpoint, buffered=False,
content_type='multipart/form-data',
data=data,headers={"Authorization": f"JWT {tokenResponse.json['access_token']}"})
> assert response.status_code == 200
E assert 422 == 200
E + where 422 = <Response streamed [422 UNPROCESSABLE ENTITY]>.status_code
../tests/test_endpoints.py:153: AssertionError
First suggestion, if you look at the response.get_json() it should give you a helpful error message for why the 422 was thrown (invalid audience, jwt verification failed, etc). That might help point you in the right direction.
Here is an example of a working spec that creates and passes a JWT in via headers if it helps:
import pytest
from flask import Flask
from flask import jsonify
from flask_jwt_extended import JWTManager
from flask_jwt_extended import jwt_required
from flask_jwt_extended import create_access_token
#pytest.fixture(scope="function")
def app():
app = Flask(__name__)
app.config["JWT_SECRET_KEY"] = "foobarbaz"
JWTManager(app)
#app.route("/login", methods=["POST"])
def login():
return jsonify(access_token=create_access_token("test_user"))
#app.route("/protected", methods=["GET"])
#jwt_required
def access_protected():
return jsonify(foo="bar")
return app
def test_default_headers(app):
test_client = app.test_client()
response = test_client.post("/login")
access_token = response.get_json()["access_token"]
access_headers = {"Authorization": "Bearer {}".format(access_token)}
response = test_client.get("/protected", headers=access_headers)
assert response.status_code == 200
assert response.get_json() == {"foo": "bar"}

I am having error invalid syntax in my python code

I am trying to add multiple inner function in my code. But I am not able to add it properly and also it is giving me a syntax error. Here I am trying to run my python script when I click a button and it will take input from a textfield and then run my script which has multiple functions. Is this the right way to add inner functions or is there another way to do this?
ERROR
File "server.py", line 17``
pass = request.form['typed']here
^
SyntaxError: invalid syntax
[1]: https://i.stack.imgur.com/H6Mg2.png
from flask import Flask, render_template, url_for, request, redirect
app= Flask(__name__)
#app.route('/')`enter code here`
def my_home():
return render_template('index.html')
#app.route('/<string:page_name>')
def html_page(page_name):
return render_template(page_name)
import hashlib
import sys
import requests
#app.route('/send',methods =['POST'])
def send():
if request.method == 'POST':
pass = request.form['typed']
def request_api_data(query_char)
res = requests.get(url)
if res.status_code != 200:
raise RuntimeError(f'Error fetching: {res.status_code}, check the api and try again')
return res
pass is a statement and cannot be used as a variable name.
https://docs.python.org/3/tutorial/controlflow.html#pass-statements
from flask import Flask, render_template, url_for, request, redirect
app= Flask(__name__)
#app.route('/')`enter code here`
def my_home():
return render_template('index.html')
#app.route('/<string:page_name>')
def html_page(page_name):
return render_template(page_name)
import hashlib
import sys
import requests
#app.route('/send',methods =['POST'])
def send():
if request.method == 'POST':
# pass = request.form['typed']
my_pass = request.form['typed'] # <- this should work.
def request_api_data(query_char)
res = requests.get(url)
if res.status_code != 200:
raise RuntimeError(f'Error fetching: {res.status_code}, check the api and try again')
return res

Google Drive API Webhook

I have set up a google drive webhook through the "watch property"(https://developers.google.com/drive/api/v2/reference/files/watch) and it is working well and submitting a response as soon as any changes are detected on the watch file. However, the request body (i.e.posted_data=request.get_data( )) as below comes back empty (i.e. None). I have tried other options such as request.json but still empty. Does anyone have any ideas on what I am possibly doing wrong? My Python Flask webhook code is below and works well (i.e. any file updates are posted) except that it returns an empty data type (i.e.posted_data=request.get_data( ) is None). Any suggestions are highly appreciated!
from datetime import datetime
from flask import Flask, request, jsonify
import pytz
def get_timestamp():
dt=datetime.now(pytz.timezone('US/Central'))
return dt.strftime(("%Y-%m-%d %H:%M:%S"))
app = Flask(__name__)
#app.route('/webhook', methods=['POST','GET'])
def webhook():
if request.method=='GET':
return '<h1> This is a webhook listener!</h1>'
if request.method == 'POST':
posted_data=request.get_data( )
print("We have received a request =====>",posted_data)
cur_date=get_timestamp()
print("Date and time of update ====>",cur_date)
http_status=jsonify({'status':'success'}),200
else:
http_status='',400
return http_status
if __name__ == '__main__':
app.run(port=5000)
The above code works except that google will post their response as headers (i.e. request.headers). See updated code below.
from datetime import datetime
from flask import Flask, request, jsonify
import pytz
def get_timestamp():
dt=datetime.now(pytz.timezone('US/Central'))
return dt.strftime(("%Y-%m-%d %H:%M:%S"))
app = Flask(__name__)
#app.route('/webhook', methods=['POST','GET'])
def webhook():
if request.method=='GET':
return '<h1> This is a webhook listener!</h1>'
if request.method == 'POST':
print(request.headers)
cur_date=get_timestamp()
print("Date and time of update ====>",cur_date)
http_status=jsonify({'status':'success'}),200
else:
http_status='',400
return http_status
if __name__ == '__main__':
app.run(port=5000)

Flask JWT Extended cookie name overide Flask Session Cookie Name

I am using the Flask JWT Extended extension for flask and have built a login application successfully using JWT. I have gone through the tutorial on JWT in Cookies on the JWT extended documentation site using CSRF protection and everything.
What I can't seem to figure out is when using the set_access_cookies() and set_refresh_cookies() methods the JWTs are not saved in a httponly cookie named using the JWT Extended default configuration setting.
app.config.setdefault('JWT_ACCESS_COOKIE_NAME', 'access_token_cookie')
app.config.setdefault('JWT_REFRESH_COOKIE_NAME', 'refresh_token_cookie')
Instead when I debug the return back from the auth call the cookies are saved in the base Flask default configuration instead.
'SESSION_COOKIE_NAME': 'session',
Shouldn't the set_access_cookies() and set_refresh_cookies() methods override the base Flask default configurations as long as make sure to register my app in the JWTManager()?
uscc_login_app = Flask(__name__)
jwt = JWTManager(uscc_login_app)
Or is there something else I missed in the base Flask JWT Extended documentation to ensure that its configuration defaults are used when appropriate?
Updated code via request.
The code is pretty spread out but here is my best shot to include what I think will help.
In init.py:
from flask import Flask, url_for
from flask_restful import Api
from flask_jwt_extended import JWTManager
from resources.auth import Authenticate
from resources.refresh import Refresh
from temp_app.temp import TempApp
from uscc_login.uscc_app_login import *
uscc_login_app = Flask(__name__)
uscc_login_app.config.from_object(os.environ.get('FLASK_ENV'))
jwt = JWTManager(uscc_login_app)
api = Api(uscc_login_app, prefix='/v1')
# Add resources via the add_resource method
api.add_resource(Authenticate, '/login')
api.add_resource(Refresh, '/refresh_token')
login_view = Login.as_view(name='uscc_login')
uscc_login_app.add_url_rule('/login', view_func=login_view, methods=['POST', 'GET'])
In my app.py:
from uscc_login import uscc_login_app
if __name__ == '__main__':
uscc_login_app.run(debug=uscc_login_app.config.get('DEBUG'), threaded=uscc_login_app.config.get('THREADED'),
port=uscc_login_app.config.get('PORT'), host=uscc_login_app.config.get('HOST'))
In my config.py since I am using the Flask config.from_objects
import os
import datetime
uscc_login_app_dir = os.path.abspath(os.path.dirname(__file__))
class BaseConfig:
SECRET_KEY = os.environ.get('USCC_SECRET_KEY') or 'you-will-never-guess'
JWT_SECRET_KEY = os.environ.get('USCC_JWT_KEY') or 'super-secret'
JWT_TOKEN_LOCATION = ['cookies']
JWT_COOKIE_CSRF_PROTECT = True
JWT_HEADER_TYPE = 'JWT'
PROPAGATE_EXCEPTIONS = True
THREADED = True
class DevelopmentConfig(BaseConfig):
DEBUG = True
PORT = 5000 if os.environ.get("PORT") is None else int(os.environ.get("PORT"))
HOST = os.environ.get('HOST') or 'localhost'
if os.environ.get('access_token_expiration') is not None:
JWT_ACCESS_TOKEN_EXPIRES = datetime.timedelta(seconds=int(os.environ.get('access_token_expiration')))
if os.environ.get('refresh_token_expiration') is not None:
JWT_REFRESH_TOKEN_EXPIRES = datetime.timedelta(seconds=int(os.environ.get('refresh_token_expiration')))
So then in my Flask MethodView that contains my login authorization POST I have the following:
auth.py
import sys
import os
from flask import jsonify, request
from flask_restful import Resource
from flask_jwt_extended import create_access_token, create_refresh_token, jwt_refresh_token_required, get_jwt_identity, \
set_access_cookies, set_refresh_cookies
from utilities import Common
class Authenticate(Resource):
#staticmethod
def post():
"""
:return:
"""
api_cred_path = os.environ.get('api_cred_path')
if api_cred_path is None:
response = jsonify({"msg": "Environment Variable 'api_cred_path' is not set."})
response.status_code = 500
return response
if not request.is_json:
response = jsonify({'msg': 'Missing JSON in request'})
response.status_code = 400
return response
params = request.get_json()
user_name = params.get('username')
user_password = params.get('password')
if not user_name:
response = jsonify({'msg': 'Missing username parameter'})
response.status_code = 400
return response
if not user_password:
response = jsonify({'msg': 'Missing password parameter'})
response.status_code = 400
return response
if Common.check_path_exists(api_cred_path):
with open(api_cred_path) as afh:
for line in afh:
file_userid, file_password = line.split('=')
if file_userid == user_name and file_password.strip('\n') == user_password:
access_token = create_access_token(identity=user_name)
refresh_token = create_refresh_token(identity=user_name)
response = jsonify({'login': True})
set_access_cookies(response, access_token)
set_refresh_cookies(response, refresh_token)
# # Identity can be any data that is json serializable
# art = {
# 'access_token': create_access_token(identity=user_name),
# 'refresh_token': create_refresh_token(identity=user_name)}
# response = jsonify(art)
response.status_code = 200
return response
else:
response = jsonify({"msg": "api_cred_path invalid."})
response.status_code = 500
return response
response = jsonify({'msg': 'Bad username or password'})
response.status_code = 401
return response
Could you provide some code to duplicate what you are seeing? When I try running the example token in jwt code (https://github.com/vimalloc/flask-jwt-extended/blob/master/examples/jwt_in_cookie.py) I see the expected cookie values when I login:
$ http :5000/token/auth username=test password=test
...
Set-Cookie: access_token_cookie=<jwt>; HttpOnly; Path=/api/
Set-Cookie: refresh_token_cookie=<jwt>; HttpOnly; Path=/token/refresh
...
So I realized my mistake in this. I was trying to get the access_token_cookie variable to be set from my auth.py which serves as my RESTFUL based microservice of which my login app calls to do the authorization. Realized it won't be available after redirecting back to the caller from the login apps POST method since the cookie was related to the login app UI frontend. So I just base the access and refresh tokens back from the auth.py POST method to the login POST method and let it set the cookies so they are available to the end client.
This was more of design problem than a code problem.

Resources