Skip to content

Commit 3ae19b9

Browse files
authored
sso implement (#114)
implement sso client for eventyay-talk to eventyay-ticket
1 parent d3b0cc2 commit 3ae19b9

File tree

14 files changed

+446
-2
lines changed

14 files changed

+446
-2
lines changed

pyproject.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,8 @@ dependencies = [
6767
"zxcvbn~=4.4.0",
6868
"jwt~=1.3.1",
6969
'pretalx_pages @ git+https://github.com/fossasia/eventyay-talk-pages.git@main',
70-
'pretalx_venueless @ git+https://github.com/fossasia/eventyay-talk-video.git@main'
70+
'pretalx_venueless @ git+https://github.com/fossasia/eventyay-talk-video.git@main',
71+
"django-allauth~=0.63.3"
7172
]
7273

7374
[project.optional-dependencies]
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
from django.core.management.base import BaseCommand
2+
from allauth.socialaccount.models import SocialApp
3+
from django.conf import settings
4+
from django.contrib.sites.models import Site
5+
6+
class Command(BaseCommand):
7+
help = 'Create SocialApp entries for Eventyay-ticket Provider'
8+
9+
def add_arguments(self, parser):
10+
parser.add_argument('--eventyay-ticket-client-id', type=str, help='Eventyay-Ticket Provider Client ID')
11+
parser.add_argument('--eventyay-ticket-secret', type=str, help='Eventyay-Ticket Provider Secret')
12+
13+
def handle(self, *args, **options):
14+
site = Site.objects.get(pk=settings.SITE_ID)
15+
eventyay_ticket_client_id = options.get('eventyay-ticket-client-id') or input('Enter Eventyay-Ticket Provider Client ID: ')
16+
eventyay_ticket_secret = options.get('eventyay-ticket-secret') or input('Enter Eventyay-Ticket Provider Secret: ')
17+
18+
if not SocialApp.objects.filter(provider='eventyay').exists():
19+
custom_app = SocialApp.objects.create(
20+
provider='eventyay',
21+
name='Eventyay Ticket Provider',
22+
client_id=eventyay_ticket_client_id,
23+
secret=eventyay_ticket_secret,
24+
key=''
25+
)
26+
custom_app.sites.add(site)
27+
self.stdout.write(self.style.SUCCESS('Successfully created Eventyay-ticket Provider SocialApp'))

src/pretalx/common/templates/common/auth.html

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
{% load compress %}
33
{% load i18n %}
44
{% load static %}
5+
{% load socialaccount %}
56

67
{% bootstrap_form_errors form %}
78
{% if no_form %}
@@ -26,6 +27,13 @@ <h4 class="text-center">{% translate "I already have an account" %}</h4>
2627
{% translate "Log in" %}
2728
</button>
2829
{% endif %}
30+
{% if not no_buttons %}
31+
<div class="text-center">
32+
<a class="btn btn-lg btn-primary btn-block mt-3" href="{% provider_login_url 'eventyay' %}">
33+
{% translate "Login with Eventyay-ticket" %}
34+
</a>
35+
</div>
36+
{% endif %}
2937
{% if password_reset_link or request.event %}
3038
<a class="btn btn-block btn-link" href="{% if password_reset_link %}{{ password_reset_link }}{% else %}{{ request.event.urls.reset }}{% endif %}">
3139
{% translate "Reset password" %}

src/pretalx/settings.py

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@
6666
"django.contrib.messages",
6767
"django.contrib.staticfiles",
6868
"django.contrib.humanize",
69+
'django.contrib.sites',
6970
]
7071
EXTERNAL_APPS = [
7172
"compressor",
@@ -75,6 +76,9 @@
7576
"jquery",
7677
"rest_framework.authtoken",
7778
"rules",
79+
"allauth",
80+
"allauth.account",
81+
"allauth.socialaccount",
7882
]
7983
LOCAL_APPS = [
8084
"pretalx.api",
@@ -87,6 +91,7 @@
8791
"pretalx.agenda",
8892
"pretalx.cfp",
8993
"pretalx.orga",
94+
"pretalx.sso_provider",
9095
]
9196
FALLBACK_APPS = [
9297
"bootstrap4",
@@ -503,6 +508,7 @@ def merge_csp(*options, config=None):
503508
"rules.permissions.ObjectPermissionBackend",
504509
"django.contrib.auth.backends.ModelBackend",
505510
"pretalx.common.auth.AuthenticationTokenBackend",
511+
"allauth.account.auth_backends.AuthenticationBackend",
506512
)
507513
AUTH_PASSWORD_VALIDATORS = [
508514
{
@@ -528,6 +534,7 @@ def merge_csp(*options, config=None):
528534
"django.contrib.messages.middleware.MessageMiddleware", # Uses sessions
529535
"django.middleware.clickjacking.XFrameOptionsMiddleware", # Protects against clickjacking
530536
"csp.middleware.CSPMiddleware", # Modifies/sets CSP headers
537+
"allauth.account.middleware.AccountMiddleware", # Adds account information to the request
531538
]
532539

533540

@@ -546,6 +553,8 @@ def merge_csp(*options, config=None):
546553
"DIRS": [
547554
DATA_DIR / "templates",
548555
BASE_DIR / "templates",
556+
BASE_DIR / "pretalx" / "sso_provider" / "templates",
557+
549558
],
550559
"OPTIONS": {
551560
"context_processors": [
@@ -686,3 +695,23 @@ def merge_csp(*options, config=None):
686695
LOG_DIR=LOG_DIR,
687696
plugins=PLUGINS,
688697
)
698+
699+
# Below is configuration for SSO using eventyay-ticket
700+
EVENTYAY_TICKET_BASE_PATH = os.getenv("EVENTYAY_TICKET_BASE_PATH",
701+
"https://tickets-dev.eventyay.com")
702+
SITE_ID = 1
703+
# for now, customer must verified their email at eventyay-ticket, so this check not required
704+
ACCOUNT_EMAIL_VERIFICATION = 'none'
705+
# will take name from eventyay-ticket as username
706+
ACCOUNT_USER_MODEL_USERNAME_FIELD = 'name'
707+
# redirect to home page after login with eventyay-ticket
708+
LOGIN_REDIRECT_URL = '/'
709+
# custom form for signup and adapter
710+
SOCIALACCOUNT_FORMS = {"signup": "pretalx.sso_provider.forms.CustomSignUpForm"}
711+
SOCIALACCOUNT_ADAPTER = 'pretalx.sso_provider.views.CustomSocialAccountAdapter'
712+
# disable confirm step when using eventyay-ticket to login
713+
SOCIALACCOUNT_LOGIN_ON_GET = True
714+
# eventyay-ticket provider configuration
715+
EVENTYAY_TICKET_SSO_WELL_KNOW_URL = "/".join([EVENTYAY_TICKET_BASE_PATH,
716+
'{org}',
717+
'.well-known/openid-configuration'])

src/pretalx/sso_provider/__init__.py

Whitespace-only changes.

src/pretalx/sso_provider/apps.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
from django.apps import AppConfig
2+
3+
class SSOProviderConfig(AppConfig):
4+
name = 'pretalx.sso_provider'
5+
6+
def ready(self):
7+
from allauth.socialaccount import providers
8+
from .providers import EventyayProvider
9+
providers.registry.register(EventyayProvider)

src/pretalx/sso_provider/forms.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
from allauth.socialaccount.forms import SignupForm
2+
3+
class CustomSignUpForm(SignupForm):
4+
def __init__(self, *args, **kwargs):
5+
super().__init__(*args, **kwargs)
6+
# TODO add custom fields here
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
import requests
2+
3+
from allauth.socialaccount.providers.oauth2.provider import OAuth2Provider
4+
from allauth.socialaccount.providers.base import AuthAction, ProviderAccount
5+
from allauth.socialaccount.app_settings import QUERY_EMAIL
6+
from allauth.account.models import EmailAddress
7+
from allauth.core.exceptions import ImmediateHttpResponse
8+
from allauth.socialaccount.helpers import render_authentication_error
9+
from django.conf import settings
10+
from django.urls import reverse
11+
12+
from .views import EventyayTicketOAuth2Adapter
13+
14+
15+
class Scope(object):
16+
OPEN_ID = "openid"
17+
EMAIL = "email"
18+
PROFILE = "profile"
19+
20+
21+
class EventYayTicketAccount(ProviderAccount):
22+
23+
def get_profile_url(self):
24+
return self.account.extra_data.get("link")
25+
26+
def get_avatar_url(self):
27+
return self.account.extra_data.get("picture")
28+
29+
def to_str(self):
30+
dflt = super(GoogleAccount, self).to_str()
31+
return self.account.extra_data.get("name", dflt)
32+
33+
34+
class EventyayProvider(OAuth2Provider):
35+
id = 'eventyay'
36+
name = 'Eventyay'
37+
account_class = EventYayTicketAccount
38+
oauth2_adapter_class = EventyayTicketOAuth2Adapter
39+
40+
def get_openid_config(self):
41+
try:
42+
response = requests.get(settings.EVENTYAY_TICKET_SSO_WELL_KNOW_URL
43+
.format(org=self.request.session.get('org')))
44+
response.raise_for_status()
45+
except:
46+
raise ImmediateHttpResponse(
47+
render_authentication_error(self.request,
48+
'Error happened when trying get configurations from Eventyay-ticket'))
49+
return response.json()
50+
51+
def get_default_scope(self):
52+
scope = [Scope.PROFILE]
53+
scope.append(Scope.EMAIL)
54+
scope.append(Scope.OPEN_ID)
55+
return scope
56+
57+
def extract_uid(self, data):
58+
if "sub" in data:
59+
return data["sub"]
60+
return data["id"]
61+
62+
def extract_common_fields(self, data):
63+
return dict(email=data.get('email'),
64+
username=data.get('name'))
65+
66+
def extract_email_addresses(self, data):
67+
ret = []
68+
email = data.get("email")
69+
if email:
70+
verified = bool(data.get("email_verified") or data.get("verified_email"))
71+
ret.append(EmailAddress(email=email, verified=verified, primary=True))
72+
return ret
73+
74+
def get_login_url(self, request, **kwargs):
75+
current_event = request.event
76+
request.session['org'] = current_event.organiser.slug
77+
url = reverse(self.id + "_login")
78+
if kwargs:
79+
url = url + "?" + urlencode(kwargs)
80+
return url
81+
82+
83+
provider_classes = [EventyayProvider]
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
{% extends "./base.html" %}
2+
{% load bootstrap4 %}
3+
{% load compress %}
4+
{% load i18n %}
5+
{% load static %}
6+
{% load allauth %}
7+
8+
{% block head_title %}
9+
{% trans "Third-Party Login Failure" %}
10+
{% endblock head_title %}
11+
{% block content %}
12+
{% element h1 %}
13+
{% trans "Eventyay-ticket Login Failure" %}
14+
{% endelement %}
15+
{% element p %}
16+
{% trans "An error occurred while attempting to login via your Eventyay-ticket account." %}
17+
{% endelement %}
18+
{% endblock content %}

0 commit comments

Comments
 (0)