Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Create speaker account with SSO as part of the answer to Call for Proposals #258

Merged
merged 9 commits into from
Jan 21, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/pretalx/cfp/templates/cfp/event/login.html
Original file line number Diff line number Diff line change
Expand Up @@ -18,5 +18,5 @@ <h2>{% translate "Welcome back!" %}</h2>
or if you are participating in the event as a speaker or organizer.
{% endblocktranslate %}
</p>
{% include "common/auth.html" with hide_register=True %}
{% include "common/auth.html" with hide_register=True next_url=request.get_full_path %}
{% endblock %}
8 changes: 8 additions & 0 deletions src/pretalx/cfp/templates/cfp/event/submission_base.html
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,14 @@
{% block content %}
<div id="submission-steps" class="stages">
{% for stp in cfp_flow %}
{% if stp.identifier != 'user' %}
<a {% if stp.resolved_url %}href="{{ stp.resolved_url }}"{% endif %} class="step step-{% if stp.is_before %}done{% elif stp.identifier == step.identifier %}current{% else %}tbd{% endif %}">
<div class="step-icon">
<span class="fa {% if stp.is_before %}fa-check{% elif stp.icon %}fa-{{ stp.icon }}{% else %}fa-pencil{% endif %}"></span>
</div>
<div class="step-label">{{ stp.label }}</div>
</a>
{% endif %}
{% endfor %}
<div class="step step-tbd">
<div class="step-icon">
Expand All @@ -35,6 +37,7 @@
</div>
</div>
{% block cfp_form %}
{% if request.user.is_authenticated %}
<form method="post"{% if form.is_multipart %} enctype="multipart/form-data"{% endif %}>
{% csrf_token %}
{{ wizard.management_form }}
Expand Down Expand Up @@ -78,5 +81,10 @@ <h2>{% block submission_step_title %}{{ title|default:'' }}{% endblock submissio
</div>
{% endblock buttons %}
</form>
{% else %}
<h2>{% translate "You are required to be logged in to submit a proposal" %}</h2>
<p>{% 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." %}</p>
{% 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 %}
5 changes: 3 additions & 2 deletions src/pretalx/common/templates/common/auth.html
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
{% load i18n %}
{% load static %}
{% load socialaccount %}
{% load oauth_tags %}

{% include "common/forms/errors.html" %}
{% if no_form %}
Expand All @@ -26,7 +27,7 @@
{% if not hide_login %}
<div class="panel panel-default">
<div class="panel-heading text-center" id="headingOne">
<a class="btn btn-lg btn-primary btn-block mt-3" href='{% url "eventyay_common:oauth2_provider.login" %}'>
<a class="btn btn-lg btn-primary btn-block mt-3" href="{% oauth_login_url next_url %}">
{% translate "Login with SSO" %}
</a>
</div>
Expand All @@ -36,7 +37,7 @@
<hr>
<div class="panel panel-default">
<div class="panel-heading text-center" id="headingTwo">
<a class="btn btn-lg btn-primary btn-block mt-3" role="button" data-toggle="collapse" data-parent="#accordion" href="#collapseTwo" aria-expanded="false" aria-controls="collapseTwo">
<a class="btn btn-lg btn-primary btn-block mt-3" href="{% register_account_url next_url %}">
{% translate "Register Speaker Account" %}
</a>
</div>
Expand Down
31 changes: 31 additions & 0 deletions src/pretalx/common/templatetags/oauth_tags.py
Original file line number Diff line number Diff line change
@@ -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
3 changes: 2 additions & 1 deletion src/pretalx/eventyay_common/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"),
Expand Down
91 changes: 71 additions & 20 deletions src/pretalx/eventyay_common/views/auth.py
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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
HungNgien marked this conversation as resolved.
Show resolved Hide resolved

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):
Expand Down Expand Up @@ -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"))
2 changes: 1 addition & 1 deletion src/pretalx/orga/templates/orga/auth/login.html
Original file line number Diff line number Diff line change
Expand Up @@ -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 %}
2 changes: 1 addition & 1 deletion src/pretalx/orga/templates/orga/invitation.html
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ <h2 class="col-md-6 ml-auto mr-auto">
</div>
<hr>

{% include "common/auth.html" %}
{% include "common/auth.html" with next_url=request.get_full_path %}

{% else %}
<form method="post" class="col-md-6 ml-auto mr-auto">
Expand Down
Loading