From f3af2480c565e04a061680dad32e5d54681f4fc1 Mon Sep 17 00:00:00 2001 From: Hung Nguyen Date: Tue, 14 Jan 2025 10:03:36 +0700 Subject: [PATCH 1/9] Add function to register account, and redirect user back --- src/pretalx/common/templates/common/auth.html | 4 +- src/pretalx/eventyay_common/urls.py | 3 +- src/pretalx/eventyay_common/views/auth.py | 71 +++++++++++++------ .../orga/templates/orga/invitation.html | 2 +- 4 files changed, 55 insertions(+), 25 deletions(-) diff --git a/src/pretalx/common/templates/common/auth.html b/src/pretalx/common/templates/common/auth.html index ec3406779..3c383dbd4 100644 --- a/src/pretalx/common/templates/common/auth.html +++ b/src/pretalx/common/templates/common/auth.html @@ -26,7 +26,7 @@ {% if not hide_login %}
@@ -36,7 +36,7 @@
diff --git a/src/pretalx/eventyay_common/urls.py b/src/pretalx/eventyay_common/urls.py index 88cbe049e..b6025fbdb 100644 --- a/src/pretalx/eventyay_common/urls.py +++ b/src/pretalx/eventyay_common/urls.py @@ -11,7 +11,8 @@ urlpatterns = [ path("oauth2/", include("oauth2_provider.urls", namespace="oauth2_provider")), - path("login/", auth.oauth2_login_view, name="oauth2_provider.login"), + path("login/", auth.OAuth2LoginView.as_view(), name="oauth2_provider.login"), + path("register/", auth.register, name="register.account"), 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"), diff --git a/src/pretalx/eventyay_common/views/auth.py b/src/pretalx/eventyay_common/views/auth.py index 8e0d351d5..7bc85bfe6 100644 --- a/src/pretalx/eventyay_common/views/auth.py +++ b/src/pretalx/eventyay_common/views/auth.py @@ -1,12 +1,16 @@ import logging import os +from typing import Optional, Tuple +from urllib.parse import urljoin, quote from allauth.socialaccount.models import SocialApp from django.conf import settings from django.contrib import messages from django.contrib.auth import login +from django.http import HttpRequest, HttpResponse from django.shortcuts import redirect from django.urls import reverse +from django.views import View from requests_oauthlib import OAuth2Session from pretalx.person.models import User @@ -20,34 +24,58 @@ ) -def oauth2_login_view(request, *args, **kwargs): - sso_provider = SocialApp.objects.filter( - provider=settings.EVENTYAY_SSO_PROVIDER - ).first() - if not sso_provider: +def register(request: HttpRequest) -> HttpResponse: + """ + Register a new user account, redirects to previous page + """ + register_url = urljoin(settings.EVENTYAY_TICKET_BASE_PATH, "/control/register") + next = request.GET.get('next') or request.POST.get('next') + next_url = request.build_absolute_uri(next) + next_param = f"?next={quote(next_url)}" + return redirect(f"{register_url}{next_param}") + + +class OAuth2LoginView(View): + def get(self, request: HttpRequest, *args, **kwargs) -> HttpResponse: + # Store the 'next' URL in the session, for redirecting user back after login + next_url = request.GET.get('next') or request.POST.get('next') + if next_url: + request.session['next'] = next_url + + sso_provider = self.get_sso_provider() + if not sso_provider: + return self.handle_sso_not_configured(request) + + oauth2_session = self.create_oauth2_session(sso_provider) + authorization_url, state = self.get_authorization_url(oauth2_session) + request.session["oauth2_state"] = state + + return redirect(authorization_url) + + @staticmethod + def get_sso_provider() -> Optional[SocialApp]: + return SocialApp.objects.filter(provider=settings.EVENTYAY_SSO_PROVIDER).first() + + @staticmethod + def handle_sso_not_configured(request: HttpRequest) -> HttpResponse: messages.error( request, "SSO not configured yet, please contact the " "administrator or come back later.", ) return redirect(reverse("orga:login")) - # Create an OAuth2 session using the client ID and redirect URI - oauth2_session = OAuth2Session( - client_id=sso_provider.client_id, - redirect_uri=settings.OAUTH2_PROVIDER["REDIRECT_URI"], - scope=settings.OAUTH2_PROVIDER["SCOPE"], - ) - # Generate the authorization URL for the SSO provider - authorization_url, state = oauth2_session.authorization_url( - settings.OAUTH2_PROVIDER["AUTHORIZE_URL"] - ) - - # Save the OAuth2 session state to the user's session for security - request.session["oauth2_state"] = state + @staticmethod + def create_oauth2_session(sso_provider: SocialApp) -> OAuth2Session: + return OAuth2Session( + client_id=sso_provider.client_id, + redirect_uri=settings.OAUTH2_PROVIDER["REDIRECT_URI"], + scope=settings.OAUTH2_PROVIDER["SCOPE"], + ) - # Redirect to the SSO provider's login page - return redirect(authorization_url) + @staticmethod + def get_authorization_url(oauth2_session: OAuth2Session) -> Tuple[str, str]: + return oauth2_session.authorization_url(settings.OAUTH2_PROVIDER["AUTHORIZE_URL"]) def oauth2_callback(request): @@ -98,4 +126,5 @@ def oauth2_callback(request): # Log the user into the session login(request, user, backend="django.contrib.auth.backends.ModelBackend") - return redirect(reverse("cfp:root.main")) + next_url = request.session.pop('next', None) + return redirect(next_url or reverse('cfp:root.main')) diff --git a/src/pretalx/orga/templates/orga/invitation.html b/src/pretalx/orga/templates/orga/invitation.html index 190249ae6..3cf972e45 100644 --- a/src/pretalx/orga/templates/orga/invitation.html +++ b/src/pretalx/orga/templates/orga/invitation.html @@ -24,7 +24,7 @@


- {% include "common/auth.html" %} + {% include "common/auth.html" with next_url=request.get_full_path %} {% else %}
From 1b29a1efb379309783859d20b45970a38f7d4b15 Mon Sep 17 00:00:00 2001 From: Hung Nguyen Date: Tue, 14 Jan 2025 21:41:06 +0700 Subject: [PATCH 2/9] Implement Register Speaker Account button --- src/pretalx/cfp/templates/cfp/event/login.html | 2 +- .../templates/cfp/event/submission_base.html | 8 ++++++++ src/pretalx/common/templates/common/auth.html | 6 ++++-- src/pretalx/eventyay_common/views/auth.py | 18 ++++++++++++------ .../orga/templates/orga/auth/login.html | 2 +- 5 files changed, 26 insertions(+), 10 deletions(-) diff --git a/src/pretalx/cfp/templates/cfp/event/login.html b/src/pretalx/cfp/templates/cfp/event/login.html index c38566461..abad98492 100644 --- a/src/pretalx/cfp/templates/cfp/event/login.html +++ b/src/pretalx/cfp/templates/cfp/event/login.html @@ -18,5 +18,5 @@

{% translate "Welcome back!" %}

or if you are participating in the event as a speaker or organizer. {% endblocktranslate %}

- {% include "common/auth.html" with hide_register=True %} + {% include "common/auth.html" with hide_register=True next_url=request.get_full_path %} {% endblock %} diff --git a/src/pretalx/cfp/templates/cfp/event/submission_base.html b/src/pretalx/cfp/templates/cfp/event/submission_base.html index 351a43e19..0b96a28f9 100644 --- a/src/pretalx/cfp/templates/cfp/event/submission_base.html +++ b/src/pretalx/cfp/templates/cfp/event/submission_base.html @@ -20,12 +20,14 @@ {% block content %}
{% for stp in cfp_flow %} + {% if stp.identifier != 'user' %}
{{ stp.label }}
+ {% endif %} {% endfor %}
@@ -35,6 +37,7 @@
{% block cfp_form %} + {% if request.user.is_authenticated %} {% csrf_token %} {{ wizard.management_form }} @@ -78,5 +81,10 @@

{% block submission_step_title %}{{ title|default:'' }}{% endblock submissio

{% endblock buttons %} + {% else %} +

{% translate "You are required to be logged in to submit a proposal" %}

+

{% translate "To create your proposal, you need an account on this page. This not only gives us a way to contact you, it also gives you the possibility to edit your proposal or to view its current state." %}

+ {% include "common/auth.html" with form=form no_form=True no_buttons=True next_url=request.get_full_path %} + {% endif %} {% endblock cfp_form %} {% endblock content %} diff --git a/src/pretalx/common/templates/common/auth.html b/src/pretalx/common/templates/common/auth.html index 3c383dbd4..932dc4315 100644 --- a/src/pretalx/common/templates/common/auth.html +++ b/src/pretalx/common/templates/common/auth.html @@ -26,7 +26,8 @@ {% if not hide_login %}
@@ -36,7 +37,8 @@
diff --git a/src/pretalx/eventyay_common/views/auth.py b/src/pretalx/eventyay_common/views/auth.py index 7bc85bfe6..eacd50616 100644 --- a/src/pretalx/eventyay_common/views/auth.py +++ b/src/pretalx/eventyay_common/views/auth.py @@ -26,12 +26,16 @@ def register(request: HttpRequest) -> HttpResponse: """ - Register a new user account, redirects to previous page + Register a new user account and redirect to the previous page. + + This function constructs a registration URL with a 'next' parameter + to ensure the user is redirected back to their original location + after registration. """ register_url = urljoin(settings.EVENTYAY_TICKET_BASE_PATH, "/control/register") - next = request.GET.get('next') or request.POST.get('next') - next_url = request.build_absolute_uri(next) - next_param = f"?next={quote(next_url)}" + next_url = request.GET.get('next') or request.POST.get('next') + full_next_url = request.build_absolute_uri(next_url) + next_param = f"?next={quote(full_next_url)}" return redirect(f"{register_url}{next_param}") @@ -45,7 +49,6 @@ def get(self, request: HttpRequest, *args, **kwargs) -> HttpResponse: sso_provider = self.get_sso_provider() if not sso_provider: return self.handle_sso_not_configured(request) - oauth2_session = self.create_oauth2_session(sso_provider) authorization_url, state = self.get_authorization_url(oauth2_session) request.session["oauth2_state"] = state @@ -75,7 +78,9 @@ def create_oauth2_session(sso_provider: SocialApp) -> OAuth2Session: @staticmethod def get_authorization_url(oauth2_session: OAuth2Session) -> Tuple[str, str]: - return oauth2_session.authorization_url(settings.OAUTH2_PROVIDER["AUTHORIZE_URL"]) + return oauth2_session.authorization_url( + settings.OAUTH2_PROVIDER["AUTHORIZE_URL"] + ) def oauth2_callback(request): @@ -126,5 +131,6 @@ def oauth2_callback(request): # Log the user into the session login(request, user, backend="django.contrib.auth.backends.ModelBackend") + # If a 'next' URL was stored in the session, use it for redirecting user back after login next_url = request.session.pop('next', None) return redirect(next_url or reverse('cfp:root.main')) diff --git a/src/pretalx/orga/templates/orga/auth/login.html b/src/pretalx/orga/templates/orga/auth/login.html index 9fff8bd71..792d402d3 100644 --- a/src/pretalx/orga/templates/orga/auth/login.html +++ b/src/pretalx/orga/templates/orga/auth/login.html @@ -3,5 +3,5 @@ {% load static %} {% block title %}{% translate "Sign in" %}{% endblock title %} {% block content %} - {% include "common/auth.html" with hide_register=True %} + {% include "common/auth.html" with hide_register=True next_url=request.get_full_path %} {% endblock content %} From 6d2f42c70f75ffb05d57c7c2c7bc5a5a8f62f1bc Mon Sep 17 00:00:00 2001 From: Hung Nguyen Date: Wed, 15 Jan 2025 11:30:23 +0700 Subject: [PATCH 3/9] Delete blank line --- src/pretalx/eventyay_common/views/auth.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/pretalx/eventyay_common/views/auth.py b/src/pretalx/eventyay_common/views/auth.py index eacd50616..9c412e54c 100644 --- a/src/pretalx/eventyay_common/views/auth.py +++ b/src/pretalx/eventyay_common/views/auth.py @@ -1,7 +1,7 @@ import logging import os from typing import Optional, Tuple -from urllib.parse import urljoin, quote +from urllib.parse import quote, urljoin from allauth.socialaccount.models import SocialApp from django.conf import settings @@ -27,7 +27,7 @@ def register(request: HttpRequest) -> HttpResponse: """ Register a new user account and redirect to the previous page. - + This function constructs a registration URL with a 'next' parameter to ensure the user is redirected back to their original location after registration. From 8ba5ccfd067b617e19a4ebe3323f8bc99731ff06 Mon Sep 17 00:00:00 2001 From: Hung Nguyen Date: Wed, 15 Jan 2025 11:36:46 +0700 Subject: [PATCH 4/9] Fix black format --- src/pretalx/eventyay_common/views/auth.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/pretalx/eventyay_common/views/auth.py b/src/pretalx/eventyay_common/views/auth.py index 9c412e54c..60d1fa8c6 100644 --- a/src/pretalx/eventyay_common/views/auth.py +++ b/src/pretalx/eventyay_common/views/auth.py @@ -33,7 +33,7 @@ def register(request: HttpRequest) -> HttpResponse: after registration. """ register_url = urljoin(settings.EVENTYAY_TICKET_BASE_PATH, "/control/register") - next_url = request.GET.get('next') or request.POST.get('next') + next_url = request.GET.get("next") or request.POST.get("next") full_next_url = request.build_absolute_uri(next_url) next_param = f"?next={quote(full_next_url)}" return redirect(f"{register_url}{next_param}") @@ -42,9 +42,9 @@ def register(request: HttpRequest) -> HttpResponse: class OAuth2LoginView(View): def get(self, request: HttpRequest, *args, **kwargs) -> HttpResponse: # Store the 'next' URL in the session, for redirecting user back after login - next_url = request.GET.get('next') or request.POST.get('next') + next_url = request.GET.get("next") or request.POST.get("next") if next_url: - request.session['next'] = next_url + request.session["next"] = next_url sso_provider = self.get_sso_provider() if not sso_provider: @@ -132,5 +132,5 @@ def oauth2_callback(request): # Log the user into the session login(request, user, backend="django.contrib.auth.backends.ModelBackend") # If a 'next' URL was stored in the session, use it for redirecting user back after login - next_url = request.session.pop('next', None) - return redirect(next_url or reverse('cfp:root.main')) + next_url = request.session.pop("next", None) + return redirect(next_url or reverse("cfp:root.main")) From c9309c8b8286943e4d875032fd685bf960053433 Mon Sep 17 00:00:00 2001 From: Hung Nguyen Date: Wed, 15 Jan 2025 15:21:51 +0700 Subject: [PATCH 5/9] Add url validation --- src/pretalx/eventyay_common/views/auth.py | 26 ++++++++++++++++++----- 1 file changed, 21 insertions(+), 5 deletions(-) diff --git a/src/pretalx/eventyay_common/views/auth.py b/src/pretalx/eventyay_common/views/auth.py index 60d1fa8c6..55a5e69ab 100644 --- a/src/pretalx/eventyay_common/views/auth.py +++ b/src/pretalx/eventyay_common/views/auth.py @@ -1,7 +1,7 @@ import logging import os from typing import Optional, Tuple -from urllib.parse import quote, urljoin +from urllib.parse import quote, urljoin, urlparse from allauth.socialaccount.models import SocialApp from django.conf import settings @@ -24,6 +24,14 @@ ) +def validate_relative_url(next_url: str) -> Optional[str]: + parsed = urlparse(next_url) + # Allow relative URLs + if not parsed.netloc: + return next_url + return + + def register(request: HttpRequest) -> HttpResponse: """ Register a new user account and redirect to the previous page. @@ -34,9 +42,14 @@ def register(request: HttpRequest) -> HttpResponse: """ register_url = urljoin(settings.EVENTYAY_TICKET_BASE_PATH, "/control/register") next_url = request.GET.get("next") or request.POST.get("next") - full_next_url = request.build_absolute_uri(next_url) - next_param = f"?next={quote(full_next_url)}" - return redirect(f"{register_url}{next_param}") + + validated_next_url = validate_relative_url(next_url) + if validated_next_url: + full_next_url = request.build_absolute_uri(validated_next_url) + next_param = f"?next={quote(full_next_url)}" + return redirect(f"{register_url}{next_param}") + + return redirect(register_url) class OAuth2LoginView(View): @@ -133,4 +146,7 @@ def oauth2_callback(request): login(request, user, backend="django.contrib.auth.backends.ModelBackend") # If a 'next' URL was stored in the session, use it for redirecting user back after login next_url = request.session.pop("next", None) - return redirect(next_url or reverse("cfp:root.main")) + validated_next_url = validate_relative_url(next_url) + if validated_next_url: + return redirect(validated_next_url) + return redirect(reverse("cfp:root.main")) From 14088aa7c0d524f9978de49dcd4d9005f9d9aff3 Mon Sep 17 00:00:00 2001 From: Hung Nguyen Date: Thu, 16 Jan 2025 10:37:35 +0700 Subject: [PATCH 6/9] Update url validation --- src/pretalx/eventyay_common/views/auth.py | 26 +++++++++++------------ 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/src/pretalx/eventyay_common/views/auth.py b/src/pretalx/eventyay_common/views/auth.py index 55a5e69ab..e6215f386 100644 --- a/src/pretalx/eventyay_common/views/auth.py +++ b/src/pretalx/eventyay_common/views/auth.py @@ -24,12 +24,15 @@ ) -def validate_relative_url(next_url: str) -> Optional[str]: +def validate_relative_url(next_url: str) -> bool: + """ + Only allow relative urls + """ parsed = urlparse(next_url) - # Allow relative URLs - if not parsed.netloc: - return next_url - return + if parsed.scheme or parsed.netloc: + return False + + return True def register(request: HttpRequest) -> HttpResponse: @@ -42,10 +45,8 @@ def register(request: HttpRequest) -> HttpResponse: """ register_url = urljoin(settings.EVENTYAY_TICKET_BASE_PATH, "/control/register") next_url = request.GET.get("next") or request.POST.get("next") - - validated_next_url = validate_relative_url(next_url) - if validated_next_url: - full_next_url = request.build_absolute_uri(validated_next_url) + if next_url and validate_relative_url(next_url): + full_next_url = request.build_absolute_uri(next_url) next_param = f"?next={quote(full_next_url)}" return redirect(f"{register_url}{next_param}") @@ -56,7 +57,7 @@ class OAuth2LoginView(View): def get(self, request: HttpRequest, *args, **kwargs) -> HttpResponse: # Store the 'next' URL in the session, for redirecting user back after login next_url = request.GET.get("next") or request.POST.get("next") - if next_url: + if next_url and validate_relative_url(next_url): request.session["next"] = next_url sso_provider = self.get_sso_provider() @@ -146,7 +147,6 @@ def oauth2_callback(request): login(request, user, backend="django.contrib.auth.backends.ModelBackend") # If a 'next' URL was stored in the session, use it for redirecting user back after login next_url = request.session.pop("next", None) - validated_next_url = validate_relative_url(next_url) - if validated_next_url: - return redirect(validated_next_url) + if next_url and validate_relative_url(next_url): + return redirect(next_url) return redirect(reverse("cfp:root.main")) From 6c1189508db7e44f0b523cd7c67b645aacbceb8a Mon Sep 17 00:00:00 2001 From: Hung Nguyen Date: Thu, 16 Jan 2025 16:56:10 +0700 Subject: [PATCH 7/9] Resolve conversation --- src/pretalx/common/templates/common/auth.html | 7 ++-- src/pretalx/common/templatetags/oauth_tags.py | 32 +++++++++++++++++++ 2 files changed, 35 insertions(+), 4 deletions(-) create mode 100644 src/pretalx/common/templatetags/oauth_tags.py diff --git a/src/pretalx/common/templates/common/auth.html b/src/pretalx/common/templates/common/auth.html index 932dc4315..b618bcd6f 100644 --- a/src/pretalx/common/templates/common/auth.html +++ b/src/pretalx/common/templates/common/auth.html @@ -2,6 +2,7 @@ {% load i18n %} {% load static %} {% load socialaccount %} +{% load oauth_tags %} {% include "common/forms/errors.html" %} {% if no_form %} @@ -26,8 +27,7 @@ {% if not hide_login %}
@@ -37,8 +37,7 @@
diff --git a/src/pretalx/common/templatetags/oauth_tags.py b/src/pretalx/common/templatetags/oauth_tags.py new file mode 100644 index 000000000..2e2d39bc5 --- /dev/null +++ b/src/pretalx/common/templatetags/oauth_tags.py @@ -0,0 +1,32 @@ +from typing import Optional +from urllib.parse import quote + +from django import template +from django.urls import reverse + +register = template.Library() + + +@register.simple_tag +def oauth_login_url(next_url: Optional[str] = None) -> str: + """ + Generate the OAuth login URL with an optional next parameter. + Usage: {% oauth_login_url next_url %} + """ + base_url = reverse('eventyay_common:oauth2_provider.login') + + if next_url: + return f"{base_url}?next={quote(next_url)}" + return base_url + + +@register.simple_tag +def register_account_url(next_url: Optional[str] = None) -> str: + """ + Generate the registration URL with an optional next parameter. + Usage: {% register_account_url next_url %} + """ + base_url = reverse("eventyay_common:register.account") + if next_url: + return f"{base_url}?next={quote(next_url)}" + return base_url From 376d2a8383edcaecaa550a24c78915daaeb44329 Mon Sep 17 00:00:00 2001 From: Hung Nguyen Date: Thu, 16 Jan 2025 16:57:35 +0700 Subject: [PATCH 8/9] Delete blank line --- src/pretalx/common/templatetags/oauth_tags.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/pretalx/common/templatetags/oauth_tags.py b/src/pretalx/common/templatetags/oauth_tags.py index 2e2d39bc5..cfe4d7051 100644 --- a/src/pretalx/common/templatetags/oauth_tags.py +++ b/src/pretalx/common/templatetags/oauth_tags.py @@ -14,7 +14,6 @@ def oauth_login_url(next_url: Optional[str] = None) -> str: Usage: {% oauth_login_url next_url %} """ base_url = reverse('eventyay_common:oauth2_provider.login') - if next_url: return f"{base_url}?next={quote(next_url)}" return base_url From 3547246cf6a9c823d8c3e93ae384c39fb40c5d04 Mon Sep 17 00:00:00 2001 From: Hung Nguyen Date: Thu, 16 Jan 2025 16:59:32 +0700 Subject: [PATCH 9/9] Black format --- src/pretalx/common/templatetags/oauth_tags.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pretalx/common/templatetags/oauth_tags.py b/src/pretalx/common/templatetags/oauth_tags.py index cfe4d7051..d3f1fd543 100644 --- a/src/pretalx/common/templatetags/oauth_tags.py +++ b/src/pretalx/common/templatetags/oauth_tags.py @@ -13,7 +13,7 @@ def oauth_login_url(next_url: Optional[str] = None) -> str: Generate the OAuth login URL with an optional next parameter. Usage: {% oauth_login_url next_url %} """ - base_url = reverse('eventyay_common:oauth2_provider.login') + base_url = reverse("eventyay_common:oauth2_provider.login") if next_url: return f"{base_url}?next={quote(next_url)}" return base_url