Skip to content

Commit

Permalink
sso implement (#114)
Browse files Browse the repository at this point in the history
implement sso client for eventyay-talk to eventyay-ticket
  • Loading branch information
odkhang authored Jul 14, 2024
1 parent d3b0cc2 commit 3ae19b9
Show file tree
Hide file tree
Showing 14 changed files with 446 additions and 2 deletions.
3 changes: 2 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,8 @@ dependencies = [
"zxcvbn~=4.4.0",
"jwt~=1.3.1",
'pretalx_pages @ git+https://github.com/fossasia/eventyay-talk-pages.git@main',
'pretalx_venueless @ git+https://github.com/fossasia/eventyay-talk-video.git@main'
'pretalx_venueless @ git+https://github.com/fossasia/eventyay-talk-video.git@main',
"django-allauth~=0.63.3"
]

[project.optional-dependencies]
Expand Down
27 changes: 27 additions & 0 deletions src/pretalx/common/management/commands/create_social_apps.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
from django.core.management.base import BaseCommand
from allauth.socialaccount.models import SocialApp
from django.conf import settings
from django.contrib.sites.models import Site

class Command(BaseCommand):
help = 'Create SocialApp entries for Eventyay-ticket Provider'

def add_arguments(self, parser):
parser.add_argument('--eventyay-ticket-client-id', type=str, help='Eventyay-Ticket Provider Client ID')
parser.add_argument('--eventyay-ticket-secret', type=str, help='Eventyay-Ticket Provider Secret')

def handle(self, *args, **options):
site = Site.objects.get(pk=settings.SITE_ID)
eventyay_ticket_client_id = options.get('eventyay-ticket-client-id') or input('Enter Eventyay-Ticket Provider Client ID: ')
eventyay_ticket_secret = options.get('eventyay-ticket-secret') or input('Enter Eventyay-Ticket Provider Secret: ')

if not SocialApp.objects.filter(provider='eventyay').exists():
custom_app = SocialApp.objects.create(
provider='eventyay',
name='Eventyay Ticket Provider',
client_id=eventyay_ticket_client_id,
secret=eventyay_ticket_secret,
key=''
)
custom_app.sites.add(site)
self.stdout.write(self.style.SUCCESS('Successfully created Eventyay-ticket Provider SocialApp'))
8 changes: 8 additions & 0 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 compress %}
{% load i18n %}
{% load static %}
{% load socialaccount %}

{% bootstrap_form_errors form %}
{% if no_form %}
Expand All @@ -26,6 +27,13 @@ <h4 class="text-center">{% translate "I already have an account" %}</h4>
{% translate "Log in" %}
</button>
{% endif %}
{% if not no_buttons %}
<div class="text-center">
<a class="btn btn-lg btn-primary btn-block mt-3" href="{% provider_login_url 'eventyay' %}">
{% translate "Login with Eventyay-ticket" %}
</a>
</div>
{% endif %}
{% if password_reset_link or request.event %}
<a class="btn btn-block btn-link" href="{% if password_reset_link %}{{ password_reset_link }}{% else %}{{ request.event.urls.reset }}{% endif %}">
{% translate "Reset password" %}
Expand Down
29 changes: 29 additions & 0 deletions src/pretalx/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@
"django.contrib.messages",
"django.contrib.staticfiles",
"django.contrib.humanize",
'django.contrib.sites',
]
EXTERNAL_APPS = [
"compressor",
Expand All @@ -75,6 +76,9 @@
"jquery",
"rest_framework.authtoken",
"rules",
"allauth",
"allauth.account",
"allauth.socialaccount",
]
LOCAL_APPS = [
"pretalx.api",
Expand All @@ -87,6 +91,7 @@
"pretalx.agenda",
"pretalx.cfp",
"pretalx.orga",
"pretalx.sso_provider",
]
FALLBACK_APPS = [
"bootstrap4",
Expand Down Expand Up @@ -503,6 +508,7 @@ def merge_csp(*options, config=None):
"rules.permissions.ObjectPermissionBackend",
"django.contrib.auth.backends.ModelBackend",
"pretalx.common.auth.AuthenticationTokenBackend",
"allauth.account.auth_backends.AuthenticationBackend",
)
AUTH_PASSWORD_VALIDATORS = [
{
Expand All @@ -528,6 +534,7 @@ def merge_csp(*options, config=None):
"django.contrib.messages.middleware.MessageMiddleware", # Uses sessions
"django.middleware.clickjacking.XFrameOptionsMiddleware", # Protects against clickjacking
"csp.middleware.CSPMiddleware", # Modifies/sets CSP headers
"allauth.account.middleware.AccountMiddleware", # Adds account information to the request
]


Expand All @@ -546,6 +553,8 @@ def merge_csp(*options, config=None):
"DIRS": [
DATA_DIR / "templates",
BASE_DIR / "templates",
BASE_DIR / "pretalx" / "sso_provider" / "templates",

],
"OPTIONS": {
"context_processors": [
Expand Down Expand Up @@ -686,3 +695,23 @@ def merge_csp(*options, config=None):
LOG_DIR=LOG_DIR,
plugins=PLUGINS,
)

# Below is configuration for SSO using eventyay-ticket
EVENTYAY_TICKET_BASE_PATH = os.getenv("EVENTYAY_TICKET_BASE_PATH",
"https://tickets-dev.eventyay.com")
SITE_ID = 1
# for now, customer must verified their email at eventyay-ticket, so this check not required
ACCOUNT_EMAIL_VERIFICATION = 'none'
# will take name from eventyay-ticket as username
ACCOUNT_USER_MODEL_USERNAME_FIELD = 'name'
# redirect to home page after login with eventyay-ticket
LOGIN_REDIRECT_URL = '/'
# custom form for signup and adapter
SOCIALACCOUNT_FORMS = {"signup": "pretalx.sso_provider.forms.CustomSignUpForm"}
SOCIALACCOUNT_ADAPTER = 'pretalx.sso_provider.views.CustomSocialAccountAdapter'
# disable confirm step when using eventyay-ticket to login
SOCIALACCOUNT_LOGIN_ON_GET = True
# eventyay-ticket provider configuration
EVENTYAY_TICKET_SSO_WELL_KNOW_URL = "/".join([EVENTYAY_TICKET_BASE_PATH,
'{org}',
'.well-known/openid-configuration'])
Empty file.
9 changes: 9 additions & 0 deletions src/pretalx/sso_provider/apps.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
from django.apps import AppConfig

class SSOProviderConfig(AppConfig):
name = 'pretalx.sso_provider'

def ready(self):
from allauth.socialaccount import providers
from .providers import EventyayProvider
providers.registry.register(EventyayProvider)
6 changes: 6 additions & 0 deletions src/pretalx/sso_provider/forms.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
from allauth.socialaccount.forms import SignupForm

class CustomSignUpForm(SignupForm):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
# TODO add custom fields here
83 changes: 83 additions & 0 deletions src/pretalx/sso_provider/providers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
import requests

from allauth.socialaccount.providers.oauth2.provider import OAuth2Provider
from allauth.socialaccount.providers.base import AuthAction, ProviderAccount
from allauth.socialaccount.app_settings import QUERY_EMAIL
from allauth.account.models import EmailAddress
from allauth.core.exceptions import ImmediateHttpResponse
from allauth.socialaccount.helpers import render_authentication_error
from django.conf import settings
from django.urls import reverse

from .views import EventyayTicketOAuth2Adapter


class Scope(object):
OPEN_ID = "openid"
EMAIL = "email"
PROFILE = "profile"


class EventYayTicketAccount(ProviderAccount):

def get_profile_url(self):
return self.account.extra_data.get("link")

def get_avatar_url(self):
return self.account.extra_data.get("picture")

def to_str(self):
dflt = super(GoogleAccount, self).to_str()
return self.account.extra_data.get("name", dflt)


class EventyayProvider(OAuth2Provider):
id = 'eventyay'
name = 'Eventyay'
account_class = EventYayTicketAccount
oauth2_adapter_class = EventyayTicketOAuth2Adapter

def get_openid_config(self):
try:
response = requests.get(settings.EVENTYAY_TICKET_SSO_WELL_KNOW_URL
.format(org=self.request.session.get('org')))
response.raise_for_status()
except:
raise ImmediateHttpResponse(
render_authentication_error(self.request,
'Error happened when trying get configurations from Eventyay-ticket'))
return response.json()

def get_default_scope(self):
scope = [Scope.PROFILE]
scope.append(Scope.EMAIL)
scope.append(Scope.OPEN_ID)
return scope

def extract_uid(self, data):
if "sub" in data:
return data["sub"]
return data["id"]

def extract_common_fields(self, data):
return dict(email=data.get('email'),
username=data.get('name'))

def extract_email_addresses(self, data):
ret = []
email = data.get("email")
if email:
verified = bool(data.get("email_verified") or data.get("verified_email"))
ret.append(EmailAddress(email=email, verified=verified, primary=True))
return ret

def get_login_url(self, request, **kwargs):
current_event = request.event
request.session['org'] = current_event.organiser.slug
url = reverse(self.id + "_login")
if kwargs:
url = url + "?" + urlencode(kwargs)
return url


provider_classes = [EventyayProvider]
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
{% extends "./base.html" %}
{% load bootstrap4 %}
{% load compress %}
{% load i18n %}
{% load static %}
{% load allauth %}

{% block head_title %}
{% trans "Third-Party Login Failure" %}
{% endblock head_title %}
{% block content %}
{% element h1 %}
{% trans "Eventyay-ticket Login Failure" %}
{% endelement %}
{% element p %}
{% trans "An error occurred while attempting to login via your Eventyay-ticket account." %}
{% endelement %}
{% endblock content %}
Loading

0 comments on commit 3ae19b9

Please sign in to comment.