Skip to content

General improvement #34

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ You can configure what the application does by copying the sample config file
"github_ips_only": true,
"enforce_secret": "",
"return_scripts_info": true
"hooks_path": "/.../hooks/"
"hooks_path": "/absolute/path/to/your/hooks/"
}

:github_ips_only: Restrict application to be called only by GitHub IPs. IPs
Expand Down Expand Up @@ -140,7 +140,7 @@ of your WSGI script:

::

http://my.site.com/webhooks
http://my.site.com/webhooks/

Docker
------
Expand Down
15 changes: 13 additions & 2 deletions webhooks.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@
import logging
from sys import stderr, hexversion
logging.basicConfig(stream=stderr)
# If you need troubleshooting logs, comment the previous line and uncomment the next one
#logging.basicConfig(filename=/your/writable/path/for/hooks.log, level=10)

import hmac
from hashlib import sha1
Expand Down Expand Up @@ -45,6 +47,7 @@ def index():

# Only POST is implemented
if request.method != 'POST':
logging.warning("We got a $s request, this isn't supported", request.method)
abort(501)

# Load config
Expand All @@ -53,6 +56,8 @@ def index():

hooks = config.get('hooks_path', join(path, 'hooks'))

logging.info("Config loaded, handling request")

# Allow Github IPs only
if config.get('github_ips_only', True):
src_ip = ip_address(
Expand All @@ -64,6 +69,7 @@ def index():
if src_ip in ip_network(valid_ip):
break
else:
logging.warning("We got a request from unauthorized IP: %s", src_ip)
abort(403)

# Enforce secret
Expand All @@ -72,36 +78,41 @@ def index():
# Only SHA1 is supported
header_signature = request.headers.get('X-Hub-Signature')
if header_signature is None:
logging.warning("No signature found when expecting one")
abort(403)

sha_name, signature = header_signature.split('=')
if sha_name != 'sha1':
logging.warning("Unsupported signature mech: %s", sha_name)
abort(501)

# HMAC requires the key to be bytes, but data is string
mac = hmac.new(str(secret), msg=request.data, digestmod='sha1')
mac = hmac.new(str(secret), msg=request.data, digestmod=sha1)

# Python prior to 2.7.7 does not have hmac.compare_digest
if hexversion >= 0x020707F0:
if not hmac.compare_digest(str(mac.hexdigest()), str(signature)):
logging.warning("Invalid digest comparaison")
abort(403)
else:
# What compare_digest provides is protection against timing
# attacks; we can live without this protection for a web-based
# application
if not str(mac.hexdigest()) == str(signature):
logging.warning("Hex digest aren't equals")
abort(403)

# Implement ping
event = request.headers.get('X-GitHub-Event', 'ping')
if event == 'ping':
logging.warning("Ping Pong")
return dumps({'msg': 'pong'})

# Gather data
try:
payload = request.get_json()
except Exception:
logging.warning('Request parsing failed')
logging.warning('Request parsing failed with exception %s', Exception)
abort(400)

# Determining the branch is tricky, as it only appears for certain event
Expand Down