Skip to content

Commit ef19b79

Browse files
committed
Added gitea support
1 parent bfc6f89 commit ef19b79

3 files changed

Lines changed: 52 additions & 13 deletions

File tree

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# gitWebhook
22

3-
A Python library providing [Flask blueprints](https://flask.palletsprojects.com/en/3.0.x/blueprints/) for receiving GitHub or GitLab webhooks and acting upon them.
3+
A Python library providing [Flask blueprints](https://flask.palletsprojects.com/en/3.0.x/blueprints/) for receiving GitHub, GitLab or Gitea webhooks and acting upon them.
44
The library provides webhooks allowing for automatic deployment, testing and integrations.
55
However due to the open ended nature of the blueprint this behavior can be easily customized thanks to the very open ended class dependency tree.
66

@@ -9,7 +9,7 @@ However due to the open ended nature of the blueprint this behavior can be easil
99
1. Setup the webhook on the git side
1010
During the setup be sure to pay close attention to any opportunities to input any sort of secret key.
1111
You will need that key later if you want to enable webhook verification **THIS IS SOMETHING THAT I GREATLY ADVISE YOU DO**.
12-
For GitHub that would be the secret string that you provide [during creation](https://docs.github.com/en/webhooks/using-webhooks/creating-webhooks#creating-a-repository-webhook) and for GitLab that would be the [secret token](https://docs.gitlab.com/ee/user/project/integrations/webhooks.html#validate-payloads-by-using-a-secret-token).
12+
For GitHub that would be the secret string that you provide [during creation](https://docs.github.com/en/webhooks/using-webhooks/creating-webhooks#creating-a-repository-webhook), for GitLab that would be the [secret token](https://docs.gitlab.com/ee/user/project/integrations/webhooks.html#validate-payloads-by-using-a-secret-token) and for Gitea that would be the [authorization token](https://docs.gitea.com/usage/webhooks#authorization-header).
1313
2. Install this package
1414
- Using pip
1515

gitWebhook/webhook.py

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ def verifyGitlabRequest(request: Request, token:str) -> bool:
2222
"""Verify the GitLab token of a webhook request"""
2323
return request.headers.get(GITLAB_HEADER) == token
2424

25+
# TODO add IP whitelisting, limiting to one git type, automatic github IP whitelisting
26+
2527
class webhookBlueprint(Blueprint, gitWebhookBlueprintABC):
2628
"""Wrapper over the flask blueprint that creates an endpoint for receiving and processing git webhooks. Overwrite the processWebhook method to process the webhook data."""
2729

@@ -47,13 +49,18 @@ def receiveWebhook(self) -> Response:
4749
if GITHUB_HEADER in request.headers:
4850
if not verifyGithubRequest(request, self.webhookToken):
4951
if self.log is not None:
50-
self.log.warning("A request with an invalid GitHub signature")
52+
self.log.warning("A request with an invalid GitHub signaturez")
5153
abort(401)
52-
elif GITLAB_HEADER:
54+
elif GITLAB_HEADER in request.headers:
5355
if not verifyGitlabRequest(request, self.webhookToken):
5456
if self.log is not None:
5557
self.log.warning("A request with an invalid GitLab token")
5658
abort(401)
59+
elif request.authorization is not None: # basic authorization which is what Gitea uses
60+
if not str(request.authorization) == self.webhookToken:
61+
if self.log is not None:
62+
self.log.warning("A request with an invalid basic authorization")
63+
abort(401)
5764
else:
5865
if self.log is not None:
5966
self.log.warning("A request with no signature found")

test.py

Lines changed: 41 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import unittest
2-
from flask import Request, request, Flask
2+
from flask import Request, Flask
33
from gitWebhook.webhook import verifyGithubRequest, verifyGitlabRequest, webhookBlueprint
44
from gitWebhook.functionWebhook import functionWebhookBlueprint
55
import random
@@ -13,6 +13,7 @@ def __init__(self, token:str):
1313
self.data = random.randbytes(50)
1414
hash_object = hmacNew(token.encode("utf-8"), msg=self.data, digestmod=sha256)
1515
self.headers = {"X-Hub-Signature-256": f"sha256={hash_object.hexdigest()}", "X-Gitlab-Token":token}
16+
1617
def get_data(self):
1718
return self.data
1819

@@ -21,16 +22,20 @@ def setUp(self) -> None:
2122
self.validRequest = testingRequest(VALID_TOKEN)
2223
self.invalidRequest = testingRequest("12345")
2324
return super().setUp()
25+
2426
def testVerifyGithubRequest(self):
2527
self.assertTrue(verifyGithubRequest(self.validRequest, VALID_TOKEN))
2628
self.assertFalse(verifyGithubRequest(self.validRequest, "12345"))
2729
self.assertFalse(verifyGithubRequest(self.invalidRequest, VALID_TOKEN))
2830
self.assertFalse(verifyGithubRequest(self.invalidRequest, "12346"))
31+
2932
def testVerifyGitlabRequest(self):
3033
self.assertTrue(verifyGitlabRequest(self.validRequest, "1234"))
3134
self.assertFalse(verifyGitlabRequest(self.validRequest, "12345"))
3235
self.assertFalse(verifyGitlabRequest(self.invalidRequest, "1234"))
3336

37+
# TODO add tests for basic auth
38+
3439
class TestWehbookBlueprint(unittest.TestCase):
3540
def setUp(self) -> None:
3641
self.webhook = webhookBlueprint(VALID_TOKEN, name="valid")
@@ -41,27 +46,51 @@ def setUp(self) -> None:
4146
self.app.config.update({"TESTING": True})
4247
self.client = self.app.test_client()
4348
return super().setUp()
44-
def testReceiveWebhookValid(self):
49+
50+
def testReceiveWebhookValidGithub(self):
4551
request = testingRequest(VALID_TOKEN)
46-
resp = self.client.post("/valid/", headers=request.headers, data=request.data)
52+
limitedHeaders = {"X-Hub-Signature-256":request.headers["X-Hub-Signature-256"]}
53+
resp = self.client.post("/valid/", headers=limitedHeaders, data=request.data)
4754
self.assertEqual(resp.status_code, 415)
48-
request.headers["Content-Type"] = "application/json"
49-
resp = self.client.post("/valid/", headers=request.headers, data=request.data)
55+
limitedHeaders["Content-Type"] = "application/json"
56+
resp = self.client.post("/valid/", headers=limitedHeaders, data=request.data)
5057
self.assertEqual(resp.status_code, 400) #no json
58+
59+
def testReceiveWebhookValidGitlab(self):
60+
request = testingRequest(VALID_TOKEN)
61+
limitedHeaders = {"X-Gitlab-Token":request.headers["X-Gitlab-Token"]}
62+
resp = self.client.post("/valid/", headers=limitedHeaders, data=request.data)
63+
self.assertEqual(resp.status_code, 415)
64+
limitedHeaders["Content-Type"] = "application/json"
65+
resp = self.client.post("/valid/", headers=limitedHeaders, data=request.data)
66+
self.assertEqual(resp.status_code, 400)
67+
5168
def testReceiveWebhookInvalidNoCheck(self):
5269
request = testingRequest("123")
5370
resp = self.client.post("/noToken/", headers=request.headers, data=request.data)
5471
self.assertEqual(resp.status_code, 415)
5572
request.headers["Content-Type"] = "application/json"
5673
resp = self.client.post("/noToken/", headers=request.headers, data=request.data)
5774
self.assertEqual(resp.status_code, 400)
58-
def testReceiveWebhookInvalidCheck(self):
75+
76+
def testReceiveWebhookInvalidCheckGithub(self):
5977
request = testingRequest("123")
60-
resp = self.client.post("/valid/", headers=request.headers, data=request.data)
78+
limitedHeaders = {"X-Hub-Signature-256":request.headers["X-Hub-Signature-256"]}
79+
resp = self.client.post("/valid/", headers=limitedHeaders, data=request.data)
6180
self.assertEqual(resp.status_code, 415)
62-
request.headers["Content-Type"] = "application/json"
63-
resp = self.client.post("/valid/", headers=request.headers, data=request.data)
81+
limitedHeaders["Content-Type"] = "application/json"
82+
resp = self.client.post("/valid/", headers=limitedHeaders, data=request.data)
6483
self.assertEqual(resp.status_code, 401)
84+
85+
def testReceiveWebhookInvalidCheckGitlab(self):
86+
request = testingRequest("123")
87+
limitedHeaders = {"X-Gitlab-Token":request.headers["X-Gitlab-Token"]}
88+
resp = self.client.post("/valid/", headers=limitedHeaders, data=request.data)
89+
self.assertEqual(resp.status_code, 415)
90+
limitedHeaders["Content-Type"] = "application/json"
91+
resp = self.client.post("/valid/", headers=limitedHeaders, data=request.data)
92+
self.assertEqual(resp.status_code, 401)
93+
6594
def testProcessWebhook(self):
6695
self.assertEqual(self.webhook.processWebhook({"test":"test"}), (200, "OK"))
6796
self.assertEqual(self.webhookNoToken.processWebhook({"test":"test"}), (200, "OK"))
@@ -75,20 +104,23 @@ def setUp(self) -> None:
75104
self.app.config.update({"TESTING": True})
76105
self.client = self.app.test_client()
77106
return super().setUp()
107+
78108
def testReceiveWebhookValid(self):
79109
request = testingRequest(VALID_TOKEN)
80110
resp = self.client.post("/valid/", headers=request.headers, data=request.data)
81111
self.assertEqual(resp.status_code, 415)
82112
request.headers["Content-Type"] = "application/json"
83113
resp = self.client.post("/valid/", headers=request.headers, data=request.data)
84114
self.assertEqual(resp.status_code, 400) #no json
115+
85116
def testReceiveWebhookInvalidCheck(self):
86117
request = testingRequest("123")
87118
resp = self.client.post("/valid/", headers=request.headers, data=request.data)
88119
self.assertEqual(resp.status_code, 415)
89120
request.headers["Content-Type"] = "application/json"
90121
resp = self.client.post("/valid/", headers=request.headers, data=request.data)
91122
self.assertEqual(resp.status_code, 401)
123+
92124
def testProcessWebhook(self):
93125
self.assertEqual(self.webhook.processWebhook({"test":"test"}), (400, "Function <lambda> returned false"))
94126

0 commit comments

Comments
 (0)