Skip to content

Commit

Permalink
ESN Accounts Integration (#28)
Browse files Browse the repository at this point in the history
* Added support for CAS ESN Accounts login #27

* Added section membership spawn from ESN Accounts login

* [pre-commit.ci] auto fixes from pre-commit.com hooks

* Linted

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
  • Loading branch information
thejoeejoee and pre-commit-ci[bot] committed Feb 9, 2022
1 parent 67cf837 commit 2926377
Show file tree
Hide file tree
Showing 20 changed files with 320 additions and 23 deletions.
5 changes: 3 additions & 2 deletions fiesta/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
FROM python:3.9.7-alpine as builder

RUN apk add --no-cache --virtual build-deps \
build-base gcc python3-dev musl-dev gettext-dev libffi-dev g++ postgresql-dev mariadb-dev \
musl-dev rust cargo patchelf
build-base gcc python3-dev musl-dev gettext-dev libffi-dev g++ \
postgresql-dev mariadb-dev libxml2-dev libxslt-dev \
musl-dev rust cargo patchelf git

COPY pyproject.toml poetry.lock ./

Expand Down
13 changes: 13 additions & 0 deletions fiesta/apps/accounts/social.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
from allauth.socialaccount.adapter import DefaultSocialAccountAdapter
from allauth.socialaccount.models import SocialAccount, SocialLogin
from django.http import HttpRequest

from apps.esnaccounts.provider import ESNAccountsProvider


class SocialAccountAdapter(DefaultSocialAccountAdapter):
def pre_social_login(self, request: HttpRequest, login: SocialLogin):

sa: SocialAccount = login.account
if sa.provider == ESNAccountsProvider.id:
ESNAccountsProvider.pre_social_login(request, login)
26 changes: 26 additions & 0 deletions fiesta/apps/accounts/templates/accounts/profile.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
{% extends "fiesta/base.html" %}

{% block main %}
<ul class="list-disc">
{% for sa in request.user.socialaccount_set.all %}
<li>
{% if sa.provider == 'esnaccounts' %}
<ul class="list-disc">
{% for role in sa.extra_data.roles %}
<li>role: {{ role }}</li>
{% endfor %}

<li>joined: {{ sa.date_joined }}
<li>email: {{ sa.extra_data.mail }}
<li>uid: {{ sa.extra_data.uid }}
<li><img src="{{ sa.extra_data.picture }}" width="60px">
<li>data: {{ sa.extra_data }}
</ul>
{% else %}
{{ sa.provider }}
{% endif %}
</li>
{% endfor %}
</ul>

{% endblock %}
3 changes: 3 additions & 0 deletions fiesta/apps/accounts/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,14 @@
# Define your urls here
from django.views.generic import TemplateView

from apps.accounts.views.profile import ProfileView

urlpatterns = [
path(
"auth/login",
TemplateView.as_view(template_name="accounts/auth/login.html"),
name="login",
),
path("profile", ProfileView.as_view()),
path("", TemplateView.as_view(template_name="accounts/index.html")),
]
Empty file.
9 changes: 9 additions & 0 deletions fiesta/apps/accounts/views/profile.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
from django.utils.translation import gettext_lazy as _
from django.views.generic import TemplateView

from apps.utils.breadcrumbs import with_breadcrumb


@with_breadcrumb(_("My Profile"))
class ProfileView(TemplateView):
template_name = "accounts/profile.html"
1 change: 1 addition & 0 deletions fiesta/apps/esnaccounts/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
__version__ = "0.5.0"
6 changes: 6 additions & 0 deletions fiesta/apps/esnaccounts/apps.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
from django.apps import AppConfig


class ESNAccountsConfig(AppConfig):
default_auto_field = "django.db.models.BigAutoField"
name = "apps.esnaccounts"
74 changes: 74 additions & 0 deletions fiesta/apps/esnaccounts/provider.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import typing

from allauth.socialaccount.providers.base import ProviderAccount
from allauth_cas.providers import CASProvider
from django.http import HttpRequest

from apps.accounts.models import User
from apps.sections.models import Section, SectionMembership

if typing.TYPE_CHECKING:
from allauth.socialaccount.models import SocialAccount, SocialLogin


class ESNAccountsAccount(ProviderAccount):
def get_avatar_url(self):
sa: "SocialAccount" = self.account
return sa.extra_data.get("picture")


class ESNAccountsProvider(CASProvider):
id = "esnaccounts"
name = "ESN Accounts"
account_class = ESNAccountsAccount

def extract_common_fields(self, data):
uid, extra = data
return {
"username": extra.get("username", uid),
"email": extra.get("mail"),
"first_name": extra.get("first"),
"last_name": extra.get("last"),
}

MEMBER_ROLE = "Local.activeMember"
EDITOR_ROLE = "Local.regularBoardMember"

@classmethod
def pre_social_login(
cls,
request: HttpRequest,
login: "SocialLogin",
):
user: User = login.user
sa: SocialAccount = login.account
roles = sa.extra_data.get("roles", [])
section_code = sa.extra_data.get("sc")
section_name = sa.extra_data.get("section")
user_nationality = sa.extra_data.get("nationality")
# national_section = sa.extra_data.get("country")

user.save()
SectionMembership.objects.update_or_create(
user=user,
section=Section.objects.get_or_create(
name=section_name,
defaults=dict(
code=section_code,
# TODO: definitely not, user nationality != section assignment
country=user_nationality,
),
)[0],
defaults=dict(
# TODO: check all possible for ESN Accounts roles
state=SectionMembership.State.ACTIVE,
role=SectionMembership.Role.EDITOR
if cls.EDITOR_ROLE in roles
else SectionMembership.Role.MEMBER
if cls.MEMBER_ROLE in roles
else SectionMembership.Role.INTERNATIONAL,
),
)


provider_classes = [ESNAccountsProvider]
5 changes: 5 additions & 0 deletions fiesta/apps/esnaccounts/urls.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
from allauth_cas.urls import default_urlpatterns

from .provider import ESNAccountsProvider

urlpatterns = default_urlpatterns(ESNAccountsProvider)
16 changes: 16 additions & 0 deletions fiesta/apps/esnaccounts/views.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
from allauth_cas.views import CASAdapter, CASCallbackView, CASLoginView, CASLogoutView

from .provider import ESNAccountsProvider


class ESNAccountsAdapter(CASAdapter):
provider_id = ESNAccountsProvider.id
url = "https://accounts.esn.org/cas/"
version = 3


login = CASLoginView.adapter_view(ESNAccountsAdapter)

callback = CASCallbackView.adapter_view(ESNAccountsAdapter)

logout = CASLogoutView.adapter_view(ESNAccountsAdapter)
2 changes: 2 additions & 0 deletions fiesta/apps/sections/models/membership.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,8 @@ class State(models.TextChoices):
verbose_name=_("membership state"),
)

# TODO: add flag to signalize, if membership has been added from ESN Accounts

class Meta:
verbose_name = _("section membership")
verbose_name_plural = _("section memberships")
Expand Down
10 changes: 10 additions & 0 deletions fiesta/apps/sections/models/section.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,16 @@ class Section(BaseTimestampedModel):
help_text=_("Universities, for whose this section offers services."),
)

code = models.SlugField(
verbose_name=_("code"),
help_text=_(
"Official code used in ESN world, especially in ESN Accounts database."
),
# TODO: remove blankness after proper migration from ESN accounts
null=True,
blank=True,
)

class Meta:
verbose_name = _("ESN section")
verbose_name_plural = _("ESN sections")
Expand Down
5 changes: 2 additions & 3 deletions fiesta/apps/utils/templatetags/breadcrumbs.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@

from django import template
from django.template.loader import render_to_string
from django.views import View
from django.utils.translation import gettext_lazy as _
from django.views import View

from apps.plugins.middleware.plugin import HttpRequest

Expand Down Expand Up @@ -36,8 +36,7 @@ def breadcrumbs(context: dict):
[
# TODO: slash is not always the home page?
BreadcrumbTitle(
req.membership.section if req.membership else _('Home'),
"/"
req.membership.section if req.membership else _("Home"), "/"
),
BreadcrumbTitle(apps.title, f"/{apps.url_prefix}")
if (plugin := req.plugin) and (apps := plugin.app_config)
Expand Down
17 changes: 12 additions & 5 deletions fiesta/fiesta/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,20 +53,22 @@
"webpack_loader",
"django_htmx",
# Fiesta apps
"apps.plugins.apps.PluginsConfig",
"apps.accounts.apps.AccountsConfig",
"apps.universities.apps.UniversitiesConfig",
"apps.sections.apps.SectionsConfig",
"apps.utils.apps.UtilsConfig",
"apps.esnaccounts", # cannot have full config Path, since allauth/socialaccount/providers/__init__.py:38 sucks
"apps.esncards.apps.ESNcardsConfig",
"apps.fiestaforms.apps.FiestaformsConfig",
"apps.plugins.apps.PluginsConfig",
"apps.sections.apps.SectionsConfig",
"apps.universities.apps.UniversitiesConfig",
"apps.utils.apps.UtilsConfig",
# Debugs
"django_extensions",
# django-allauth
"allauth",
"allauth.account",
"allauth.socialaccount",
"allauth.socialaccount.providers.facebook",
"allauth_cas",
]

MIDDLEWARE = [
Expand Down Expand Up @@ -164,6 +166,8 @@

SITE_ID = 1

SECURE_PROXY_SSL_HEADER = "HTTP_X_FORWARDED_SSL", "on"

SOCIALACCOUNT_PROVIDERS = {
"facebook": {
"METHOD": "oauth2",
Expand All @@ -184,7 +188,8 @@
"LOCALE_FUNC": lambda request: "en",
"VERIFIED_EMAIL": False,
"VERSION": "v12.0",
}
},
"esnaccounts": {},
}

ACCOUNT_AUTHENTICATED_LOGIN_REDIRECTS = False
Expand All @@ -195,8 +200,10 @@
ACCOUNT_USERNAME_MIN_LENGTH = 4 # a personal preference
ACCOUNT_SESSION_REMEMBER = True # None by default (to ask 'Remember me?'). I want the user to be always logged in
ACCOUNT_DEFAULT_HTTP_PROTOCOL = "https"
SOCIALACCOUNT_ADAPTER = "apps.accounts.social.SocialAccountAdapter"

LOGIN_URL = "/accounts/auth/login"
LOGIN_REDIRECT_URL = "/accounts/profile"

# Internationalization
# https://docs.djangoproject.com/en/4.0/topics/i18n/
Expand Down
10 changes: 8 additions & 2 deletions fiesta/fiesta/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,14 @@
)
for app in all_plugin_apps()
] + [
path("", TemplateView.as_view(template_name="fiesta/pages/public.html"), name='home'),
path("team", TemplateView.as_view(template_name="fiesta/pages/team.html"), name='team'),
path(
"", TemplateView.as_view(template_name="fiesta/pages/public.html"), name="home"
),
path(
"team",
TemplateView.as_view(template_name="fiesta/pages/team.html"),
name="team",
),
path("admin/", admin.site.urls),
path("auth/", include("allauth.urls")),
]
Expand Down
File renamed without changes.
Loading

0 comments on commit 2926377

Please sign in to comment.