Skip to content

Commit

Permalink
add federation (#972)
Browse files Browse the repository at this point in the history
* add federation plugin, add federation models, add API route for shared events

* handle AccessToken that does not belong to a FederatedGuest

* add frontend view to display incoming events, add OAuth2 flow, add API route for own user data

* add nav link, use correct signup url

* check access token validity before serving the event detail view

* add shift signup flow for federated users, add referrer to start correct OAuth2 flow,

* set session expiration

* add QualificationSerializer

* respect included qualifications

* improve incoming event view

* make pylint happy

* add form to select guests to share an event with

* add RedeemFederationInviteCodeView

* finish invite flow

* fix event create

* fix event create

* fix event create

* fix naming

* add delete views for host and guest, handle invite code expiration

* add InviteCodeRevealView, add explanations and experimental labels

* remove expired invite codes

* fix authorize translation

* address some review comments

* address more review comments

* address more reivew comments

* add flow to tear down federation connection

* fix poetry.lock

* fix tests

* fix linter

* address review comments

* fix css for external event list

* start adding tests

* add test_redeem_invitecode_api

* add shared_event_list_test

* add shared_event_detail test

* fix lockfile

* remove django_db_serialized_rollback

* lint templates

* run all tests with rollback emulation

* enable OAuth testing

* fix poetry.lock

* add translations

---------

Co-authored-by: Felix Rindt <[email protected]>
  • Loading branch information
jeriox and felixrindt authored Oct 7, 2023
1 parent 34fee3e commit 078333b
Show file tree
Hide file tree
Showing 40 changed files with 1,937 additions and 94 deletions.
2 changes: 1 addition & 1 deletion ephios/api/access/auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ def authenticate(self, request):
if oauth_result is None:
return None
user, token = oauth_result
if not user.is_active:
if user is not None and not user.is_active:
return None
return user, token

Expand Down
4 changes: 4 additions & 0 deletions ephios/api/templates/api/access_token_list.html
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,10 @@ <h5 class="card-title">
{{ token.application.name }}
</h5>
<p class="text-body-secondary">
{# this belongs to the federation plugin, but we display the information here for better understanding #}
{% if token.application.federatedhost %}
{% translate "ephios instance" %}
{% endif %}
</p>
</div>
<div class="col-12 col-lg">
Expand Down
38 changes: 23 additions & 15 deletions ephios/api/templates/oauth2_provider/application_list.html
Original file line number Diff line number Diff line change
Expand Up @@ -33,21 +33,29 @@ <h4>{% translate "OAuth2 applications" %}</h4>
<td>{{ application.created }}</td>
<td class="d-flex">
<div>
<a class="btn btn-secondary"
href="{{ application.get_absolute_url }}">
<span class="fa fa-eye"></span>
<span class="d-none d-lg-inline">{% translate "View" %}</span>
</a>
<a class="btn btn-secondary ms-1"
href="{% url "api:settings-oauth-app-update" application.id %}">
<span class="fa fa-edit"></span>
<span class="d-none d-lg-inline">{% translate "Edit" %}</span>
</a>
<a class="btn btn-secondary ms-1"
href="{% url "api:settings-oauth-app-delete" application.id %}">
<span class="fa fa-trash-alt"></span>
<span class="d-none d-lg-inline">{% translate "Delete" %}</span>
</a>
{% if application.federatedhost %}
<a class="btn btn-secondary"
href="{% url "federation:settings" %}">
<span class="fa fa-cog"></span>
<span class="d-none d-lg-inline">{% translate "Manage federation" %}</span>
</a>
{% else %}
<a class="btn btn-secondary"
href="{{ application.get_absolute_url }}">
<span class="fa fa-eye"></span>
<span class="d-none d-lg-inline">{% translate "View" %}</span>
</a>
<a class="btn btn-secondary ms-1"
href="{% url "api:settings-oauth-app-update" application.id %}">
<span class="fa fa-edit"></span>
<span class="d-none d-lg-inline">{% translate "Edit" %}</span>
</a>
<a class="btn btn-secondary ms-1"
href="{% url "api:settings-oauth-app-delete" application.id %}">
<span class="fa fa-trash-alt"></span>
<span class="d-none d-lg-inline">{% translate "Delete" %}</span>
</a>
{% endif %}
</div>
</td>
</tr>
Expand Down
6 changes: 5 additions & 1 deletion ephios/api/templates/oauth2_provider/authorize.html
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,11 @@
{% block content %}
{% if not error %}
<form id="authorizationForm" method="post">
<h3 class="block-center-heading">{% translate "Authorize" %} {{ application.name }}?</h3>
<h3 class="block-center-heading">
{% blocktranslate trimmed with app_name=application.name %}
Authorize {{ app_name }}?
{% endblocktranslate %}
</h3>
{% csrf_token %}

{# pass query params through the submit process for saving #}
Expand Down
2 changes: 2 additions & 0 deletions ephios/api/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,15 @@
ApplicationDelete,
)
from ephios.api.views.events import EventViewSet
from ephios.api.views.users import UserProfileMeView
from ephios.extra.permissions import staff_required

router = routers.DefaultRouter()
router.register(r"events", EventViewSet)

app_name = "api"
urlpatterns = [
path("users/me/", UserProfileMeView.as_view(), name="user-profile-me"),
path(
"settings/",
include(
Expand Down
64 changes: 64 additions & 0 deletions ephios/api/views/users.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
from django.db.models import Q
from django.utils import timezone
from oauth2_provider.contrib.rest_framework import IsAuthenticatedOrTokenHasScope
from rest_framework.exceptions import PermissionDenied
from rest_framework.fields import SerializerMethodField
from rest_framework.generics import RetrieveAPIView
from rest_framework.relations import SlugRelatedField
from rest_framework.serializers import ModelSerializer

from ephios.core.models import Qualification, UserProfile


class QualificationSerializer(ModelSerializer):
category = SlugRelatedField(slug_field="uuid", read_only=True)
includes = SerializerMethodField()

class Meta:
model = Qualification
fields = [
"uuid",
"title",
"abbreviation",
"category",
"includes",
]

def get_includes(self, obj):
qualifications = Qualification.collect_all_included_qualifications(obj.includes.all())
return [q.uuid for q in qualifications]


class UserProfileSerializer(ModelSerializer):
qualifications = SerializerMethodField()

class Meta:
model = UserProfile
fields = [
"first_name",
"last_name",
"date_of_birth",
"email",
"qualifications",
]

def get_qualifications(self, obj):
return QualificationSerializer(
Qualification.objects.filter(
Q(grants__user=obj)
& (Q(grants__expires__gte=timezone.now()) | Q(grants__expires__isnull=True))
),
many=True,
).data


class UserProfileMeView(RetrieveAPIView):
serializer_class = UserProfileSerializer
queryset = UserProfile.objects.all()
permission_classes = [IsAuthenticatedOrTokenHasScope]
required_scopes = ["ME_READ"]

def get_object(self):
if self.request.user is None:
raise PermissionDenied()
return self.request.user
2 changes: 2 additions & 0 deletions ephios/core/context.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from django.conf import settings
from django.utils.translation import get_language
from dynamic_preferences.registries import global_preferences_registry

from ephios.core.models import AbstractParticipation
from ephios.core.signals import footer_link, nav_link
Expand All @@ -22,4 +23,5 @@ def ephios_base_context(request):
"LANGUAGE_CODE": get_language(),
"ephios_version": settings.EPHIOS_VERSION,
"PWA_APP_ICONS": settings.PWA_APP_ICONS,
"organization_name": global_preferences_registry.manager()["general__organization_name"],
}
Loading

0 comments on commit 078333b

Please sign in to comment.