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 ec3406779..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,7 +27,7 @@ {% if not hide_login %}
- + {% translate "Login with SSO" %}
@@ -36,7 +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..d3f1fd543 --- /dev/null +++ b/src/pretalx/common/templatetags/oauth_tags.py @@ -0,0 +1,31 @@ +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 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..e6215f386 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 quote, urljoin, urlparse 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,77 @@ ) -def oauth2_login_view(request, *args, **kwargs): - sso_provider = SocialApp.objects.filter( - provider=settings.EVENTYAY_SSO_PROVIDER - ).first() - if not sso_provider: +def validate_relative_url(next_url: str) -> bool: + """ + Only allow relative urls + """ + parsed = urlparse(next_url) + if parsed.scheme or parsed.netloc: + return False + + return True + + +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. + """ + register_url = urljoin(settings.EVENTYAY_TICKET_BASE_PATH, "/control/register") + next_url = request.GET.get("next") or request.POST.get("next") + 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}") + + return redirect(register_url) + + +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 and validate_relative_url(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 +145,8 @@ 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) + if next_url and validate_relative_url(next_url): + return redirect(next_url) return redirect(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 %} 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 %}