Skip to content

Commit

Permalink
add dynamic_settings
Browse files Browse the repository at this point in the history
  • Loading branch information
felixrindt committed Dec 26, 2024
1 parent 9e8f868 commit 34ba262
Show file tree
Hide file tree
Showing 17 changed files with 70 additions and 44 deletions.
25 changes: 25 additions & 0 deletions ephios/core/dynamic.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
class DynamicSettingsProxy:
"""
Proxy access to django settings but first check if any receiver overwrites it with a dynamic value.
"""

NONE = object()

def __init__(self):
from django.conf import settings

self._django_settings = settings

def __getattr__(self, name):
from ephios.core.signals import provide_dynamic_settings

for _, result in provide_dynamic_settings.send(None, name=name):
if result is not None:
if result is DynamicSettingsProxy.NONE:
return None
return result
# default to django settings
return getattr(self._django_settings, f"DEFAULT_{name}")


dynamic_settings = DynamicSettingsProxy()
4 changes: 2 additions & 2 deletions ephios/core/ical.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
from django.conf import settings
from django.contrib.auth import get_user_model
from django.db.models import Prefetch
from django.shortcuts import get_object_or_404
from django_ical.views import ICalFeed
from guardian.shortcuts import get_users_with_perms
from icalendar import vCalAddress

from ephios.core.dynamic import dynamic_settings
from ephios.core.models import AbstractParticipation, Shift


Expand Down Expand Up @@ -38,7 +38,7 @@ def item_location(self, item):
return item.event.location

def item_guid(self, item):
return f"{item.pk}@{settings.GET_SITE_URL()}"
return f"{item.pk}@{dynamic_settings.SITE_URL}"

def item_organizer(self, item):
user = get_users_with_perms(item.event, only_with_perms_in=["change_event"]).first()
Expand Down
2 changes: 1 addition & 1 deletion ephios/core/migrations/0022_identityprovider.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@


def migrate_oidc_provider(apps, schema_editor):
from ephios import settings
from django.conf import settings

try:
if settings.env.bool("ENABLE_OIDC_CLIENT"):
Expand Down
10 changes: 6 additions & 4 deletions ephios/core/services/files.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@
from django.urls import reverse
from django.views import View

from ephios.core.dynamic import dynamic_settings


class UserContentView(View):
"""
Expand All @@ -22,7 +24,7 @@ class UserContentView(View):
def dispatch(self, request, *args, **kwargs):
# If media files are served from a different domain and
# the user requests media files from the app domain via this view --> 404
if (loc := urlsplit(settings.GET_USERCONTENT_URL()).netloc) and request.get_host() != loc:
if (loc := urlsplit(dynamic_settings.USERCONTENT_URL).netloc) and request.get_host() != loc:
raise PermissionDenied()
return super().dispatch(request, *args, **kwargs)

Expand Down Expand Up @@ -64,7 +66,7 @@ def redirect_to_file_download(field_file):
"""
Shortcut for redirecting to the ticketed media file download view.
"""
if loc := urlsplit(settings.GET_USERCONTENT_URL()).netloc:
if loc := urlsplit(dynamic_settings.USERCONTENT_URL).netloc:
ticket = file_ticket(field_file)
path = reverse("core:file_ticket", kwargs={"ticket": ticket})
return redirect(urlunsplit(("http" if settings.DEBUG else "https", loc, path, "", "")))
Expand All @@ -81,9 +83,9 @@ def __call__(self, request):
response = self.get_response(request)
if (
# if the usercontent URL does not contain a domain, the request host will be checked against `None` --> no redirect loop
request.get_host() == urlsplit(settings.GET_USERCONTENT_URL()).netloc
request.get_host() == urlsplit(dynamic_settings.USERCONTENT_URL).netloc
and request.resolver_match
and not getattr(request.resolver_match.func.view_class, "is_usercontent_view", False)
):
return redirect(urljoin(settings.GET_SITE_URL(), request.path))
return redirect(urljoin(dynamic_settings.SITE_URL, request.path))
return response
4 changes: 2 additions & 2 deletions ephios/core/services/notifications/types.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
from typing import List
from urllib.parse import urlparse

from django.conf import settings
from django.contrib.auth.tokens import default_token_generator
from django.template.loader import render_to_string
from django.urls import reverse
Expand All @@ -13,6 +12,7 @@
from guardian.shortcuts import get_users_with_perms
from requests import PreparedRequest

from ephios.core.dynamic import dynamic_settings
from ephios.core.models import AbstractParticipation, Event, LocalParticipation, UserProfile
from ephios.core.models.users import Consequence, Notification
from ephios.core.signals import register_notification_types
Expand Down Expand Up @@ -98,7 +98,7 @@ def get_actions_with_referrer(cls, notification):
"""
actions = []
for label, url in cls.get_actions(notification):
if urlparse(url).netloc in settings.GET_SITE_URL():
if urlparse(url).netloc in dynamic_settings.SITE_URL:
req = PreparedRequest()
req.prepare_url(url, {NOTIFICATION_READ_PARAM_NAME: notification.pk})
url = req.url
Expand Down
5 changes: 3 additions & 2 deletions ephios/core/services/password_reset.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import logging

from django.conf import settings
from django.contrib.auth.password_validation import MinimumLengthValidator
from django.core.mail import EmailMultiAlternatives
from django.template.loader import render_to_string
from django.utils.translation import gettext as _

from ephios.core.dynamic import dynamic_settings

logger = logging.getLogger(__name__)


Expand All @@ -19,7 +20,7 @@ def password_changed(self, password, user):
# send notification to user
text_content = _(
"Your password for {site} has been changed. If you didn't request this change, contact an administrator immediately."
).format(site=settings.GET_SITE_URL())
).format(site=dynamic_settings.SITE_URL)
html_content = render_to_string(
"core/mails/base.html",
{
Expand Down
8 changes: 8 additions & 0 deletions ephios/core/signals.py
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,14 @@
"""


provide_dynamic_settings = PluginSignal()
"""
Use this signal to overwrite the defaults of django settings accessed
using ``ephios.core.signals.DynamicSettingsProxy``.
Receivers receive a ``name`` keyword argument naming the setting.
"""


@receiver(
register_consequence_handlers,
dispatch_uid="ephios.core.signals.register_base_consequence_handlers",
Expand Down
6 changes: 3 additions & 3 deletions ephios/core/templatetags/settings_extras.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
from urllib.parse import urljoin

from django import template
from django.conf import settings
from dynamic_preferences.registries import global_preferences_registry

from ephios.core.dynamic import dynamic_settings
from ephios.core.models.users import IdentityProvider

register = template.Library()
Expand All @@ -23,7 +23,7 @@ def identity_providers():

@register.simple_tag
def site_url():
return settings.GET_SITE_URL()
return dynamic_settings.SITE_URL


@register.simple_tag
Expand All @@ -33,7 +33,7 @@ def organization_name():

@register.filter
def make_absolute(location):
return urljoin(settings.GET_SITE_URL(), location)
return urljoin(dynamic_settings.SITE_URL, location)


@register.simple_tag(takes_context=True)
Expand Down
6 changes: 3 additions & 3 deletions ephios/core/views/auth.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
from urllib.parse import urljoin

import requests
from django.conf import settings
from django.contrib import auth, messages
from django.contrib.auth.views import LoginView
from django.contrib.messages.views import SuccessMessageMixin
Expand All @@ -23,6 +22,7 @@
from requests import PreparedRequest, RequestException
from requests_oauthlib import OAuth2Session

from ephios.core.dynamic import dynamic_settings
from ephios.core.dynamic_preferences_registry import LoginRedirectToSoleIndentityProvider
from ephios.core.forms.auth import OIDCLoginForm
from ephios.core.forms.users import IdentityProviderForm, OIDCDiscoveryForm
Expand All @@ -38,7 +38,7 @@ def get_redirect_url(self, *args, **kwargs):
oauth_client = WebApplicationClient(client_id=provider.client_id)
oauth = OAuth2Session(
client=oauth_client,
redirect_uri=urljoin(settings.GET_SITE_URL(), reverse("core:oidc_callback")),
redirect_uri=urljoin(dynamic_settings.SITE_URL, reverse("core:oidc_callback")),
scope=provider.scopes,
)

Expand Down Expand Up @@ -93,7 +93,7 @@ def get_redirect_url(self, *args, **kwargs):
{}
if auto_provider_redirect
else {
"post_logout_redirect_uri": settings.GET_SITE_URL(),
"post_logout_redirect_uri": dynamic_settings.SITE_URL,
"client_id": provider.client_id,
}
),
Expand Down
4 changes: 2 additions & 2 deletions ephios/extra/auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
from urllib.parse import urljoin

import jwt
from django.conf import settings
from django.contrib.auth import get_user_model
from django.contrib.auth.backends import ModelBackend
from django.contrib.auth.models import Group
Expand All @@ -16,6 +15,7 @@
from requests_oauthlib import OAuth2Session
from urllib3.exceptions import RequestError

from ephios.core.dynamic import dynamic_settings
from ephios.core.models import Qualification
from ephios.core.models.users import IdentityProvider, QualificationGrant
from ephios.core.signals import oidc_update_user
Expand Down Expand Up @@ -99,7 +99,7 @@ def authenticate(self, request, username=None, password=None, **kwargs):
self.provider = IdentityProvider.objects.get(id=request.session["oidc_provider"])
oauth = OAuth2Session(
client=WebApplicationClient(client_id=self.provider.client_id),
redirect_uri=urljoin(settings.GET_SITE_URL(), reverse("core:oidc_callback")),
redirect_uri=urljoin(dynamic_settings.SITE_URL, reverse("core:oidc_callback")),
)
token = oauth.fetch_token(
self.provider.token_endpoint,
Expand Down
5 changes: 3 additions & 2 deletions ephios/extra/templatetags/rich_text.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,11 @@
import markdown
from bleach.linkifier import DEFAULT_CALLBACKS
from django import template
from django.conf import settings
from django.utils.http import url_has_allowed_host_and_scheme
from django.utils.safestring import mark_safe

from ephios.core.dynamic import dynamic_settings

register = template.Library()

ALLOWED_TAGS = {
Expand Down Expand Up @@ -80,7 +81,7 @@ def safelink_callback(attrs, new=False):
if not url_has_allowed_host_and_scheme(
url,
allowed_hosts=[
urlparse(settings.GET_SITE_URL()).netloc,
urlparse(dynamic_settings.SITE_URL).netloc,
],
):
attrs[None, "target"] = "_blank"
Expand Down
4 changes: 2 additions & 2 deletions ephios/plugins/federation/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,11 @@
from urllib.parse import urlparse

from django import forms
from django.conf import settings
from django.core.exceptions import ValidationError
from django.forms import CheckboxSelectMultiple
from django.utils.translation import gettext as _

from ephios.core.dynamic import dynamic_settings
from ephios.core.forms.events import BasePluginFormMixin
from ephios.plugins.federation.models import FederatedEventShare, FederatedGuest, InviteCode

Expand Down Expand Up @@ -66,7 +66,7 @@ class RedeemInviteCodeForm(forms.Form):
def clean_code(self):
try:
data = json.loads(base64.b64decode(self.cleaned_data["code"].encode()).decode())
if settings.GET_SITE_URL() != data["guest_url"]:
if dynamic_settings.SITE_URL != data["guest_url"]:
raise ValidationError(_("This invite code is not issued for this instance."))
except (binascii.Error, JSONDecodeError, KeyError) as exc:
raise ValidationError(_("Invalid code")) from exc
Expand Down
4 changes: 2 additions & 2 deletions ephios/plugins/federation/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,14 @@
import json
from secrets import token_hex

from django.conf import settings
from django.db import models
from django.urls import reverse
from django.utils import timezone
from django.utils.safestring import mark_safe
from django.utils.translation import gettext_lazy as _

from ephios.api.models import AccessToken, Application
from ephios.core.dynamic import dynamic_settings
from ephios.core.models import AbstractParticipation, Event, Qualification
from ephios.core.models.events import PARTICIPATION_LOG_CONFIG
from ephios.core.signup.participants import AbstractParticipant
Expand Down Expand Up @@ -93,7 +93,7 @@ def is_expired(self):
def get_share_string(self):
return base64.b64encode(
json.dumps(
{"guest_url": self.url, "code": self.code, "host_url": settings.GET_SITE_URL()}
{"guest_url": self.url, "code": self.code, "host_url": dynamic_settings.SITE_URL}
).encode()
).decode()

Expand Down
4 changes: 2 additions & 2 deletions ephios/plugins/federation/serializers.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import secrets
from urllib.parse import urljoin

from django.conf import settings
from django.urls import reverse
from dynamic_preferences.registries import global_preferences_registry
from rest_framework import serializers

from ephios.api.models import AccessToken
from ephios.api.serializers import EventSerializer
from ephios.core.dynamic import dynamic_settings
from ephios.core.models import Event
from ephios.plugins.federation.models import FederatedGuest, InviteCode

Expand Down Expand Up @@ -71,7 +71,7 @@ class Meta:

def get_signup_url(self, obj):
return urljoin(
settings.GET_SITE_URL(),
dynamic_settings.SITE_URL,
reverse(
"federation:event_detail",
kwargs={"pk": obj.pk, "guest": self.context["federated_guest"].pk},
Expand Down
5 changes: 2 additions & 3 deletions ephios/plugins/federation/views/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

import django_filters
import requests
from django.conf import settings
from django.core.exceptions import MultipleObjectsReturned
from django.db.models import Max, Min
from django.shortcuts import redirect
Expand Down Expand Up @@ -126,7 +125,7 @@ def _get_authorization_url(self):
oauth_client = WebApplicationClient(client_id=self.guest.client_id)
oauth = OAuth2Session(
client=oauth_client,
redirect_uri=urljoin(settings.GET_SITE_URL(), reverse("federation:oauth_callback")),
redirect_uri=urljoin(dynamic_settings.SITE_URL, reverse("federation:oauth_callback")),
scope=["ME_READ"],
)
verifier = oauth_client.create_code_verifier(64)
Expand All @@ -148,7 +147,7 @@ def _oauth_callback(self):
oauth = OAuth2Session(client=oauth_client)
token = oauth.fetch_token(
urljoin(self.guest.url, "api/oauth/token/"),
authorization_response=urljoin(settings.GET_SITE_URL(), self.request.get_full_path()),
authorization_response=urljoin(dynamic_settings.SITE_URL, self.request.get_full_path()),
client_secret=self.guest.client_secret,
code_verifier=self.request.session["code_verifier"],
)
Expand Down
14 changes: 3 additions & 11 deletions ephios/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -234,6 +234,9 @@
MEDIA_URL = env.str("MEDIA_URL", default="/usercontent/")
FALLBACK_MEDIA_SERVING = env.bool("FALLBACK_MEDIA_SERVING", default=DEBUG)

DEFAULT_SITE_URL = env.str("SITE_URL").rstrip("/")
DEFAULT_USERCONTENT_URL = MEDIA_URL

STATICFILES_DIRS = (os.path.join(BASE_DIR, "ephios/static"),)
STATICFILES_FINDERS = [
"django.contrib.staticfiles.finders.FileSystemFinder",
Expand Down Expand Up @@ -318,17 +321,6 @@
}


def GET_SITE_URL():
site_url = env.str("SITE_URL")
if site_url.endswith("/"):
site_url = site_url[:-1]
return site_url


def GET_USERCONTENT_URL():
return MEDIA_URL


def GET_USERCONTENT_QUOTA():
"""Returns a tuple (used, free) of the user content quota in bytes"""
used = sum(p.stat().st_size for p in Path(MEDIA_ROOT).rglob("*"))
Expand Down
Loading

0 comments on commit 34ba262

Please sign in to comment.