Compare commits
14 Commits
1ddd966b38
...
master
Author | SHA1 | Date | |
---|---|---|---|
c3141df775 | |||
0217d98d4f | |||
62be2d30f3 | |||
0c113739ee | |||
a04f24fcff | |||
8c0872ea1a | |||
de0a1809a4 | |||
28b09e374a | |||
c3d363e15a | |||
346c57d2b9 | |||
2451d5b000 | |||
3ff1800869 | |||
683a4b487e | |||
b8267c2040 |
@@ -1,2 +1,3 @@
|
|||||||
DOMAIN =
|
BRANCH=
|
||||||
EMAIL =
|
TOKEN=
|
||||||
|
WEBHOOK_SECRET=
|
@@ -1,11 +1,11 @@
|
|||||||
FROM python:slim
|
FROM tiangolo/uvicorn-gunicorn-fastapi:python3.7
|
||||||
|
|
||||||
|
|
||||||
ENV DOCKER=1
|
ENV DOCKER=1
|
||||||
|
|
||||||
RUN pip install aiohttp
|
RUN pip install fastapi-responses
|
||||||
|
|
||||||
|
|
||||||
COPY src /src
|
COPY ./app /app/app
|
||||||
|
|
||||||
CMD [ "python", "/src/main.py"]
|
|
||||||
|
|
||||||
|
5
Pipfile
5
Pipfile
@@ -11,11 +11,10 @@ requests = "*"
|
|||||||
fastapi = "*"
|
fastapi = "*"
|
||||||
uvicorn = {extras = ["standard"], version = "*"}
|
uvicorn = {extras = ["standard"], version = "*"}
|
||||||
fastapi-responses = "*"
|
fastapi-responses = "*"
|
||||||
python-dotenv = "*"
|
|
||||||
|
|
||||||
[requires]
|
[requires]
|
||||||
python_version = "3.8"
|
python_version = "3.8"
|
||||||
|
|
||||||
[scripts]
|
[scripts]
|
||||||
test = "pytest src/test.py -s --capture=sys"
|
test = "pytest app/test/test.py -s --capture=sys"
|
||||||
dev = "uvicorn src/main:app --reload"
|
dev = "uvicorn app.main:app --reload"
|
||||||
|
3
Pipfile.lock
generated
3
Pipfile.lock
generated
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"_meta": {
|
"_meta": {
|
||||||
"hash": {
|
"hash": {
|
||||||
"sha256": "3316652f79feb318e6104f936f3380ec7feee77cab7033b07666e40461611dc6"
|
"sha256": "4ce5e23dc521f0bcb803b2fa8327b19f956a56bc4267a13b1a0dd644af024078"
|
||||||
},
|
},
|
||||||
"pipfile-spec": 6,
|
"pipfile-spec": 6,
|
||||||
"requires": {
|
"requires": {
|
||||||
@@ -109,7 +109,6 @@
|
|||||||
"sha256:aae25dc1ebe97c420f50b81fb0e5c949659af713f31fdb63c749ca68748f34b1",
|
"sha256:aae25dc1ebe97c420f50b81fb0e5c949659af713f31fdb63c749ca68748f34b1",
|
||||||
"sha256:f521bc2ac9a8e03c736f62911605c5d83970021e3fa95b37d769e2bbbe9b6172"
|
"sha256:f521bc2ac9a8e03c736f62911605c5d83970021e3fa95b37d769e2bbbe9b6172"
|
||||||
],
|
],
|
||||||
"index": "pypi",
|
|
||||||
"version": "==0.19.0"
|
"version": "==0.19.0"
|
||||||
},
|
},
|
||||||
"pyyaml": {
|
"pyyaml": {
|
||||||
|
15
README.md
15
README.md
@@ -1,2 +1,15 @@
|
|||||||
# webhook
|
# Github Web Hook
|
||||||
|
|
||||||
|
Example of how to use Github web hooks using Fastapi
|
||||||
|
|
||||||
|
```pipenv sync```
|
||||||
|
|
||||||
|
```pipenv run test```
|
||||||
|
|
||||||
|
```cp .env.sample .env```
|
||||||
|
|
||||||
|
```pipenv run dev```
|
||||||
|
|
||||||
|
```docker-compose up --build```
|
||||||
|
|
||||||
|
|
||||||
|
0
app/__init__.py
Normal file
0
app/__init__.py
Normal file
@@ -1,26 +1,17 @@
|
|||||||
import os
|
from os import getenv
|
||||||
import hmac
|
import hmac
|
||||||
from fastapi import Request
|
from fastapi import Request
|
||||||
from fastapi.exceptions import HTTPException
|
from fastapi.exceptions import HTTPException
|
||||||
from fastapi.param_functions import Header
|
|
||||||
from dotenv import load_dotenv
|
|
||||||
|
|
||||||
load_dotenv()
|
|
||||||
|
|
||||||
async def check_ref(request: Request):
|
|
||||||
json = await request.json()
|
|
||||||
if json["ref"] and json["ref"] == f"refs/heads/{os.environ.get('BRANCH')}":
|
|
||||||
return
|
|
||||||
raise HTTPException(status_code=403, detail="Invalid branch")
|
|
||||||
|
|
||||||
async def auth_hook(request: Request):
|
async def auth_hook(request: Request):
|
||||||
try:
|
try:
|
||||||
json = await request.json()
|
|
||||||
text = await request.body()
|
text = await request.body()
|
||||||
|
json = await request.json()
|
||||||
except:
|
except:
|
||||||
raise HTTPException(status_code=204, detail="Missing or bad content")
|
raise HTTPException(status_code=204, detail="Missing or bad content")
|
||||||
header_signature = request.headers.get('X-Hub-Signature')
|
|
||||||
|
|
||||||
|
|
||||||
|
header_signature = request.headers.get('X-Hub-Signature')
|
||||||
if not header_signature:
|
if not header_signature:
|
||||||
raise HTTPException(status_code=400, detail="Missing signature")
|
raise HTTPException(status_code=400, detail="Missing signature")
|
||||||
|
|
||||||
@@ -29,7 +20,7 @@ async def auth_hook(request: Request):
|
|||||||
if sha_name != 'sha1':
|
if sha_name != 'sha1':
|
||||||
raise HTTPException(status_code=400, detail="Invalid signature")
|
raise HTTPException(status_code=400, detail="Invalid signature")
|
||||||
|
|
||||||
secret_key = os.environ.get('WEBHOOK_SECRET')
|
secret_key = getenv('WEBHOOK_SECRET')
|
||||||
if secret_key is None:
|
if secret_key is None:
|
||||||
raise HTTPException(status_code=503, detail="Missing WEBHOOK_SECRET")
|
raise HTTPException(status_code=503, detail="Missing WEBHOOK_SECRET")
|
||||||
|
|
||||||
@@ -37,14 +28,14 @@ async def auth_hook(request: Request):
|
|||||||
mac = hmac.new(secret_key.encode(), msg=text, digestmod='sha1')
|
mac = hmac.new(secret_key.encode(), msg=text, digestmod='sha1')
|
||||||
|
|
||||||
# verify the digest matches the signature
|
# verify the digest matches the signature
|
||||||
|
print(f'{mac.hexdigest()} {signature}')
|
||||||
if not hmac.compare_digest(mac.hexdigest(), signature):
|
if not hmac.compare_digest(mac.hexdigest(), signature):
|
||||||
raise HTTPException(status_code=403, detail="Unauthorized")
|
raise HTTPException(status_code=403, detail="Unauthorized")
|
||||||
|
|
||||||
async def auth_web(request: Request):
|
async def auth_web(request: Request):
|
||||||
token = request._query_params.get("token")
|
token = request._query_params.get("token")
|
||||||
if token is None:
|
if token == None or token == "":
|
||||||
raise HTTPException(status_code=400, detail="Missing token")
|
raise HTTPException(status_code=400, detail="Missing token")
|
||||||
print(token, os.environ.get("TOKEN"))
|
if token == getenv("TOKEN"):
|
||||||
if token == os.environ.get("TOKEN"):
|
|
||||||
return
|
return
|
||||||
raise HTTPException(status_code=403, detail="Invalid token")
|
raise HTTPException(status_code=403, detail="Invalid token")
|
16
app/main.py
Normal file
16
app/main.py
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
from fastapi import FastAPI, Request, Depends
|
||||||
|
from fastapi_responses import custom_openapi
|
||||||
|
from app.dependencies import auth_hook, auth_web
|
||||||
|
|
||||||
|
app = FastAPI()
|
||||||
|
|
||||||
|
app.openapi = custom_openapi(app)
|
||||||
|
|
||||||
|
@app.get("/", dependencies=[Depends(auth_web)])
|
||||||
|
@app.post("/", dependencies=[Depends(auth_hook)])
|
||||||
|
async def hook(req: Request):
|
||||||
|
json = await req.json()
|
||||||
|
print(json)
|
||||||
|
return "Update"
|
||||||
|
|
||||||
|
|
3
app/test/__init__.py
Normal file
3
app/test/__init__.py
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
import sys
|
||||||
|
import os
|
||||||
|
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))
|
@@ -2,17 +2,15 @@ from fastapi import FastAPI, Request, Depends
|
|||||||
|
|
||||||
from fastapi.testclient import TestClient
|
from fastapi.testclient import TestClient
|
||||||
import hmac
|
import hmac
|
||||||
|
from app.main import app
|
||||||
from starlette.routing import request_response
|
from app.dependencies import auth_hook, auth_web
|
||||||
from main import app
|
from os import environ, getenv
|
||||||
from auth import auth_hook, auth_web, check_ref
|
|
||||||
from os import environ
|
|
||||||
import json
|
import json
|
||||||
|
|
||||||
environ['WEBHOOK_SECRET'] = "dfsgdsjghhgdaehlsdfjhjkdh"
|
environ['WEBHOOK_SECRET'] = "dfsgdsjghhgdaehlsdfjhjkdh"
|
||||||
environ["BRANCH"] = "master"
|
environ["BRANCH"] = "master"
|
||||||
environ["TOKEN"] = "assdcvfgvh"
|
environ["TOKEN"] = "assdcvfgvh"
|
||||||
secret_key = environ.get('WEBHOOK_SECRET')
|
secret_key = getenv('WEBHOOK_SECRET')
|
||||||
|
|
||||||
client = TestClient(app)
|
client = TestClient(app)
|
||||||
|
|
||||||
@@ -20,10 +18,6 @@ client = TestClient(app)
|
|||||||
async def auth_test_handler(request: Request):
|
async def auth_test_handler(request: Request):
|
||||||
return 200
|
return 200
|
||||||
|
|
||||||
@app.post("/test_ref", dependencies=[Depends(check_ref)])
|
|
||||||
async def auth_test_handler(request: Request):
|
|
||||||
return 200
|
|
||||||
|
|
||||||
@app.get("/test_web", dependencies=[Depends(auth_web)])
|
@app.get("/test_web", dependencies=[Depends(auth_web)])
|
||||||
async def web_test_hnadler(request: Request):
|
async def web_test_hnadler(request: Request):
|
||||||
return 200
|
return 200
|
||||||
@@ -52,18 +46,8 @@ def test_auth():
|
|||||||
assert response.status_code == 403
|
assert response.status_code == 403
|
||||||
assert response.text == '{"detail":"Unauthorized"}'
|
assert response.text == '{"detail":"Unauthorized"}'
|
||||||
|
|
||||||
|
|
||||||
def test_branch():
|
|
||||||
payload = {"ref": "refs/heads/master"}
|
|
||||||
response = client.post("/test_ref", json= payload)
|
|
||||||
assert response.status_code == 200
|
|
||||||
|
|
||||||
payload = {"ref": "refs/heads/test"}
|
|
||||||
response = client.post("/test_ref", json= payload)
|
|
||||||
assert response.status_code == 403
|
|
||||||
|
|
||||||
def test_web():
|
def test_web():
|
||||||
response = client.get('/test_web?token={}'.format(environ.get("TOKEN")))
|
response = client.get('/test_web?token={}'.format(getenv("TOKEN")))
|
||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
|
|
||||||
response = client.get('/test_web')
|
response = client.get('/test_web')
|
@@ -8,15 +8,15 @@ services:
|
|||||||
networks:
|
networks:
|
||||||
- caddy
|
- caddy
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
labels:
|
|
||||||
caddy: ${DOMAIN}
|
|
||||||
caddy.tls: ${EMAIL}
|
|
||||||
caddy.reverse_proxy: "{{upstreams 8080}}"
|
|
||||||
logging:
|
logging:
|
||||||
driver: "json-file"
|
driver: "json-file"
|
||||||
options:
|
options:
|
||||||
max-size: "1m"
|
max-size: "1m"
|
||||||
tty: true
|
tty: true
|
||||||
|
env_file:
|
||||||
|
.env
|
||||||
|
ports:
|
||||||
|
- 8000:80
|
||||||
|
|
||||||
networks:
|
networks:
|
||||||
caddy:
|
caddy:
|
||||||
|
25
src/main.py
25
src/main.py
@@ -1,25 +0,0 @@
|
|||||||
from os import environ
|
|
||||||
import os
|
|
||||||
from fastapi import FastAPI, Body, Request, Depends
|
|
||||||
import json
|
|
||||||
from fastapi.exceptions import HTTPException
|
|
||||||
|
|
||||||
from fastapi.param_functions import Header
|
|
||||||
from fastapi_responses import custom_openapi
|
|
||||||
from auth import auth_hook, auth_web, check_ref
|
|
||||||
|
|
||||||
|
|
||||||
if not os.environ.get("DOCKER"):
|
|
||||||
from dotenv import load_dotenv
|
|
||||||
load_dotenv
|
|
||||||
|
|
||||||
app = FastAPI()
|
|
||||||
|
|
||||||
app.openapi = custom_openapi(app)
|
|
||||||
|
|
||||||
@app.get("/", dependencies=[Depends(auth_web)])
|
|
||||||
@app.post("/", dependencies=[Depends(auth_hook), Depends(check_ref)])
|
|
||||||
async def hook(req: Request):
|
|
||||||
return "Update"
|
|
||||||
|
|
||||||
|
|
Reference in New Issue
Block a user