Repository: bradshjg/flask-githubapp

Issue:
Exception on / [POST]
I'm getting some some errors and I'm not really sure where they're coming from or how to debug. Is this a lib issue or an issue with my setup?
```
Traceback (most recent call last):
  File "/app/site-packages/flask/app.py", line 2447, in wsgi_app
    response = self.full_dispatch_request()
  File "/app/site-packages/flask/app.py", line 1952, in full_dispatch_request
    rv = self.handle_user_exception(e)
  File "/app/site-packages/flask/app.py", line 1821, in handle_user_exception
    reraise(exc_type, exc_value, tb)
  File "/app/site-packages/flask/_compat.py", line 39, in reraise
    raise value
  File "/app/site-packages/flask/app.py", line 1950, in full_dispatch_request
    rv = self.dispatch_request()
  File "/app/site-packages/flask/app.py", line 1936, in dispatch_request
    return self.view_functions[rule.endpoint](**req.view_args)
  File "/app/site-packages/flask_githubapp/core.py", line 169, in _flask_view_func
    event = request.headers['X-GitHub-Event']
  File "/app/site-packages/werkzeug/datastructures.py", line 1463, in __getitem__
    return _unicodify_header_value(self.environ["HTTP_" + key])
KeyError: 'HTTP_X_GITHUB_EVENT'
```

Discussion:
bradshjg: @Napo2k No pressure to review this, but any review certainly appreciated 😄 

At some point I'd love to refactor this library, but this implementation is kinda trying to follow in the spirit of the existing code (for better or worse).

Patch:
diff --git a/flask_githubapp/core.py b/flask_githubapp/core.py
index 3072a87..661f8d0 100644
--- a/flask_githubapp/core.py
+++ b/flask_githubapp/core.py
@@ -12,6 +12,14 @@ STATUS_FUNC_CALLED = 'HIT'
 STATUS_NO_FUNC_CALLED = 'MISS'
 
 
+class GitHubAppError(Exception):
+    pass
+
+
+class GitHubAppValidationError(GitHubAppError):
+    pass
+
+
 class GitHubApp(object):
     """The GitHubApp object provides the central interface for interacting GitHub hooks
     and creating GitHub app clients.
@@ -163,30 +171,37 @@ class GitHubApp(object):
 
         return decorator
 
-    def _flask_view_func(self):
-        functions_to_call = []
-        calls = {}
-
+    def _validate_request(self):
         if not request.is_json:
-            error_message = 'Invalid HTTP Content-Type header for JSON body ' \
-                            '(must be application/json or application/*+json).'
-            LOG.error(error_message)
-            error_response = make_response(jsonify(status='ERROR', description=error_message),
-                                           400)
-            abort(error_response)
+            raise GitHubAppValidationError('Invalid HTTP Content-Type header for JSON body '
+                                           '(must be application/json or application/*+json).')
 
         try:
             request.json
         except BadRequest:
-            error_message = 'Invalid HTTP body (must be JSON).'
-            LOG.error(error_message)
-            error_response = make_response(jsonify(status='ERROR', description=error_message),
-                                           400)
-            abort(error_response)
+            raise GitHubAppValidationError('Invalid HTTP body (must be JSON).')
+
+        event = request.headers.get('X-GitHub-Event')
+
+        if event is None:
+            raise GitHubAppValidationError('Missing X-GitHub-Event HTTP header.')
 
-        event = request.headers['X-GitHub-Event']
         action = request.json.get('action')
 
+        return event, action
+
+    def _flask_view_func(self):
+        functions_to_call = []
+        calls = {}
+
+        try:
+            event, action = self._validate_request()
+        except GitHubAppValidationError as e:
+            LOG.error(e)
+            error_response = make_response(jsonify(status='ERROR', description=str(e)),
+                                           400)
+            return abort(error_response)
+
         if current_app.config['GITHUBAPP_SECRET'] is not False:
             self._verify_webhook()
 
@@ -220,10 +235,10 @@ class GitHubApp(object):
         else:
             LOG.warning('Signature header missing. Configure your GitHub App with a secret or set GITHUBAPP_SECRET'
                         'to False to disable verification.')
-            abort(400)
+            return abort(400)
 
         mac = hmac.new(self.secret, msg=request.data, digestmod=digestmod)
 
         if not hmac.compare_digest(mac.hexdigest(), signature):
             LOG.warning('GitHub hook signature verification failed.')
-            abort(400)
+            return abort(400)


Test patch:
diff --git a/tests/test_core.py b/tests/test_core.py
index 593c568..32de120 100644
--- a/tests/test_core.py
+++ b/tests/test_core.py
@@ -276,3 +276,19 @@ def test_invalid_json_body_returns_error(github_app):
             'status': 'ERROR',
             'description': 'Invalid HTTP body (must be JSON).'
         }
+
+def test_missing_event_header_returns_error(github_app):
+    """HTTP request must havea X-GitHub-Event header"""
+    github_app.config['GITHUBAPP_SECRET'] = False
+    with github_app.test_client() as client:
+        resp = client.post('/',
+                           data=json.dumps({'installation': {'id': 2},
+                                            'action': 'bar'}),
+                           headers={
+                              'Content-Type': 'application/json'
+                           })
+        assert resp.status_code == 400
+        assert resp.json == {
+            'status': 'ERROR',
+            'description': 'Missing X-GitHub-Event HTTP header.'
+        }