Skip to content

Commit

Permalink
Implement webhook for create/update event (#195)
Browse files Browse the repository at this point in the history
* implement sso login with ticket component

* hide register in login view

* fix isort

* disable https check setting

* fix black test

* implement webhook for organiser/team create/update/delete

* fix flake8

* fix flake8: remove unused import

* fix isort
  • Loading branch information
odkhang authored Sep 17, 2024
1 parent 61c207a commit e1fa4a7
Show file tree
Hide file tree
Showing 3 changed files with 306 additions and 0 deletions.
168 changes: 168 additions & 0 deletions src/pretalx/eventyay_common/tasks.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
import logging
from datetime import datetime

from celery import shared_task
from django.core.exceptions import ValidationError
from django.http import Http404
from django.shortcuts import get_object_or_404
from django_scopes import scopes_disabled

from pretalx.event.forms import TeamForm
from pretalx.event.models import Event, Organiser, Team

logger = logging.getLogger(__name__)


class Action:
CREATE = "create"
UPDATE = "update"
DELETE = "delete"


@shared_task
def process_organiser_webhook(organiser_data):
try:
action = organiser_data.get("action")
if action == Action.CREATE:
organiser = Organiser(
name=organiser_data.get("name"), slug=organiser_data.get("slug")
)
organiser.full_clean()
organiser.save()
logger.info(f"Organiser {organiser.name} created successfully.")
# Create an Administrator team for new organiser
team = TeamForm(
organiser=organiser,
data={
"name": "Administrators",
"all_events": True,
"can_create_events": True,
"can_change_teams": True,
"can_change_organiser_settings": True,
"can_change_event_settings": True,
"can_change_submissions": True,
},
)
if team.is_valid():
team.save()
logger.info(
f"Administrator team for organiser {organiser.name} created "
f"successfully."
)
else:
logger.error(
f"Error creating Administrator team for organiser {organiser.name}: {team.errors}"
)

elif action == Action.UPDATE:
organiser = Organiser.objects.get(slug=organiser_data.get("slug"))
organiser.name = organiser_data.get("name")
organiser.full_clean()
organiser.save()
logger.info(f"Organiser {organiser.name} updated successfully.")

elif action == Action.DELETE:
# Implement delete logic here
logger.info("Organiser delete not implemented yet.")
pass

else:
logger.error(f"Unknown action: {action}")
except ValidationError as e:
logger.error("Validation error:", e.message_dict)
except Exception as e:
logger.error("Error saving organiser:", e)


@shared_task
def process_team_webhook(team_data):
try:
action = team_data.get("action")
organiser = get_object_or_404(Organiser, slug=team_data.get("organiser_slug"))
if action == Action.CREATE:
team = TeamForm(organiser=organiser, data=team_data)
if team.is_valid():
team.save()
logger.info(
f"Team for organiser {organiser.name} created successfully."
)
else:
logger.error(
f"Error creating Administrator team for organiser {organiser.name}: {team.errors}"
)

elif action == Action.UPDATE:
team = Team.objects.filter(
organiser=organiser, name=team_data.get("old_name")
).first()
if not team:
raise Http404("No Team matches the given query.")
# Update the team object with new data from team_data
for field, value in team_data.items():
setattr(team, field, value)
team.save()
logger.info(f"Team for organiser {organiser.name} created successfully.")

elif action == Action.DELETE:
team = Team.objects.filter(
organiser=organiser, name=team_data.get("name")
).first()
if not team:
raise Http404("No Team matches the given query.")
team.delete()
logger.info(f"Team for organiser {organiser.name} deleted successfully.")

else:
logger.error(f"Unknown action: {action}")
except ValidationError as e:
logger.error("Validation error:", e.message_dict)
except Exception as e:
logger.error("Error saving organiser:", e)


@shared_task
def process_event_webhook(event_data):
try:
action = event_data.get("action")
organiser = get_object_or_404(Organiser, slug=event_data.get("organiser_slug"))
if action == Action.CREATE:
with scopes_disabled():
event = Event.objects.create(
organiser=organiser,
locale_array=",".join(event_data.get("locales")),
content_locale_array=",".join(event_data.get("locales")),
name=event_data.get("name"),
slug=event_data.get("slug"),
timezone=event_data.get("timezone"),
email=event_data.get("user_email"),
locale=event_data.get("locale"),
date_from=datetime.fromisoformat(event_data.get("date_from")),
date_to=datetime.fromisoformat(event_data.get("date_to")),
)
event.save()
elif action == Action.UPDATE:
event = Event.objects.filter(
organiser=organiser, slug=event_data.get("slug")
).first()
if not event:
raise Http404("No Event matches the given query.")
# Update the team object with new data from team_data
event.name = event_data.get("name")
event.date_from = datetime.fromisoformat(event_data["date_from"])
event.date_to = datetime.fromisoformat(event_data["date_to"])
event.locale_array = ",".join(event_data.get("locales"))
event.content_locale_array = ",".join(event_data.get("locales"))
event.timezone = event_data.get("timezone")
event.locale = event_data.get("locale")
event.save()
logger.info(f"Event for organiser {organiser.name} created successfully.")

elif action == Action.DELETE:
pass

else:
logger.error(f"Unknown action: {action}")
except ValidationError as e:
logger.error("Validation error:", e.message_dict)
except Exception as e:
logger.error("Error saving organiser:", e)
8 changes: 8 additions & 0 deletions src/pretalx/eventyay_common/urls.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,19 @@
from django.urls import include, path

from pretalx.eventyay_common.views import auth
from pretalx.eventyay_common.webhooks import (
event_webhook,
organiser_webhook,
team_webhook,
)

app_name = "eventyay_common"

urlpatterns = [
path("oauth2/", include("oauth2_provider.urls", namespace="oauth2_provider")),
path("login/", auth.oauth2_login_view, name="oauth2_provider.login"),
path("oauth2/callback/", auth.oauth2_callback, name="oauth2_callback"),
path("webhook/organiser/", organiser_webhook, name="webhook.organiser"),
path("webhook/team/", team_webhook, name="webhook.team"),
path("webhook/event/", event_webhook, name="webhook.event"),
]
130 changes: 130 additions & 0 deletions src/pretalx/eventyay_common/webhooks.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
import json

import jwt
from django.conf import settings
from django.http import JsonResponse
from django.views.decorators.csrf import csrf_exempt

from pretalx.eventyay_common.tasks import (
process_event_webhook,
process_organiser_webhook,
process_team_webhook,
)
from pretalx.person.models import User


@csrf_exempt
def organiser_webhook(request):
permission_required = ("orga.change_organiser_settings",)
if request.method == "POST":
# Get the Authorization header
auth_header = request.headers.get("Authorization")

if auth_header and auth_header.startswith("Bearer "):
# Extract the token from the header
token = auth_header.split(" ")[1]

try:
if not check_token_permission(token, permission_required):
return JsonResponse(
{"status": "User does not have permission to create event"},
status=403,
)
# Check if user from jwt has permission to create organiser
# Now process the webhook as usual
organiser_data = json.loads(request.body)
process_organiser_webhook.delay(organiser_data)

return JsonResponse({"status": "success"}, status=200)

except jwt.ExpiredSignatureError:
return JsonResponse({"status": "Token has expired"}, status=401)
except jwt.InvalidTokenError:
return JsonResponse({"status": "Invalid token"}, status=401)
else:
return JsonResponse(
{"status": "Authorization header missing or invalid"}, status=403
)

return JsonResponse({"status": "Invalid method"}, status=405)


@csrf_exempt
def team_webhook(request):
permission_required = ("orga.change_teams",)
if request.method == "POST":
# Get the Authorization header
auth_header = request.headers.get("Authorization")

if auth_header and auth_header.startswith("Bearer "):
# Extract the token from the header
token = auth_header.split(" ")[1]

try:
if not check_token_permission(token, permission_required):
return JsonResponse(
{"status": "User does not have permission to create event"},
status=403,
)
# Now process the webhook as usual
organiser_data = json.loads(request.body)
process_team_webhook.delay(organiser_data)

return JsonResponse({"status": "success"}, status=200)

except jwt.ExpiredSignatureError:
return JsonResponse({"status": "Token has expired"}, status=401)
except jwt.InvalidTokenError:
return JsonResponse({"status": "Invalid token"}, status=401)
else:
return JsonResponse(
{"status": "Authorization header missing or invalid"}, status=403
)

return JsonResponse({"status": "Invalid method"}, status=405)


@csrf_exempt
def event_webhook(request):
permission_required = ("orga.create_events",)
if request.method == "POST":
# Get the Authorization header
auth_header = request.headers.get("Authorization")

if auth_header and auth_header.startswith("Bearer "):
# Extract the token from the header
token = auth_header.split(" ")[1]

try:
if not check_token_permission(token, permission_required):
return JsonResponse(
{"status": "User does not have permission to create event"},
status=403,
)
# Now process the webhook as usual
event_data = json.loads(request.body)
process_event_webhook.delay(event_data)

return JsonResponse({"status": "success"}, status=200)

except jwt.ExpiredSignatureError:
return JsonResponse({"status": "Token has expired"}, status=401)
except jwt.InvalidTokenError:
return JsonResponse({"status": "Invalid token"}, status=401)
else:
return JsonResponse(
{"status": "Authorization header missing or invalid"}, status=403
)

return JsonResponse({"status": "Invalid method"}, status=405)


def check_token_permission(token, permission_required):
# Decode and validate the JWT token
decoded_data = jwt.decode(token, settings.SECRET_KEY, algorithms=["HS256"])

# Check if user from jwt has permission to change team
user = User.objects.get(email=decoded_data["email"])
if not user.has_perms(permission_required, None):
return False
return True

0 comments on commit e1fa4a7

Please sign in to comment.