Skip to content

Commit be4f77c

Browse files
committed
added py and requirement.txt files
0 parents  commit be4f77c

File tree

2 files changed

+137
-0
lines changed

2 files changed

+137
-0
lines changed

get_email_webhook.py

Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
import os
2+
import json
3+
import hmac
4+
import hashlib
5+
import requests
6+
from msal import ConfidentialClientApplication
7+
8+
# --- Determine mode ---
9+
TEST_MODE = os.getenv("TEST_MODE", "true").lower() == "true"
10+
11+
# Only import Flask if not in TEST_MODE
12+
if not TEST_MODE:
13+
from flask import Flask, request
14+
app = Flask(__name__)
15+
16+
# --- Microsoft Graph Email Sending Function ---
17+
def send_email_via_graph(subject, body):
18+
TENANT_ID = os.getenv("TENANT_ID")
19+
CLIENT_ID = os.getenv("CLIENT_ID")
20+
CLIENT_SECRET = os.getenv("CLIENT_SECRET")
21+
FROM_EMAIL = os.getenv("FROM_EMAIL")
22+
TO_EMAIL = os.getenv("TO_EMAIL")
23+
24+
if not all([TENANT_ID, CLIENT_ID, CLIENT_SECRET, FROM_EMAIL, TO_EMAIL]):
25+
print("❌ Missing required environment variables")
26+
return
27+
28+
try:
29+
app_msal = ConfidentialClientApplication(
30+
CLIENT_ID,
31+
authority=f"https://login.microsoftonline.com/{TENANT_ID}",
32+
client_credential=CLIENT_SECRET
33+
)
34+
token = app_msal.acquire_token_for_client(scopes=["https://graph.microsoft.com/.default"])
35+
access_token = token.get("access_token")
36+
if not access_token:
37+
print(f"❌ Failed to get access token: {token}")
38+
return
39+
40+
email_msg = {
41+
"message": {
42+
"subject": subject,
43+
"body": {"contentType": "Text", "content": body},
44+
"toRecipients": [{"emailAddress": {"address": TO_EMAIL}}]
45+
}
46+
}
47+
48+
response = requests.post(
49+
f"https://graph.microsoft.com/v1.0/users/{FROM_EMAIL}/sendMail",
50+
headers={"Authorization": f"Bearer {access_token}", "Content-Type": "application/json"},
51+
json=email_msg
52+
)
53+
54+
if response.status_code == 202:
55+
print(f"✅ Email sent to {TO_EMAIL}")
56+
else:
57+
print(f"❌ Failed to send email: {response.status_code} {response.text}")
58+
59+
except Exception as e:
60+
print(f"❌ Exception occurred while sending email: {e}")
61+
62+
# --- Verify GitHub webhook signature ---
63+
def verify_github_signature(payload_body, signature, secret):
64+
if not secret:
65+
print("⚠️ No webhook secret set, skipping verification")
66+
return True
67+
if not signature:
68+
print("❌ No signature provided in headers")
69+
return False
70+
71+
mac = hmac.new(secret.encode(), msg=payload_body, digestmod=hashlib.sha256)
72+
expected_signature = "sha256=" + mac.hexdigest()
73+
return hmac.compare_digest(expected_signature, signature)
74+
75+
# --- GitHub Webhook + Health Handlers ---
76+
if not TEST_MODE:
77+
@app.route("/webhook", methods=["POST"])
78+
def github_webhook():
79+
payload_body = request.data
80+
signature = request.headers.get("X-Hub-Signature-256")
81+
secret = os.getenv("GITHUB_WEBHOOK_SECRET") # ✅ Use webhook secret here
82+
83+
# --- Debug logging ---
84+
print("📥 Incoming GitHub webhook")
85+
print(f"📥 Payload size: {len(payload_body)} bytes")
86+
print(f"📥 GitHub Event: {request.headers.get('X-GitHub-Event')}")
87+
print(f"📥 Signature header: {signature}")
88+
print(f"📥 Secret length: {len(secret) if secret else 'None'}")
89+
90+
if not verify_github_signature(payload_body, signature, secret):
91+
print(f"❌ Invalid signature! Webhook rejected.")
92+
return "❌ Invalid signature", 401
93+
94+
try:
95+
data = request.json or {}
96+
except Exception as e:
97+
print(f"❌ Failed to parse JSON payload: {e}")
98+
return "❌ Bad payload", 400
99+
100+
event = request.headers.get("X-GitHub-Event", "")
101+
102+
if event == "repository" and data.get("action") in ["created", "deleted"]:
103+
repo_name = data["repository"]["full_name"]
104+
action = data["action"]
105+
106+
subject = f"[GitHub Alert] Repository {action}: {repo_name}"
107+
body = (
108+
f"A repository was {action} in your GitHub organization.\n\n"
109+
f"Details:\n{json.dumps(data, indent=2)}"
110+
)
111+
112+
print(f"📩 Sending email alert: {subject}")
113+
send_email_via_graph(subject, body)
114+
else:
115+
print(f"ℹ️ Ignored event: {event}, action: {data.get('action')}")
116+
117+
return "OK", 200
118+
119+
# Health check endpoint
120+
@app.route("/health", methods=["GET"])
121+
def health_check():
122+
return {"status": "running"}, 200
123+
124+
# --- Main Entry Point ---
125+
if __name__ == "__main__":
126+
if TEST_MODE:
127+
print("🔹 TEST_MODE: sending test email")
128+
send_email_via_graph(
129+
"[Test] Graph Email",
130+
"This is a test email sent via Microsoft Graph with application permissions."
131+
)
132+
else:
133+
print("✅ Flask is up and listening on /webhook and /health")
134+
app.run(host="0.0.0.0", port=5000)

requirements.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
flask
2+
requests
3+
msal

0 commit comments

Comments
 (0)