GithubHelp home page GithubHelp logo

bloomberg / python-github-webhook Goto Github PK

View Code? Open in Web Editor NEW
285.0 9.0 71.0 195 KB

A framework for writing webhooks for GitHub, in Python.

Home Page: https://bloomberg.github.io/python-github-webhook

License: Apache License 2.0

Python 100.00%

python-github-webhook's Introduction

GitHub Webhook (micro) Framework

PyPI

python-github-webhook is a very simple, but powerful, microframework for writing GitHub webhooks in Python. It can be used to write webhooks for individual repositories or whole organisations, and can be used for GitHub.com or GitHub Enterprise installations; in fact, it was orginally developed for Bloomberg's GHE install.

Getting started

python-github-webhook is designed to be as simple as possible, to make a simple Webhook that receives push events all it takes is:

from github_webhook import Webhook
from flask import Flask

app = Flask(__name__)  # Standard Flask app
webhook = Webhook(app) # Defines '/postreceive' endpoint

@app.route("/")        # Standard Flask endpoint
def hello_world():
    return "Hello, World!"

@webhook.hook()        # Defines a handler for the 'push' event
def on_push(data):
    print("Got push with: {0}".format(data))

if __name__ == "__main__":
    app.run(host="0.0.0.0", port=80)

License

The python-github-webhook repository is distributed under the Apache License (version 2.0); see the LICENSE file at the top of the source tree for more information.

python-github-webhook's People

Contributors

alexchamberlain avatar fophillips avatar fspieler avatar kpfleming avatar mtreinish avatar netors avatar rascalking avatar sergioisidoro avatar shibanimahapatra avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

python-github-webhook's Issues

Version Webhook API To Support GraphQL alongside REST

Currently, REST (v3) is the officially supported GitHubb API. v4 was debuted in September 2016; at that time, GitHub projected roughly a year of beta usage before going GA. 18 months later, v4 still isn't GA, but one would assume that it is coming very shortly. In order to get ahead of that coming version, this library should add a mechanism to support both versions, with the default being v3 for some time.

I don't have an architecture already sketched out, but I'm willing to help in whatever way needed to help support this effort.

Allow hooks to return data

Currently, hooks are basically unable to influence the response returned to GitHub.

This is a valuable information and debugging channel.

Please allow return values from hooks to be passed on to github.

Extended documentation

The documentation (README.md) doesn't give out a lot.
To figure out something even as simple as validating the secret token, I'd to go through the source code. That's not ideal.
I'll be using the framework in a couple of projects. I can help with the documentation and the repo admins can review it if that's acceptable.

Request verification using SHA1 and secret fails

I tried using this to handle webhook requests and I kept seeing 400 Bad Request responses:

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
<title>400 Bad Request</title>
<h1>Bad Request</h1>
<p>Invalid signature</p>

After a bit of investigation I found out that this fails because request.data is empty:

def _get_digest(self):
"""Return message digest if a secret key was provided"""
return hmac.new(self._secret, request.data, hashlib.sha1).hexdigest() if self._secret else None

And what should be used instead is request.get_data(), since it returns value regardless off payload format used:

Request.get_data(cache=True, as_text=False, parse_form_data=False)
This reads the buffered incoming data from the client into one bytestring. By default this is cached but that behavior can be changed by setting cache to False.
https://tedboy.github.io/flask/generated/generated/flask.Request.get_data.html

Not sure how this worked before...

A Sample

Could you perhaps include a small sample? I'm new to webhooks and the Getting Started doesn't help too much ( imo ).

Make the package a Flask extention so it can be used with init_app

When making modular flask applications, it's common not to immediately provide the app in the constructor, but to pass it later on with the method init_app(app) as we can see in the documentation -- https://flask.palletsprojects.com/en/1.1.x/extensions/

With a bit of changes on the constructor this can become a "proper" flask extension
https://flask.palletsprojects.com/en/1.1.x/extensiondev/

The example of the SQLite extension provides an idea on how this could look like:

    def __init__(self, app=None):
        self.app = app
        if app is not None:
            self.init_app(app)

    def init_app(self, app):
        app.config.setdefault('SQLITE3_DATABASE', ':memory:')
        app.teardown_appcontext(self.teardown)

Add tests for problems exposed by cosmic-ray

job ID 1f37d3c49055482e8dbcea3a1d3f8830:survived:github_webhook.webhook
command: c o s m i c - r a y   w o r k e r   g i t h u b _ w e b h o o k . w e b h o o k   m u t a t e _ c o m p a r i s o n _ o p e r a t o r   1
--- mutation diff ---
--- a/Users/fphillips7/Code/python-github-webhook/github_webhook/webhook.py
+++ b/Users/fphillips7/Code/python-github-webhook/github_webhook/webhook.py
@@ -13,7 +13,7 @@
         app.add_url_rule(rule=endpoint, endpoint=endpoint, view_func=self._postreceive, methods=['POST'])
         self._hooks = collections.defaultdict(list)
         self._logger = logging.getLogger('webhook')
-        if ((secret is not None) and (not isinstance(secret, six.binary_type))):
+        if ((secret != None) and (not isinstance(secret, six.binary_type))):
             secret = secret.encode('utf-8')
         self._secret = secret


job ID 2e66309c18ed4f9c8953fc622f5f5211:survived:github_webhook.webhook
command: c o s m i c - r a y   w o r k e r   g i t h u b _ w e b h o o k . w e b h o o k   m u t a t e _ c o m p a r i s o n _ o p e r a t o r   1 0
--- mutation diff ---
--- a/Users/fphillips7/Code/python-github-webhook/github_webhook/webhook.py
+++ b/Users/fphillips7/Code/python-github-webhook/github_webhook/webhook.py
@@ -32,7 +32,7 @@
     def _postreceive(self):
         'Callback from Flask'
         digest = self._get_digest()
-        if (digest is not None):
+        if (digest != None):
             sig_parts = _get_header('X-Hub-Signature').split('=', 1)
             if (not isinstance(digest, six.text_type)):
                 digest = six.text_type(digest)

job ID 634be3d1ff434425bf254ead62935081:survived:github_webhook.webhook
command: c o s m i c - r a y   w o r k e r   g i t h u b _ w e b h o o k . w e b h o o k   m u t a t e _ c o m p a r i s o n _ o p e r a t o r   1 9
--- mutation diff ---
--- a/Users/fphillips7/Code/python-github-webhook/github_webhook/webhook.py
+++ b/Users/fphillips7/Code/python-github-webhook/github_webhook/webhook.py
@@ -36,7 +36,7 @@
             sig_parts = _get_header('X-Hub-Signature').split('=', 1)
             if (not isinstance(digest, six.text_type)):
                 digest = six.text_type(digest)
-            if ((len(sig_parts) < 2) or (sig_parts[0] != 'sha1') or (not hmac.compare_digest(sig_parts[1], digest))):
+            if ((len(sig_parts) != 2) or (sig_parts[0] != 'sha1') or (not hmac.compare_digest(sig_parts[1], digest))):
                 abort(400, 'Invalid signature')
         event_type = _get_header('X-Github-Event')
         data = request.get_json()

job ID 5ae6d0a91c2d4684b02aaa340d9d3eab:survived:github_webhook.webhook
command: c o s m i c - r a y   w o r k e r   g i t h u b _ w e b h o o k . w e b h o o k   m u t a t e _ c o m p a r i s o n _ o p e r a t o r   2 1
--- mutation diff ---
--- a/Users/fphillips7/Code/python-github-webhook/github_webhook/webhook.py
+++ b/Users/fphillips7/Code/python-github-webhook/github_webhook/webhook.py
@@ -36,7 +36,7 @@
             sig_parts = _get_header('X-Hub-Signature').split('=', 1)
             if (not isinstance(digest, six.text_type)):
                 digest = six.text_type(digest)
-            if ((len(sig_parts) < 2) or (sig_parts[0] != 'sha1') or (not hmac.compare_digest(sig_parts[1], digest))):
+            if ((len(sig_parts) > 2) or (sig_parts[0] != 'sha1') or (not hmac.compare_digest(sig_parts[1], digest))):
                 abort(400, 'Invalid signature')
         event_type = _get_header('X-Github-Event')
         data = request.get_json()

job ID 4a2be988d3894f3d8d9fdc46b55fa306:survived:github_webhook.webhook
command: c o s m i c - r a y   w o r k e r   g i t h u b _ w e b h o o k . w e b h o o k   m u t a t e _ c o m p a r i s o n _ o p e r a t o r   2 4
--- mutation diff ---
--- a/Users/fphillips7/Code/python-github-webhook/github_webhook/webhook.py
+++ b/Users/fphillips7/Code/python-github-webhook/github_webhook/webhook.py
@@ -36,7 +36,7 @@
             sig_parts = _get_header('X-Hub-Signature').split('=', 1)
             if (not isinstance(digest, six.text_type)):
                 digest = six.text_type(digest)
-            if ((len(sig_parts) < 2) or (sig_parts[0] != 'sha1') or (not hmac.compare_digest(sig_parts[1], digest))):
+            if ((len(sig_parts) is not 2) or (sig_parts[0] != 'sha1') or (not hmac.compare_digest(sig_parts[1], digest))):
                 abort(400, 'Invalid signature')
         event_type = _get_header('X-Github-Event')
         data = request.get_json()

job ID 3933a0d64fe546699d25aed4d58afece:survived:github_webhook.webhook
command: c o s m i c - r a y   w o r k e r   g i t h u b _ w e b h o o k . w e b h o o k   m u t a t e _ c o m p a r i s o n _ o p e r a t o r   2 8
--- mutation diff ---
--- a/Users/fphillips7/Code/python-github-webhook/github_webhook/webhook.py
+++ b/Users/fphillips7/Code/python-github-webhook/github_webhook/webhook.py
@@ -36,7 +36,7 @@
             sig_parts = _get_header('X-Hub-Signature').split('=', 1)
             if (not isinstance(digest, six.text_type)):
                 digest = six.text_type(digest)
-            if ((len(sig_parts) < 2) or (sig_parts[0] != 'sha1') or (not hmac.compare_digest(sig_parts[1], digest))):
+            if ((len(sig_parts) < 2) or (sig_parts[0] < 'sha1') or (not hmac.compare_digest(sig_parts[1], digest))):
                 abort(400, 'Invalid signature')
         event_type = _get_header('X-Github-Event')
         data = request.get_json()

job ID f8a284e6c67f4f669de506b73523cb11:survived:github_webhook.webhook
command: c o s m i c - r a y   w o r k e r   g i t h u b _ w e b h o o k . w e b h o o k   m u t a t e _ c o m p a r i s o n _ o p e r a t o r   3 0
--- mutation diff ---
--- a/Users/fphillips7/Code/python-github-webhook/github_webhook/webhook.py
+++ b/Users/fphillips7/Code/python-github-webhook/github_webhook/webhook.py
@@ -36,7 +36,7 @@
             sig_parts = _get_header('X-Hub-Signature').split('=', 1)
             if (not isinstance(digest, six.text_type)):
                 digest = six.text_type(digest)
-            if ((len(sig_parts) < 2) or (sig_parts[0] != 'sha1') or (not hmac.compare_digest(sig_parts[1], digest))):
+            if ((len(sig_parts) < 2) or (sig_parts[0] > 'sha1') or (not hmac.compare_digest(sig_parts[1], digest))):
                 abort(400, 'Invalid signature')
         event_type = _get_header('X-Github-Event')
         data = request.get_json()

job ID d7f60eb0989f4aacabd62e9086d87975:survived:github_webhook.webhook
command: c o s m i c - r a y   w o r k e r   g i t h u b _ w e b h o o k . w e b h o o k   m u t a t e _ c o m p a r i s o n _ o p e r a t o r   3 2
--- mutation diff ---
--- a/Users/fphillips7/Code/python-github-webhook/github_webhook/webhook.py
+++ b/Users/fphillips7/Code/python-github-webhook/github_webhook/webhook.py
@@ -36,7 +36,7 @@
             sig_parts = _get_header('X-Hub-Signature').split('=', 1)
             if (not isinstance(digest, six.text_type)):
                 digest = six.text_type(digest)
-            if ((len(sig_parts) < 2) or (sig_parts[0] != 'sha1') or (not hmac.compare_digest(sig_parts[1], digest))):
+            if ((len(sig_parts) < 2) or (sig_parts[0] is 'sha1') or (not hmac.compare_digest(sig_parts[1], digest))):
                 abort(400, 'Invalid signature')
         event_type = _get_header('X-Github-Event')
         data = request.get_json()

job ID 12a01fb33247430394d523dce2bdf91a:survived:github_webhook.webhook
command: c o s m i c - r a y   w o r k e r   g i t h u b _ w e b h o o k . w e b h o o k   m u t a t e _ c o m p a r i s o n _ o p e r a t o r   3 4
--- mutation diff ---
--- a/Users/fphillips7/Code/python-github-webhook/github_webhook/webhook.py
+++ b/Users/fphillips7/Code/python-github-webhook/github_webhook/webhook.py
@@ -36,7 +36,7 @@
             sig_parts = _get_header('X-Hub-Signature').split('=', 1)
             if (not isinstance(digest, six.text_type)):
                 digest = six.text_type(digest)
-            if ((len(sig_parts) < 2) or (sig_parts[0] != 'sha1') or (not hmac.compare_digest(sig_parts[1], digest))):
+            if ((len(sig_parts) < 2) or (sig_parts[0] not in 'sha1') or (not hmac.compare_digest(sig_parts[1], digest))):
                 abort(400, 'Invalid signature')
         event_type = _get_header('X-Github-Event')
         data = request.get_json()

job ID a300f614a83a4c109e266885c72fc6c0:survived:github_webhook.webhook
command: c o s m i c - r a y   w o r k e r   g i t h u b _ w e b h o o k . w e b h o o k   m u t a t e _ c o m p a r i s o n _ o p e r a t o r   3 5
--- mutation diff ---
--- a/Users/fphillips7/Code/python-github-webhook/github_webhook/webhook.py
+++ b/Users/fphillips7/Code/python-github-webhook/github_webhook/webhook.py
@@ -40,7 +40,7 @@
                 abort(400, 'Invalid signature')
         event_type = _get_header('X-Github-Event')
         data = request.get_json()
-        if (data is None):
+        if (data == None):
             abort(400, 'Request body must contain json')
         self._logger.info('%s (%s)', _format_event(event_type, data), _get_header('X-Github-Delivery'))
         for hook in self._hooks.get(event_type, []):

job ID 498e409e8cff49ceb7a6af2505d8ea98:survived:github_webhook.webhook
command: c o s m i c - r a y   w o r k e r   g i t h u b _ w e b h o o k . w e b h o o k   m u t a t e _ u n a r y _ o p e r a t o r   1
--- mutation diff ---
--- a/Users/fphillips7/Code/python-github-webhook/github_webhook/webhook.py
+++ b/Users/fphillips7/Code/python-github-webhook/github_webhook/webhook.py
@@ -34,7 +34,7 @@
         digest = self._get_digest()
         if (digest is not None):
             sig_parts = _get_header('X-Hub-Signature').split('=', 1)
-            if (not isinstance(digest, six.text_type)):
+            if isinstance(digest, six.text_type):
                 digest = six.text_type(digest)
             if ((len(sig_parts) < 2) or (sig_parts[0] != 'sha1') or (not hmac.compare_digest(sig_parts[1], digest))):
                 abort(400, 'Invalid signature')

job ID af7031344c8f4fb4ae68441982768390:survived:github_webhook.webhook
command: c o s m i c - r a y   w o r k e r   g i t h u b _ w e b h o o k . w e b h o o k   n u m b e r _ r e p l a c e r   0
--- mutation diff ---
--- a/Users/fphillips7/Code/python-github-webhook/github_webhook/webhook.py
+++ b/Users/fphillips7/Code/python-github-webhook/github_webhook/webhook.py
@@ -33,7 +33,7 @@
         'Callback from Flask'
         digest = self._get_digest()
         if (digest is not None):
-            sig_parts = _get_header('X-Hub-Signature').split('=', 1)
+            sig_parts = _get_header('X-Hub-Signature').split('=', 2)
             if (not isinstance(digest, six.text_type)):
                 digest = six.text_type(digest)
             if ((len(sig_parts) < 2) or (sig_parts[0] != 'sha1') or (not hmac.compare_digest(sig_parts[1], digest))):

job ID e6f95eb27afc43149a31b00e09224996:survived:github_webhook.webhook
command: c o s m i c - r a y   w o r k e r   g i t h u b _ w e b h o o k . w e b h o o k   n u m b e r _ r e p l a c e r   6
--- mutation diff ---
--- a/Users/fphillips7/Code/python-github-webhook/github_webhook/webhook.py
+++ b/Users/fphillips7/Code/python-github-webhook/github_webhook/webhook.py
@@ -45,7 +45,7 @@
         self._logger.info('%s (%s)', _format_event(event_type, data), _get_header('X-Github-Delivery'))
         for hook in self._hooks.get(event_type, []):
             hook(data)
-        return ('', 204)
+        return ('', 205)

 def _get_header(key):
     'Return message header'

job ID a3c71ec1bc31429c97003395b4ab6389:survived:github_webhook.webhook
command: c o s m i c - r a y   w o r k e r   g i t h u b _ w e b h o o k . w e b h o o k   r e p l a c e _ o r _ w i t h _ a n d   1
--- mutation diff ---
--- a/Users/fphillips7/Code/python-github-webhook/github_webhook/webhook.py
+++ b/Users/fphillips7/Code/python-github-webhook/github_webhook/webhook.py
@@ -36,7 +36,7 @@
             sig_parts = _get_header('X-Hub-Signature').split('=', 1)
             if (not isinstance(digest, six.text_type)):
                 digest = six.text_type(digest)
-            if ((len(sig_parts) < 2) or (sig_parts[0] != 'sha1') or (not hmac.compare_digest(sig_parts[1], digest))):
+            if (((len(sig_parts) < 2) and (sig_parts[0] != 'sha1')) or (not hmac.compare_digest(sig_parts[1], digest))):
                 abort(400, 'Invalid signature')
         event_type = _get_header('X-Github-Event')
         data = request.get_json()

total jobs: 79
complete: 79 (100.00%)
survival rate: 17.72%

Missing Git tags

Hi! Thanks for creating this.

The only Git tag I see in this repo is for 1.0.2, however versions 1.0.3 and 1.0.4 have been pushed to PyPI. Could tags be pushed for this and releases going forwards? This will make it easier for people to reproduce and verify that the published PyPI package matches what is in Git.

P.S. I'm planning to package this for Debian as well: https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=1068354

Can we use`"*"`to represent all events?

Hi, @bloomberg-oss

Is your feature request related to a problem? Please describe.
In the process of using, I found that many agents need to write corresponding functions separately, especially @webhook.hook() every time. Can I express all events in some way and get the types of events?

Describe the solution you'd like
I hope to use the * parameter to represent all events and give an event_type label.

@webhook.hook("*") #when any event  happened, e. g.  `push`
def event(data, event_type): 
    return event_type # event_type = "push"

I wrote a json configuration file with the following fields:

{
  "star": {
    "created": "{sender.login} starred repo. "
    }
}

the template is:

{
  "event_type": {
      "data.action": "..."
      }
}

if I read the json as config:

@webhook.hook("*")
def event(data, event_type): 
    logging.info(f"{config[event_type]} happened: Action is {config[data['action']]}")
    return "hello"

This could be activate

Typing

Would you be interested in shipping an auto-generated module that can be used as type annotation?

That way people could just do the following:

@webhook.hook()
def on_push(data: webhook.events.Push):
    data['<tab>

and get autocompletion in e.g. PyCharm.

Octokit provides a versioned schema: https://github.com/octokit/webhooks/blob/v3.11.2/index.json

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.