2026-01-09 00:51:25 +01:00
|
|
|
"""
|
|
|
|
|
Django settings for calendar project.
|
|
|
|
|
|
|
|
|
|
Generated by 'django-admin startproject' using Django 3.1.5.
|
|
|
|
|
|
|
|
|
|
For more information on this file, see
|
|
|
|
|
https://docs.djangoproject.com/en/3.1/topics/settings/
|
|
|
|
|
|
|
|
|
|
For the full list of settings and their values, see
|
|
|
|
|
https://docs.djangoproject.com/en/3.1/ref/settings/
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
import os
|
|
|
|
|
import tomllib
|
|
|
|
|
from socket import gethostbyname, gethostname
|
|
|
|
|
|
|
|
|
|
import dj_database_url
|
|
|
|
|
import sentry_sdk
|
|
|
|
|
from configurations import Configuration, values
|
|
|
|
|
from lasuite.configuration.values import SecretFileValue
|
|
|
|
|
from sentry_sdk.integrations.django import DjangoIntegration
|
|
|
|
|
|
|
|
|
|
# pylint: disable=too-many-lines
|
|
|
|
|
|
|
|
|
|
# Build paths inside the project like this: BASE_DIR / 'subdir'.
|
|
|
|
|
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
|
|
|
|
DATA_DIR = os.environ.get("DATA_DIR", os.path.join("/", "data"))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def get_release():
|
|
|
|
|
"""
|
|
|
|
|
Get the current release of the application
|
|
|
|
|
"""
|
|
|
|
|
try:
|
|
|
|
|
with open(os.path.join(BASE_DIR, "pyproject.toml"), "rb") as f:
|
|
|
|
|
pyproject_data = tomllib.load(f)
|
|
|
|
|
return pyproject_data["project"]["version"]
|
|
|
|
|
except (FileNotFoundError, KeyError):
|
|
|
|
|
return "NA" # Default: not available
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class Base(Configuration):
|
|
|
|
|
"""
|
|
|
|
|
This is the base configuration every configuration (aka environment) should inherit from. It
|
|
|
|
|
is recommended to configure third-party applications by creating a configuration mixins in
|
|
|
|
|
./configurations and compose the Base configuration with those mixins.
|
|
|
|
|
|
|
|
|
|
It depends on an environment variable that SHOULD be defined:
|
|
|
|
|
|
|
|
|
|
* DJANGO_SECRET_KEY
|
|
|
|
|
|
|
|
|
|
You may also want to override default configuration by setting the following environment
|
|
|
|
|
variables:
|
|
|
|
|
|
|
|
|
|
* SENTRY_DSN
|
|
|
|
|
* DB_NAME
|
|
|
|
|
* DB_HOST
|
|
|
|
|
* DB_PASSWORD
|
|
|
|
|
* DB_USER
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
DEBUG = False
|
|
|
|
|
LOAD_E2E_URLS = False
|
|
|
|
|
USE_SWAGGER = False
|
|
|
|
|
|
|
|
|
|
API_VERSION = "v1.0"
|
|
|
|
|
|
2026-01-11 02:28:04 +01:00
|
|
|
# CalDAV server URL
|
|
|
|
|
CALDAV_URL = values.Value(
|
|
|
|
|
"http://caldav:80", environ_name="CALDAV_URL", environ_prefix=None
|
2026-01-09 00:51:25 +01:00
|
|
|
)
|
|
|
|
|
|
2026-01-11 03:52:43 +01:00
|
|
|
# CalDAV API keys for bidirectional authentication
|
|
|
|
|
# INBOUND: API key for authenticating requests FROM CalDAV server TO Django
|
✨(all) add organizations, resources, channels, and infra migration (#34)
Add multi-tenant organization model populated from OIDC claims with
org-scoped user discovery, CalDAV principal filtering, and cross-org
isolation at the SabreDAV layer.
Add bookable resource principals (rooms, equipment) with CalDAV
auto-scheduling that handles conflict detection, auto-accept/decline,
and org-scoped booking enforcement. Fixes #14.
Replace CalendarSubscriptionToken with a unified Channel model
supporting CalDAV integration tokens and iCal feed URLs, with
encrypted token storage and role-based access control. Fixes #16.
Migrate task queue from Celery to Dramatiq with async ICS import,
progress tracking, and task status polling endpoint.
Replace nginx with Caddy for both the reverse proxy and frontend
static serving. Switch frontend package manager from yarn/pnpm to
npm and upgrade Node to 24, Next.js to 16, TypeScript to 5.9.
Harden security with fail-closed entitlements, RSVP rate limiting
and token expiry, CalDAV proxy path validation blocking internal
API routes, channel path scope enforcement, and ETag-based
conflict prevention.
Add frontend pages for resource management and integration channel
CRUD, with resource booking in the event modal.
Restructure CalDAV paths to /calendars/users/ and
/calendars/resources/ with nested principal collections in SabreDAV.
2026-03-09 09:09:34 +01:00
|
|
|
CALDAV_INBOUND_API_KEY = SecretFileValue(
|
2026-01-11 03:52:43 +01:00
|
|
|
None, environ_name="CALDAV_INBOUND_API_KEY", environ_prefix=None
|
|
|
|
|
)
|
|
|
|
|
# OUTBOUND: API key for authenticating requests FROM Django TO CalDAV server
|
✨(all) add organizations, resources, channels, and infra migration (#34)
Add multi-tenant organization model populated from OIDC claims with
org-scoped user discovery, CalDAV principal filtering, and cross-org
isolation at the SabreDAV layer.
Add bookable resource principals (rooms, equipment) with CalDAV
auto-scheduling that handles conflict detection, auto-accept/decline,
and org-scoped booking enforcement. Fixes #14.
Replace CalendarSubscriptionToken with a unified Channel model
supporting CalDAV integration tokens and iCal feed URLs, with
encrypted token storage and role-based access control. Fixes #16.
Migrate task queue from Celery to Dramatiq with async ICS import,
progress tracking, and task status polling endpoint.
Replace nginx with Caddy for both the reverse proxy and frontend
static serving. Switch frontend package manager from yarn/pnpm to
npm and upgrade Node to 24, Next.js to 16, TypeScript to 5.9.
Harden security with fail-closed entitlements, RSVP rate limiting
and token expiry, CalDAV proxy path validation blocking internal
API routes, channel path scope enforcement, and ETag-based
conflict prevention.
Add frontend pages for resource management and integration channel
CRUD, with resource booking in the event modal.
Restructure CalDAV paths to /calendars/users/ and
/calendars/resources/ with nested principal collections in SabreDAV.
2026-03-09 09:09:34 +01:00
|
|
|
CALDAV_OUTBOUND_API_KEY = SecretFileValue(
|
2026-01-11 03:52:43 +01:00
|
|
|
None, environ_name="CALDAV_OUTBOUND_API_KEY", environ_prefix=None
|
|
|
|
|
)
|
✨(all) add organizations, resources, channels, and infra migration (#34)
Add multi-tenant organization model populated from OIDC claims with
org-scoped user discovery, CalDAV principal filtering, and cross-org
isolation at the SabreDAV layer.
Add bookable resource principals (rooms, equipment) with CalDAV
auto-scheduling that handles conflict detection, auto-accept/decline,
and org-scoped booking enforcement. Fixes #14.
Replace CalendarSubscriptionToken with a unified Channel model
supporting CalDAV integration tokens and iCal feed URLs, with
encrypted token storage and role-based access control. Fixes #16.
Migrate task queue from Celery to Dramatiq with async ICS import,
progress tracking, and task status polling endpoint.
Replace nginx with Caddy for both the reverse proxy and frontend
static serving. Switch frontend package manager from yarn/pnpm to
npm and upgrade Node to 24, Next.js to 16, TypeScript to 5.9.
Harden security with fail-closed entitlements, RSVP rate limiting
and token expiry, CalDAV proxy path validation blocking internal
API routes, channel path scope enforcement, and ETag-based
conflict prevention.
Add frontend pages for resource management and integration channel
CRUD, with resource booking in the event modal.
Restructure CalDAV paths to /calendars/users/ and
/calendars/resources/ with nested principal collections in SabreDAV.
2026-03-09 09:09:34 +01:00
|
|
|
# INTERNAL: API key for Django → CalDAV internal API (resource provisioning, import)
|
|
|
|
|
CALDAV_INTERNAL_API_KEY = SecretFileValue(
|
|
|
|
|
None, environ_name="CALDAV_INTERNAL_API_KEY", environ_prefix=None
|
|
|
|
|
)
|
2026-03-10 01:30:42 +01:00
|
|
|
|
|
|
|
|
# Default calendar sharing level for new organizations.
|
|
|
|
|
# Controls what colleagues in the same org can see by default.
|
|
|
|
|
# Values: "none", "freebusy", "read", "write"
|
|
|
|
|
ORG_DEFAULT_SHARING_LEVEL = values.Value(
|
|
|
|
|
"freebusy",
|
|
|
|
|
environ_name="ORG_DEFAULT_SHARING_LEVEL",
|
|
|
|
|
environ_prefix=None,
|
|
|
|
|
)
|
✨(all) add organizations, resources, channels, and infra migration (#34)
Add multi-tenant organization model populated from OIDC claims with
org-scoped user discovery, CalDAV principal filtering, and cross-org
isolation at the SabreDAV layer.
Add bookable resource principals (rooms, equipment) with CalDAV
auto-scheduling that handles conflict detection, auto-accept/decline,
and org-scoped booking enforcement. Fixes #14.
Replace CalendarSubscriptionToken with a unified Channel model
supporting CalDAV integration tokens and iCal feed URLs, with
encrypted token storage and role-based access control. Fixes #16.
Migrate task queue from Celery to Dramatiq with async ICS import,
progress tracking, and task status polling endpoint.
Replace nginx with Caddy for both the reverse proxy and frontend
static serving. Switch frontend package manager from yarn/pnpm to
npm and upgrade Node to 24, Next.js to 16, TypeScript to 5.9.
Harden security with fail-closed entitlements, RSVP rate limiting
and token expiry, CalDAV proxy path validation blocking internal
API routes, channel path scope enforcement, and ETag-based
conflict prevention.
Add frontend pages for resource management and integration channel
CRUD, with resource booking in the event modal.
Restructure CalDAV paths to /calendars/users/ and
/calendars/resources/ with nested principal collections in SabreDAV.
2026-03-09 09:09:34 +01:00
|
|
|
# Salt for django-fernet-encrypted-fields (Channel tokens, etc.)
|
|
|
|
|
# Used with SECRET_KEY to derive Fernet encryption keys via PBKDF2
|
|
|
|
|
SALT_KEY = values.Value(
|
|
|
|
|
"calendars-default-salt-change-in-production",
|
|
|
|
|
environ_name="SALT_KEY",
|
|
|
|
|
environ_prefix=None,
|
|
|
|
|
)
|
2026-01-25 20:33:11 +01:00
|
|
|
# Base URL for CalDAV scheduling callbacks (must be accessible from CalDAV container)
|
|
|
|
|
# In Docker environments, use the internal Docker network URL (e.g., http://backend:8000)
|
|
|
|
|
CALDAV_CALLBACK_BASE_URL = values.Value(
|
|
|
|
|
None, environ_name="CALDAV_CALLBACK_BASE_URL", environ_prefix=None
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
# Email configuration
|
|
|
|
|
# Default settings - override in environment-specific classes
|
|
|
|
|
EMAIL_BACKEND = values.Value(
|
|
|
|
|
"django.core.mail.backends.smtp.EmailBackend",
|
|
|
|
|
environ_name="EMAIL_BACKEND",
|
|
|
|
|
environ_prefix=None,
|
|
|
|
|
)
|
|
|
|
|
EMAIL_HOST = values.Value(
|
|
|
|
|
"localhost", environ_name="EMAIL_HOST", environ_prefix=None
|
|
|
|
|
)
|
|
|
|
|
EMAIL_PORT = values.IntegerValue(25, environ_name="EMAIL_PORT", environ_prefix=None)
|
|
|
|
|
EMAIL_HOST_USER = values.Value(
|
|
|
|
|
"", environ_name="EMAIL_HOST_USER", environ_prefix=None
|
|
|
|
|
)
|
|
|
|
|
EMAIL_HOST_PASSWORD = SecretFileValue(
|
|
|
|
|
"", environ_name="EMAIL_HOST_PASSWORD", environ_prefix=None
|
|
|
|
|
)
|
|
|
|
|
EMAIL_USE_TLS = values.BooleanValue(
|
|
|
|
|
False, environ_name="EMAIL_USE_TLS", environ_prefix=None
|
|
|
|
|
)
|
|
|
|
|
EMAIL_USE_SSL = values.BooleanValue(
|
|
|
|
|
False, environ_name="EMAIL_USE_SSL", environ_prefix=None
|
|
|
|
|
)
|
|
|
|
|
DEFAULT_FROM_EMAIL = values.Value(
|
|
|
|
|
"noreply@example.com", environ_name="DEFAULT_FROM_EMAIL", environ_prefix=None
|
|
|
|
|
)
|
|
|
|
|
# Calendar-specific email settings
|
|
|
|
|
CALENDAR_INVITATION_FROM_EMAIL = values.Value(
|
|
|
|
|
None, environ_name="CALENDAR_INVITATION_FROM_EMAIL", environ_prefix=None
|
|
|
|
|
)
|
✨(all) add organizations, resources, channels, and infra migration (#34)
Add multi-tenant organization model populated from OIDC claims with
org-scoped user discovery, CalDAV principal filtering, and cross-org
isolation at the SabreDAV layer.
Add bookable resource principals (rooms, equipment) with CalDAV
auto-scheduling that handles conflict detection, auto-accept/decline,
and org-scoped booking enforcement. Fixes #14.
Replace CalendarSubscriptionToken with a unified Channel model
supporting CalDAV integration tokens and iCal feed URLs, with
encrypted token storage and role-based access control. Fixes #16.
Migrate task queue from Celery to Dramatiq with async ICS import,
progress tracking, and task status polling endpoint.
Replace nginx with Caddy for both the reverse proxy and frontend
static serving. Switch frontend package manager from yarn/pnpm to
npm and upgrade Node to 24, Next.js to 16, TypeScript to 5.9.
Harden security with fail-closed entitlements, RSVP rate limiting
and token expiry, CalDAV proxy path validation blocking internal
API routes, channel path scope enforcement, and ETag-based
conflict prevention.
Add frontend pages for resource management and integration channel
CRUD, with resource booking in the event modal.
Restructure CalDAV paths to /calendars/users/ and
/calendars/resources/ with nested principal collections in SabreDAV.
2026-03-09 09:09:34 +01:00
|
|
|
APP_NAME = values.Value("Calendars", environ_name="APP_NAME", environ_prefix=None)
|
2026-01-25 20:33:11 +01:00
|
|
|
APP_URL = values.Value("", environ_name="APP_URL", environ_prefix=None)
|
2026-02-19 18:15:47 +01:00
|
|
|
CALENDAR_ITIP_ENABLED = values.BooleanValue(
|
|
|
|
|
False, environ_name="CALENDAR_ITIP_ENABLED", environ_prefix=None
|
|
|
|
|
)
|
|
|
|
|
TRANSLATIONS_JSON_PATH = values.Value(
|
|
|
|
|
"/data/translations.json",
|
|
|
|
|
environ_name="TRANSLATIONS_JSON_PATH",
|
|
|
|
|
environ_prefix=None,
|
|
|
|
|
)
|
2026-02-19 20:44:49 +01:00
|
|
|
DEFAULT_CALENDAR_COLOR = values.Value(
|
|
|
|
|
"#3788d8",
|
|
|
|
|
environ_name="DEFAULT_CALENDAR_COLOR",
|
|
|
|
|
environ_prefix=None,
|
|
|
|
|
)
|
2026-01-11 03:52:43 +01:00
|
|
|
|
✨(all) add organizations, resources, channels, and infra migration (#34)
Add multi-tenant organization model populated from OIDC claims with
org-scoped user discovery, CalDAV principal filtering, and cross-org
isolation at the SabreDAV layer.
Add bookable resource principals (rooms, equipment) with CalDAV
auto-scheduling that handles conflict detection, auto-accept/decline,
and org-scoped booking enforcement. Fixes #14.
Replace CalendarSubscriptionToken with a unified Channel model
supporting CalDAV integration tokens and iCal feed URLs, with
encrypted token storage and role-based access control. Fixes #16.
Migrate task queue from Celery to Dramatiq with async ICS import,
progress tracking, and task status polling endpoint.
Replace nginx with Caddy for both the reverse proxy and frontend
static serving. Switch frontend package manager from yarn/pnpm to
npm and upgrade Node to 24, Next.js to 16, TypeScript to 5.9.
Harden security with fail-closed entitlements, RSVP rate limiting
and token expiry, CalDAV proxy path validation blocking internal
API routes, channel path scope enforcement, and ETag-based
conflict prevention.
Add frontend pages for resource management and integration channel
CRUD, with resource booking in the event modal.
Restructure CalDAV paths to /calendars/users/ and
/calendars/resources/ with nested principal collections in SabreDAV.
2026-03-09 09:09:34 +01:00
|
|
|
# Organizations
|
|
|
|
|
OIDC_USERINFO_ORGANIZATION_CLAIM = values.Value(
|
|
|
|
|
"",
|
|
|
|
|
environ_name="OIDC_USERINFO_ORGANIZATION_CLAIM",
|
|
|
|
|
environ_prefix=None,
|
|
|
|
|
)
|
|
|
|
|
RESOURCE_EMAIL_DOMAIN = values.Value(
|
|
|
|
|
"",
|
|
|
|
|
environ_name="RESOURCE_EMAIL_DOMAIN",
|
|
|
|
|
environ_prefix=None,
|
|
|
|
|
)
|
|
|
|
|
|
2026-03-06 02:47:03 +01:00
|
|
|
# Entitlements
|
|
|
|
|
ENTITLEMENTS_BACKEND = values.Value(
|
|
|
|
|
"core.entitlements.backends.local.LocalEntitlementsBackend",
|
|
|
|
|
environ_name="ENTITLEMENTS_BACKEND",
|
|
|
|
|
environ_prefix=None,
|
|
|
|
|
)
|
|
|
|
|
ENTITLEMENTS_BACKEND_PARAMETERS = values.DictValue(
|
|
|
|
|
{},
|
|
|
|
|
environ_name="ENTITLEMENTS_BACKEND_PARAMETERS",
|
|
|
|
|
environ_prefix=None,
|
|
|
|
|
)
|
|
|
|
|
ENTITLEMENTS_CACHE_TIMEOUT = values.IntegerValue(
|
|
|
|
|
300,
|
|
|
|
|
environ_name="ENTITLEMENTS_CACHE_TIMEOUT",
|
|
|
|
|
environ_prefix=None,
|
|
|
|
|
)
|
|
|
|
|
|
2026-01-09 00:51:25 +01:00
|
|
|
# Security
|
|
|
|
|
ALLOWED_HOSTS = values.ListValue([])
|
|
|
|
|
SECRET_KEY = SecretFileValue(None)
|
|
|
|
|
SERVER_TO_SERVER_API_TOKENS = values.ListValue([])
|
|
|
|
|
|
|
|
|
|
# Application definition
|
|
|
|
|
ROOT_URLCONF = "calendars.urls"
|
|
|
|
|
WSGI_APPLICATION = "calendars.wsgi.application"
|
|
|
|
|
|
|
|
|
|
# Database
|
|
|
|
|
DATABASES = {
|
|
|
|
|
"default": dj_database_url.config()
|
|
|
|
|
if os.environ.get("DATABASE_URL")
|
|
|
|
|
else {
|
|
|
|
|
"ENGINE": values.Value(
|
|
|
|
|
"django.db.backends.postgresql",
|
|
|
|
|
environ_name="DB_ENGINE",
|
|
|
|
|
environ_prefix=None,
|
|
|
|
|
),
|
|
|
|
|
"NAME": values.Value(
|
|
|
|
|
"calendars", environ_name="DB_NAME", environ_prefix=None
|
|
|
|
|
),
|
|
|
|
|
"USER": values.Value("pgroot", environ_name="DB_USER", environ_prefix=None),
|
|
|
|
|
"PASSWORD": SecretFileValue(
|
|
|
|
|
"pass", environ_name="DB_PASSWORD", environ_prefix=None
|
|
|
|
|
),
|
|
|
|
|
"HOST": values.Value(
|
|
|
|
|
"localhost", environ_name="DB_HOST", environ_prefix=None
|
|
|
|
|
),
|
|
|
|
|
"PORT": values.Value(5432, environ_name="DB_PORT", environ_prefix=None),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
DEFAULT_AUTO_FIELD = "django.db.models.AutoField"
|
|
|
|
|
|
|
|
|
|
# Static files (CSS, JavaScript, Images)
|
|
|
|
|
STATIC_URL = "/static/"
|
|
|
|
|
STATIC_ROOT = os.path.join(DATA_DIR, "static")
|
|
|
|
|
MEDIA_URL = "/media/"
|
|
|
|
|
MEDIA_URL_PREVIEW = "/media/preview/"
|
|
|
|
|
MEDIA_ROOT = os.path.join(DATA_DIR, "media")
|
|
|
|
|
MEDIA_BASE_URL = values.Value(
|
|
|
|
|
None, environ_name="MEDIA_BASE_URL", environ_prefix=None
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
SITE_ID = 1
|
|
|
|
|
|
|
|
|
|
STORAGES = {
|
|
|
|
|
"default": {
|
|
|
|
|
"BACKEND": "django.core.files.storage.FileSystemStorage",
|
|
|
|
|
},
|
|
|
|
|
"staticfiles": {
|
|
|
|
|
"BACKEND": values.Value(
|
|
|
|
|
"whitenoise.storage.CompressedManifestStaticFilesStorage",
|
|
|
|
|
environ_name="STORAGES_STATICFILES_BACKEND",
|
|
|
|
|
),
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
# Maximum size of the request body in memory.
|
|
|
|
|
# This is used to limit the size of the request body in memory.
|
|
|
|
|
# This also limits the size of the file that can be uploaded to the server.
|
|
|
|
|
DATA_UPLOAD_MAX_MEMORY_SIZE = values.PositiveIntegerValue(
|
✨(all) add organizations, resources, channels, and infra migration (#34)
Add multi-tenant organization model populated from OIDC claims with
org-scoped user discovery, CalDAV principal filtering, and cross-org
isolation at the SabreDAV layer.
Add bookable resource principals (rooms, equipment) with CalDAV
auto-scheduling that handles conflict detection, auto-accept/decline,
and org-scoped booking enforcement. Fixes #14.
Replace CalendarSubscriptionToken with a unified Channel model
supporting CalDAV integration tokens and iCal feed URLs, with
encrypted token storage and role-based access control. Fixes #16.
Migrate task queue from Celery to Dramatiq with async ICS import,
progress tracking, and task status polling endpoint.
Replace nginx with Caddy for both the reverse proxy and frontend
static serving. Switch frontend package manager from yarn/pnpm to
npm and upgrade Node to 24, Next.js to 16, TypeScript to 5.9.
Harden security with fail-closed entitlements, RSVP rate limiting
and token expiry, CalDAV proxy path validation blocking internal
API routes, channel path scope enforcement, and ETag-based
conflict prevention.
Add frontend pages for resource management and integration channel
CRUD, with resource booking in the event modal.
Restructure CalDAV paths to /calendars/users/ and
/calendars/resources/ with nested principal collections in SabreDAV.
2026-03-09 09:09:34 +01:00
|
|
|
20 * (2**20), # 20MB
|
2026-01-09 00:51:25 +01:00
|
|
|
environ_name="DATA_UPLOAD_MAX_MEMORY_SIZE",
|
|
|
|
|
environ_prefix=None,
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
# Internationalization
|
|
|
|
|
# https://docs.djangoproject.com/en/3.1/topics/i18n/
|
|
|
|
|
|
|
|
|
|
# Languages
|
|
|
|
|
LANGUAGE_CODE = values.Value("en-us")
|
|
|
|
|
LANGUAGE_COOKIE_NAME = "calendar_language" # cookie & language is set from frontend
|
|
|
|
|
|
|
|
|
|
DRF_NESTED_MULTIPART_PARSER = {
|
|
|
|
|
# output of parser is converted to querydict
|
|
|
|
|
# if is set to False, dict python is returned
|
|
|
|
|
"querydict": False,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
# Careful! Languages should be ordered by priority, as this tuple is used to get
|
|
|
|
|
# fallback/default languages throughout the app.
|
|
|
|
|
LANGUAGES = values.SingleNestedTupleValue(
|
|
|
|
|
(
|
✨(all) add organizations, resources, channels, and infra migration (#34)
Add multi-tenant organization model populated from OIDC claims with
org-scoped user discovery, CalDAV principal filtering, and cross-org
isolation at the SabreDAV layer.
Add bookable resource principals (rooms, equipment) with CalDAV
auto-scheduling that handles conflict detection, auto-accept/decline,
and org-scoped booking enforcement. Fixes #14.
Replace CalendarSubscriptionToken with a unified Channel model
supporting CalDAV integration tokens and iCal feed URLs, with
encrypted token storage and role-based access control. Fixes #16.
Migrate task queue from Celery to Dramatiq with async ICS import,
progress tracking, and task status polling endpoint.
Replace nginx with Caddy for both the reverse proxy and frontend
static serving. Switch frontend package manager from yarn/pnpm to
npm and upgrade Node to 24, Next.js to 16, TypeScript to 5.9.
Harden security with fail-closed entitlements, RSVP rate limiting
and token expiry, CalDAV proxy path validation blocking internal
API routes, channel path scope enforcement, and ETag-based
conflict prevention.
Add frontend pages for resource management and integration channel
CRUD, with resource booking in the event modal.
Restructure CalDAV paths to /calendars/users/ and
/calendars/resources/ with nested principal collections in SabreDAV.
2026-03-09 09:09:34 +01:00
|
|
|
("en-us", "English"),
|
|
|
|
|
("fr-fr", "French"),
|
|
|
|
|
("de-de", "German"),
|
|
|
|
|
("nl-nl", "Dutch"),
|
2026-01-09 00:51:25 +01:00
|
|
|
)
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
TIME_ZONE = "UTC"
|
|
|
|
|
USE_I18N = True
|
|
|
|
|
USE_TZ = True
|
|
|
|
|
|
|
|
|
|
# Templates
|
|
|
|
|
TEMPLATES = [
|
|
|
|
|
{
|
|
|
|
|
"BACKEND": "django.template.backends.django.DjangoTemplates",
|
|
|
|
|
"DIRS": [os.path.join(BASE_DIR, "templates")],
|
|
|
|
|
"OPTIONS": {
|
|
|
|
|
"context_processors": [
|
|
|
|
|
"django.contrib.auth.context_processors.auth",
|
|
|
|
|
"django.contrib.messages.context_processors.messages",
|
|
|
|
|
"django.template.context_processors.csrf",
|
|
|
|
|
"django.template.context_processors.debug",
|
|
|
|
|
"django.template.context_processors.i18n",
|
|
|
|
|
"django.template.context_processors.media",
|
|
|
|
|
"django.template.context_processors.request",
|
|
|
|
|
"django.template.context_processors.tz",
|
|
|
|
|
],
|
|
|
|
|
"loaders": [
|
|
|
|
|
"django.template.loaders.filesystem.Loader",
|
|
|
|
|
"django.template.loaders.app_directories.Loader",
|
|
|
|
|
],
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
MIDDLEWARE = [
|
|
|
|
|
"django.middleware.security.SecurityMiddleware",
|
|
|
|
|
"whitenoise.middleware.WhiteNoiseMiddleware",
|
|
|
|
|
"django.contrib.sessions.middleware.SessionMiddleware",
|
|
|
|
|
"django.middleware.clickjacking.XFrameOptionsMiddleware",
|
|
|
|
|
"corsheaders.middleware.CorsMiddleware",
|
|
|
|
|
"django.middleware.common.CommonMiddleware",
|
|
|
|
|
"django.middleware.csrf.CsrfViewMiddleware",
|
|
|
|
|
"django.contrib.auth.middleware.AuthenticationMiddleware",
|
|
|
|
|
"django.contrib.messages.middleware.MessageMiddleware",
|
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
AUTHENTICATION_BACKENDS = [
|
|
|
|
|
"django.contrib.auth.backends.ModelBackend",
|
|
|
|
|
"core.authentication.backends.OIDCAuthenticationBackend",
|
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
# Django applications from the highest priority to the lowest
|
|
|
|
|
INSTALLED_APPS = [
|
|
|
|
|
"core",
|
|
|
|
|
"drf_spectacular",
|
|
|
|
|
"drf_standardized_errors",
|
|
|
|
|
# Third party apps
|
|
|
|
|
"corsheaders",
|
✨(all) add organizations, resources, channels, and infra migration (#34)
Add multi-tenant organization model populated from OIDC claims with
org-scoped user discovery, CalDAV principal filtering, and cross-org
isolation at the SabreDAV layer.
Add bookable resource principals (rooms, equipment) with CalDAV
auto-scheduling that handles conflict detection, auto-accept/decline,
and org-scoped booking enforcement. Fixes #14.
Replace CalendarSubscriptionToken with a unified Channel model
supporting CalDAV integration tokens and iCal feed URLs, with
encrypted token storage and role-based access control. Fixes #16.
Migrate task queue from Celery to Dramatiq with async ICS import,
progress tracking, and task status polling endpoint.
Replace nginx with Caddy for both the reverse proxy and frontend
static serving. Switch frontend package manager from yarn/pnpm to
npm and upgrade Node to 24, Next.js to 16, TypeScript to 5.9.
Harden security with fail-closed entitlements, RSVP rate limiting
and token expiry, CalDAV proxy path validation blocking internal
API routes, channel path scope enforcement, and ETag-based
conflict prevention.
Add frontend pages for resource management and integration channel
CRUD, with resource booking in the event modal.
Restructure CalDAV paths to /calendars/users/ and
/calendars/resources/ with nested principal collections in SabreDAV.
2026-03-09 09:09:34 +01:00
|
|
|
"django_dramatiq",
|
2026-01-09 00:51:25 +01:00
|
|
|
"django_filters",
|
|
|
|
|
"rest_framework",
|
|
|
|
|
"rest_framework_api_key",
|
|
|
|
|
"parler",
|
|
|
|
|
# Django
|
|
|
|
|
"django.contrib.admin",
|
|
|
|
|
"django.contrib.auth",
|
|
|
|
|
"django.contrib.contenttypes",
|
|
|
|
|
"django.contrib.postgres",
|
|
|
|
|
"django.contrib.sessions",
|
|
|
|
|
"django.contrib.sites",
|
|
|
|
|
"django.contrib.messages",
|
|
|
|
|
"django.contrib.staticfiles",
|
|
|
|
|
# OIDC third party
|
|
|
|
|
"mozilla_django_oidc",
|
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
# Cache
|
|
|
|
|
CACHES = {
|
|
|
|
|
"default": {
|
|
|
|
|
"BACKEND": "django_redis.cache.RedisCache",
|
|
|
|
|
"LOCATION": values.Value(
|
|
|
|
|
"redis://redis:6379/0",
|
|
|
|
|
environ_name="REDIS_URL",
|
|
|
|
|
environ_prefix=None,
|
|
|
|
|
),
|
|
|
|
|
"TIMEOUT": values.IntegerValue(
|
|
|
|
|
30, # timeout in seconds
|
|
|
|
|
environ_name="CACHES_DEFAULT_TIMEOUT",
|
|
|
|
|
environ_prefix=None,
|
|
|
|
|
),
|
|
|
|
|
"OPTIONS": {
|
|
|
|
|
"CLIENT_CLASS": "django_redis.client.DefaultClient",
|
|
|
|
|
},
|
|
|
|
|
"KEY_PREFIX": values.Value(
|
|
|
|
|
"calendar",
|
|
|
|
|
environ_name="CACHES_DEFAULT_KEY_PREFIX",
|
|
|
|
|
environ_prefix=None,
|
|
|
|
|
),
|
|
|
|
|
},
|
|
|
|
|
"session": {
|
|
|
|
|
"BACKEND": "django_redis.cache.RedisCache",
|
|
|
|
|
"LOCATION": values.Value(
|
|
|
|
|
"redis://redis:6379/0",
|
|
|
|
|
environ_name="REDIS_URL",
|
|
|
|
|
environ_prefix=None,
|
|
|
|
|
),
|
|
|
|
|
"TIMEOUT": values.IntegerValue(
|
|
|
|
|
30, # timeout in seconds
|
|
|
|
|
environ_name="CACHES_SESSION_TIMEOUT",
|
|
|
|
|
environ_prefix=None,
|
|
|
|
|
),
|
|
|
|
|
"OPTIONS": {
|
|
|
|
|
"CLIENT_CLASS": "django_redis.client.DefaultClient",
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
REST_FRAMEWORK = {
|
|
|
|
|
"DEFAULT_AUTHENTICATION_CLASSES": (
|
|
|
|
|
"mozilla_django_oidc.contrib.drf.OIDCAuthentication",
|
|
|
|
|
"rest_framework.authentication.SessionAuthentication",
|
|
|
|
|
),
|
|
|
|
|
"DEFAULT_PARSER_CLASSES": [
|
|
|
|
|
"rest_framework.parsers.JSONParser",
|
|
|
|
|
"nested_multipart_parser.drf.DrfNestedParser",
|
|
|
|
|
],
|
|
|
|
|
"DEFAULT_RENDERER_CLASSES": [
|
|
|
|
|
# 🔒️ Disable BrowsableAPIRenderer which provides forms allowing a user to
|
|
|
|
|
# see all the data in the database (ie a serializer with a ForeignKey field
|
|
|
|
|
# will generate a form with a field with all possible values of the FK).
|
|
|
|
|
"rest_framework.renderers.JSONRenderer",
|
|
|
|
|
],
|
|
|
|
|
"EXCEPTION_HANDLER": "core.api.exception_handler",
|
|
|
|
|
"DEFAULT_PAGINATION_CLASS": "rest_framework.pagination.PageNumberPagination",
|
|
|
|
|
"PAGE_SIZE": 20,
|
|
|
|
|
"DEFAULT_VERSIONING_CLASS": "rest_framework.versioning.URLPathVersioning",
|
|
|
|
|
"DEFAULT_SCHEMA_CLASS": "drf_spectacular.openapi.AutoSchema",
|
|
|
|
|
"DEFAULT_THROTTLE_CLASSES": ["rest_framework.throttling.ScopedRateThrottle"],
|
|
|
|
|
"DEFAULT_THROTTLE_RATES": {
|
|
|
|
|
"user_list_sustained": values.Value(
|
|
|
|
|
default="180/hour",
|
|
|
|
|
environ_name="API_USERS_LIST_THROTTLE_RATE_SUSTAINED",
|
|
|
|
|
environ_prefix=None,
|
|
|
|
|
),
|
|
|
|
|
"user_list_burst": values.Value(
|
|
|
|
|
default="30/minute",
|
|
|
|
|
environ_name="API_USERS_LIST_THROTTLE_RATE_BURST",
|
|
|
|
|
environ_prefix=None,
|
|
|
|
|
),
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
MAX_PAGE_SIZE = values.PositiveIntegerValue(
|
|
|
|
|
200, environ_name="MAX_PAGE_SIZE", environ_prefix=None
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
SPECTACULAR_SETTINGS = {
|
|
|
|
|
"TITLE": "calendar API",
|
|
|
|
|
"DESCRIPTION": "This is the calendar API schema.",
|
|
|
|
|
"VERSION": "1.0.0",
|
|
|
|
|
"SERVE_INCLUDE_SCHEMA": False,
|
|
|
|
|
"ENABLE_DJANGO_DEPLOY_CHECK": values.BooleanValue(
|
|
|
|
|
default=False,
|
|
|
|
|
environ_name="SPECTACULAR_SETTINGS_ENABLE_DJANGO_DEPLOY_CHECK",
|
|
|
|
|
),
|
|
|
|
|
"COMPONENT_SPLIT_REQUEST": True,
|
|
|
|
|
# OTHER SETTINGS
|
|
|
|
|
"SWAGGER_UI_DIST": "SIDECAR", # shorthand to use the sidecar instead
|
|
|
|
|
"SWAGGER_UI_FAVICON_HREF": "SIDECAR",
|
|
|
|
|
"REDOC_DIST": "SIDECAR",
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
TRASHBIN_CUTOFF_DAYS = values.Value(
|
|
|
|
|
30, environ_name="TRASHBIN_CUTOFF_DAYS", environ_prefix=None
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
AUTH_USER_MODEL = "core.User"
|
✨(all) add organizations, resources, channels, and infra migration (#34)
Add multi-tenant organization model populated from OIDC claims with
org-scoped user discovery, CalDAV principal filtering, and cross-org
isolation at the SabreDAV layer.
Add bookable resource principals (rooms, equipment) with CalDAV
auto-scheduling that handles conflict detection, auto-accept/decline,
and org-scoped booking enforcement. Fixes #14.
Replace CalendarSubscriptionToken with a unified Channel model
supporting CalDAV integration tokens and iCal feed URLs, with
encrypted token storage and role-based access control. Fixes #16.
Migrate task queue from Celery to Dramatiq with async ICS import,
progress tracking, and task status polling endpoint.
Replace nginx with Caddy for both the reverse proxy and frontend
static serving. Switch frontend package manager from yarn/pnpm to
npm and upgrade Node to 24, Next.js to 16, TypeScript to 5.9.
Harden security with fail-closed entitlements, RSVP rate limiting
and token expiry, CalDAV proxy path validation blocking internal
API routes, channel path scope enforcement, and ETag-based
conflict prevention.
Add frontend pages for resource management and integration channel
CRUD, with resource booking in the event modal.
Restructure CalDAV paths to /calendars/users/ and
/calendars/resources/ with nested principal collections in SabreDAV.
2026-03-09 09:09:34 +01:00
|
|
|
RSVP_TOKEN_MAX_AGE_RECURRING = values.PositiveIntegerValue(
|
|
|
|
|
7776000, # 90 days
|
|
|
|
|
environ_name="RSVP_TOKEN_MAX_AGE_RECURRING",
|
|
|
|
|
environ_prefix=None,
|
|
|
|
|
)
|
2026-01-09 00:51:25 +01:00
|
|
|
|
|
|
|
|
# CORS
|
|
|
|
|
CORS_ALLOW_CREDENTIALS = True
|
|
|
|
|
CORS_ALLOW_ALL_ORIGINS = values.BooleanValue(False)
|
|
|
|
|
CORS_ALLOWED_ORIGINS = values.ListValue([])
|
|
|
|
|
CORS_ALLOWED_ORIGIN_REGEXES = values.ListValue([])
|
2026-01-25 20:33:11 +01:00
|
|
|
# Allow CalDAV methods (PROPFIND, PROPPATCH, REPORT, etc.)
|
2026-01-09 00:51:25 +01:00
|
|
|
CORS_ALLOW_METHODS = [
|
|
|
|
|
"DELETE",
|
|
|
|
|
"GET",
|
|
|
|
|
"OPTIONS",
|
|
|
|
|
"PATCH",
|
|
|
|
|
"POST",
|
|
|
|
|
"PUT",
|
|
|
|
|
"PROPFIND",
|
2026-01-25 20:33:11 +01:00
|
|
|
"PROPPATCH",
|
2026-01-09 00:51:25 +01:00
|
|
|
"REPORT",
|
|
|
|
|
"MKCOL",
|
|
|
|
|
"MKCALENDAR",
|
|
|
|
|
]
|
|
|
|
|
# Allow CalDAV headers (case-sensitive for CORS preflight)
|
|
|
|
|
CORS_ALLOW_HEADERS = [
|
|
|
|
|
"accept",
|
|
|
|
|
"accept-encoding",
|
|
|
|
|
"authorization",
|
|
|
|
|
"content-type",
|
|
|
|
|
"dnt",
|
|
|
|
|
"origin",
|
|
|
|
|
"user-agent",
|
|
|
|
|
"x-csrftoken",
|
|
|
|
|
"x-requested-with",
|
|
|
|
|
"depth", # CalDAV header (lowercase as sent by browsers)
|
|
|
|
|
"if-match",
|
|
|
|
|
"if-none-match",
|
|
|
|
|
"prefer",
|
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
# Sentry
|
|
|
|
|
SENTRY_DSN = values.Value(None, environ_name="SENTRY_DSN", environ_prefix=None)
|
|
|
|
|
|
|
|
|
|
# Frontend
|
|
|
|
|
FRONTEND_THEME = values.Value(
|
|
|
|
|
None, environ_name="FRONTEND_THEME", environ_prefix=None
|
|
|
|
|
)
|
|
|
|
|
FRONTEND_MORE_LINK = values.Value(
|
|
|
|
|
None,
|
|
|
|
|
environ_name="FRONTEND_MORE_LINK",
|
|
|
|
|
environ_prefix=None,
|
|
|
|
|
)
|
|
|
|
|
FRONTEND_FEEDBACK_BUTTON_SHOW = values.BooleanValue(
|
|
|
|
|
default=False, environ_name="FRONTEND_FEEDBACK_BUTTON_SHOW", environ_prefix=None
|
|
|
|
|
)
|
2026-02-21 00:49:44 +01:00
|
|
|
# Bind this button to an external library to trigger survey
|
|
|
|
|
# instead of the built-in feedback modal.
|
2026-01-09 00:51:25 +01:00
|
|
|
FRONTEND_FEEDBACK_BUTTON_IDLE = values.BooleanValue(
|
|
|
|
|
default=False, environ_name="FRONTEND_FEEDBACK_BUTTON_IDLE", environ_prefix=None
|
|
|
|
|
)
|
|
|
|
|
FRONTEND_FEEDBACK_ITEMS = values.DictValue(
|
|
|
|
|
{}, environ_name="FRONTEND_FEEDBACK_ITEMS", environ_prefix=None
|
|
|
|
|
)
|
|
|
|
|
FRONTEND_FEEDBACK_MESSAGES_WIDGET_ENABLED = values.BooleanValue(
|
|
|
|
|
default=False,
|
|
|
|
|
environ_name="FRONTEND_FEEDBACK_MESSAGES_WIDGET_ENABLED",
|
|
|
|
|
environ_prefix=None,
|
|
|
|
|
)
|
|
|
|
|
FRONTEND_FEEDBACK_MESSAGES_WIDGET_API_URL = values.Value(
|
|
|
|
|
None,
|
|
|
|
|
environ_name="FRONTEND_FEEDBACK_MESSAGES_WIDGET_API_URL",
|
|
|
|
|
environ_prefix=None,
|
|
|
|
|
)
|
|
|
|
|
FRONTEND_FEEDBACK_MESSAGES_WIDGET_CHANNEL = values.Value(
|
|
|
|
|
None,
|
|
|
|
|
environ_name="FRONTEND_FEEDBACK_MESSAGES_WIDGET_CHANNEL",
|
|
|
|
|
environ_prefix=None,
|
|
|
|
|
)
|
|
|
|
|
FRONTEND_FEEDBACK_MESSAGES_WIDGET_PATH = values.Value(
|
|
|
|
|
None, environ_name="FRONTEND_FEEDBACK_MESSAGES_WIDGET_PATH", environ_prefix=None
|
|
|
|
|
)
|
|
|
|
|
FRONTEND_HIDE_GAUFRE = values.BooleanValue(
|
|
|
|
|
default=False, environ_name="FRONTEND_HIDE_GAUFRE", environ_prefix=None
|
|
|
|
|
)
|
|
|
|
|
THEME_CUSTOMIZATION_FILE_PATH = values.Value(
|
|
|
|
|
os.path.join(BASE_DIR, "calendars/configuration/theme/default.json"),
|
|
|
|
|
environ_name="THEME_CUSTOMIZATION_FILE_PATH",
|
|
|
|
|
environ_prefix=None,
|
|
|
|
|
)
|
|
|
|
|
THEME_CUSTOMIZATION_CACHE_TIMEOUT = values.Value(
|
|
|
|
|
60 * 60 * 24,
|
|
|
|
|
environ_name="THEME_CUSTOMIZATION_CACHE_TIMEOUT",
|
|
|
|
|
environ_prefix=None,
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
# Easy thumbnails
|
|
|
|
|
THUMBNAIL_EXTENSION = "webp"
|
|
|
|
|
THUMBNAIL_TRANSPARENCY_EXTENSION = "webp"
|
|
|
|
|
THUMBNAIL_DEFAULT_STORAGE_ALIAS = "default"
|
|
|
|
|
THUMBNAIL_ALIASES = {}
|
|
|
|
|
|
✨(all) add organizations, resources, channels, and infra migration (#34)
Add multi-tenant organization model populated from OIDC claims with
org-scoped user discovery, CalDAV principal filtering, and cross-org
isolation at the SabreDAV layer.
Add bookable resource principals (rooms, equipment) with CalDAV
auto-scheduling that handles conflict detection, auto-accept/decline,
and org-scoped booking enforcement. Fixes #14.
Replace CalendarSubscriptionToken with a unified Channel model
supporting CalDAV integration tokens and iCal feed URLs, with
encrypted token storage and role-based access control. Fixes #16.
Migrate task queue from Celery to Dramatiq with async ICS import,
progress tracking, and task status polling endpoint.
Replace nginx with Caddy for both the reverse proxy and frontend
static serving. Switch frontend package manager from yarn/pnpm to
npm and upgrade Node to 24, Next.js to 16, TypeScript to 5.9.
Harden security with fail-closed entitlements, RSVP rate limiting
and token expiry, CalDAV proxy path validation blocking internal
API routes, channel path scope enforcement, and ETag-based
conflict prevention.
Add frontend pages for resource management and integration channel
CRUD, with resource booking in the event modal.
Restructure CalDAV paths to /calendars/users/ and
/calendars/resources/ with nested principal collections in SabreDAV.
2026-03-09 09:09:34 +01:00
|
|
|
# Dramatiq
|
|
|
|
|
DRAMATIQ_BROKER = {
|
|
|
|
|
"BROKER": "dramatiq.brokers.redis.RedisBroker",
|
|
|
|
|
"OPTIONS": {
|
|
|
|
|
"url": values.Value(
|
|
|
|
|
"redis://redis:6379/0",
|
|
|
|
|
environ_name="DRAMATIQ_BROKER_URL",
|
|
|
|
|
environ_prefix=None,
|
|
|
|
|
),
|
|
|
|
|
},
|
|
|
|
|
"MIDDLEWARE": [
|
|
|
|
|
"dramatiq.middleware.AgeLimit",
|
|
|
|
|
"dramatiq.middleware.TimeLimit",
|
|
|
|
|
"dramatiq.middleware.Callbacks",
|
|
|
|
|
"dramatiq.middleware.Retries",
|
|
|
|
|
"dramatiq.middleware.CurrentMessage",
|
|
|
|
|
"django_dramatiq.middleware.DbConnectionsMiddleware",
|
|
|
|
|
"django_dramatiq.middleware.AdminMiddleware",
|
|
|
|
|
],
|
|
|
|
|
}
|
|
|
|
|
DRAMATIQ_RESULT_BACKEND = {
|
|
|
|
|
"BACKEND": "dramatiq.results.backends.redis.RedisBackend",
|
|
|
|
|
"BACKEND_OPTIONS": {
|
|
|
|
|
"url": values.Value(
|
|
|
|
|
"redis://redis:6379/1",
|
|
|
|
|
environ_name="DRAMATIQ_RESULT_BACKEND_URL",
|
|
|
|
|
environ_prefix=None,
|
|
|
|
|
),
|
|
|
|
|
},
|
|
|
|
|
"MIDDLEWARE_OPTIONS": {
|
|
|
|
|
"result_ttl": 1000 * 60 * 60 * 24 * 30, # 30 days
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
DRAMATIQ_AUTODISCOVER_MODULES = ["tasks"]
|
2026-01-09 00:51:25 +01:00
|
|
|
|
|
|
|
|
# Session
|
|
|
|
|
SESSION_ENGINE = "django.contrib.sessions.backends.cache"
|
|
|
|
|
SESSION_CACHE_ALIAS = "session"
|
|
|
|
|
SESSION_COOKIE_AGE = 60 * 60 * 12
|
|
|
|
|
|
|
|
|
|
# OIDC - Authorization Code Flow
|
|
|
|
|
OIDC_CREATE_USER = values.BooleanValue(
|
|
|
|
|
default=True,
|
|
|
|
|
environ_name="OIDC_CREATE_USER",
|
|
|
|
|
)
|
|
|
|
|
OIDC_CALLBACK_CLASS = "core.authentication.views.OIDCAuthenticationCallbackView"
|
|
|
|
|
OIDC_RP_SIGN_ALGO = values.Value(
|
|
|
|
|
"RS256", environ_name="OIDC_RP_SIGN_ALGO", environ_prefix=None
|
|
|
|
|
)
|
|
|
|
|
OIDC_RP_CLIENT_ID = values.Value(
|
|
|
|
|
"calendar", environ_name="OIDC_RP_CLIENT_ID", environ_prefix=None
|
|
|
|
|
)
|
|
|
|
|
OIDC_RP_CLIENT_SECRET = SecretFileValue(
|
|
|
|
|
None,
|
|
|
|
|
environ_name="OIDC_RP_CLIENT_SECRET",
|
|
|
|
|
environ_prefix=None,
|
|
|
|
|
)
|
|
|
|
|
OIDC_OP_JWKS_ENDPOINT = values.Value(
|
|
|
|
|
environ_name="OIDC_OP_JWKS_ENDPOINT", environ_prefix=None
|
|
|
|
|
)
|
|
|
|
|
OIDC_OP_AUTHORIZATION_ENDPOINT = values.Value(
|
|
|
|
|
environ_name="OIDC_OP_AUTHORIZATION_ENDPOINT", environ_prefix=None
|
|
|
|
|
)
|
|
|
|
|
OIDC_OP_TOKEN_ENDPOINT = values.Value(
|
|
|
|
|
None, environ_name="OIDC_OP_TOKEN_ENDPOINT", environ_prefix=None
|
|
|
|
|
)
|
|
|
|
|
OIDC_OP_USER_ENDPOINT = values.Value(
|
|
|
|
|
None, environ_name="OIDC_OP_USER_ENDPOINT", environ_prefix=None
|
|
|
|
|
)
|
|
|
|
|
OIDC_OP_LOGOUT_ENDPOINT = values.Value(
|
|
|
|
|
None, environ_name="OIDC_OP_LOGOUT_ENDPOINT", environ_prefix=None
|
|
|
|
|
)
|
|
|
|
|
OIDC_REDIRECT_FIELD_NAME = values.Value(
|
|
|
|
|
"returnTo", environ_name="OIDC_REDIRECT_FIELD_NAME", environ_prefix=None
|
|
|
|
|
)
|
|
|
|
|
OIDC_AUTH_REQUEST_EXTRA_PARAMS = values.DictValue(
|
|
|
|
|
{}, environ_name="OIDC_AUTH_REQUEST_EXTRA_PARAMS", environ_prefix=None
|
|
|
|
|
)
|
|
|
|
|
OIDC_RP_SCOPES = values.Value(
|
|
|
|
|
"openid email", environ_name="OIDC_RP_SCOPES", environ_prefix=None
|
|
|
|
|
)
|
|
|
|
|
LOGIN_REDIRECT_URL = values.Value(
|
|
|
|
|
None, environ_name="LOGIN_REDIRECT_URL", environ_prefix=None
|
|
|
|
|
)
|
|
|
|
|
LOGIN_REDIRECT_URL_FAILURE = values.Value(
|
|
|
|
|
None, environ_name="LOGIN_REDIRECT_URL_FAILURE", environ_prefix=None
|
|
|
|
|
)
|
|
|
|
|
LOGOUT_REDIRECT_URL = values.Value(
|
|
|
|
|
None, environ_name="LOGOUT_REDIRECT_URL", environ_prefix=None
|
|
|
|
|
)
|
|
|
|
|
OIDC_USE_NONCE = values.BooleanValue(
|
|
|
|
|
default=True, environ_name="OIDC_USE_NONCE", environ_prefix=None
|
|
|
|
|
)
|
|
|
|
|
OIDC_REDIRECT_REQUIRE_HTTPS = values.BooleanValue(
|
|
|
|
|
default=False, environ_name="OIDC_REDIRECT_REQUIRE_HTTPS", environ_prefix=None
|
|
|
|
|
)
|
|
|
|
|
OIDC_REDIRECT_ALLOWED_HOSTS = values.ListValue(
|
|
|
|
|
default=[], environ_name="OIDC_REDIRECT_ALLOWED_HOSTS", environ_prefix=None
|
|
|
|
|
)
|
|
|
|
|
OIDC_STORE_ID_TOKEN = values.BooleanValue(
|
|
|
|
|
default=True, environ_name="OIDC_STORE_ID_TOKEN", environ_prefix=None
|
|
|
|
|
)
|
|
|
|
|
OIDC_FALLBACK_TO_EMAIL_FOR_IDENTIFICATION = values.BooleanValue(
|
|
|
|
|
default=True,
|
|
|
|
|
environ_name="OIDC_FALLBACK_TO_EMAIL_FOR_IDENTIFICATION",
|
|
|
|
|
environ_prefix=None,
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
OIDC_STORE_ACCESS_TOKEN = values.BooleanValue(
|
|
|
|
|
default=False, environ_name="OIDC_STORE_ACCESS_TOKEN", environ_prefix=None
|
|
|
|
|
)
|
|
|
|
|
OIDC_STORE_REFRESH_TOKEN = values.BooleanValue(
|
|
|
|
|
default=False, environ_name="OIDC_STORE_REFRESH_TOKEN", environ_prefix=None
|
|
|
|
|
)
|
|
|
|
|
OIDC_STORE_REFRESH_TOKEN_KEY = values.Value(
|
|
|
|
|
default=None,
|
|
|
|
|
environ_name="OIDC_STORE_REFRESH_TOKEN_KEY",
|
|
|
|
|
environ_prefix=None,
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
# OIDC claims to store
|
|
|
|
|
OIDC_STORE_CLAIMS = values.ListValue(
|
|
|
|
|
default=[],
|
|
|
|
|
environ_name="OIDC_STORE_CLAIMS",
|
|
|
|
|
environ_prefix=None,
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
# WARNING: Enabling this setting allows multiple user accounts to share the same email
|
|
|
|
|
# address. This may cause security issues and is not recommended for production use when
|
|
|
|
|
# email is activated as fallback for identification (see previous setting).
|
|
|
|
|
OIDC_ALLOW_DUPLICATE_EMAILS = values.BooleanValue(
|
|
|
|
|
default=False,
|
|
|
|
|
environ_name="OIDC_ALLOW_DUPLICATE_EMAILS",
|
|
|
|
|
environ_prefix=None,
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
OIDC_USER_INFO = values.ListValue(
|
|
|
|
|
default=values.ListValue( # retrocompatibility
|
|
|
|
|
default=[],
|
|
|
|
|
environ_name="USER_OIDC_ESSENTIAL_CLAIMS",
|
|
|
|
|
environ_prefix=None,
|
|
|
|
|
),
|
|
|
|
|
environ_name="OIDC_USER_INFO",
|
|
|
|
|
environ_prefix=None,
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
OIDC_USERINFO_FULLNAME_FIELDS = values.ListValue(
|
|
|
|
|
default=["first_name", "last_name"],
|
|
|
|
|
environ_name="OIDC_USERINFO_FULLNAME_FIELDS",
|
|
|
|
|
environ_prefix=None,
|
|
|
|
|
)
|
|
|
|
|
# OIDC Resource Server
|
|
|
|
|
|
|
|
|
|
OIDC_RESOURCE_SERVER_ENABLED = values.BooleanValue(
|
|
|
|
|
default=False, environ_name="OIDC_RESOURCE_SERVER_ENABLED", environ_prefix=None
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
OIDC_RS_BACKEND_CLASS = values.Value(
|
|
|
|
|
"lasuite.oidc_resource_server.backend.ResourceServerBackend",
|
|
|
|
|
environ_name="OIDC_RS_BACKEND_CLASS",
|
|
|
|
|
environ_prefix=None,
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
OIDC_OP_URL = values.Value(None, environ_name="OIDC_OP_URL", environ_prefix=None)
|
|
|
|
|
|
|
|
|
|
OIDC_VERIFY_SSL = values.BooleanValue(
|
|
|
|
|
default=True, environ_name="OIDC_VERIFY_SSL", environ_prefix=None
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
OIDC_TIMEOUT = values.PositiveIntegerValue(
|
|
|
|
|
3, environ_name="OIDC_TIMEOUT", environ_prefix=None
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
OIDC_PROXY = values.Value(None, environ_name="OIDC_PROXY", environ_prefix=None)
|
|
|
|
|
|
|
|
|
|
OIDC_OP_INTROSPECTION_ENDPOINT = values.Value(
|
|
|
|
|
None, environ_name="OIDC_OP_INTROSPECTION_ENDPOINT", environ_prefix=None
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
OIDC_RS_CLIENT_ID = values.Value(
|
|
|
|
|
None, environ_name="OIDC_RS_CLIENT_ID", environ_prefix=None
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
OIDC_RS_CLIENT_SECRET = values.Value(
|
|
|
|
|
None, environ_name="OIDC_RS_CLIENT_SECRET", environ_prefix=None
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
OIDC_RS_AUDIENCE_CLAIM = values.Value(
|
|
|
|
|
"client_id", environ_name="OIDC_RS_AUDIENCE_CLAIM", environ_prefix=None
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
OIDC_RS_ENCRYPTION_ENCODING = values.Value(
|
|
|
|
|
"A256GCM", environ_name="OIDC_RS_ENCRYPTION_ENCODING", environ_prefix=None
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
OIDC_RS_ENCRYPTION_ALGO = values.Value(
|
|
|
|
|
"RSA-OAEP", environ_name="OIDC_RS_ENCRYPTION_ALGO", environ_prefix=None
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
OIDC_RS_SIGNING_ALGO = values.Value(
|
|
|
|
|
"ES256", environ_name="OIDC_RS_SIGNING_ALGO", environ_prefix=None
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
OIDC_RS_SCOPES = values.ListValue(
|
|
|
|
|
["openid"], environ_name="OIDC_RS_SCOPES", environ_prefix=None
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
OIDC_RS_ALLOWED_AUDIENCES = values.ListValue(
|
|
|
|
|
default=[],
|
|
|
|
|
environ_name="OIDC_RS_ALLOWED_AUDIENCES",
|
|
|
|
|
environ_prefix=None,
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
# External API Configuration
|
|
|
|
|
# Configure available routes and actions for external_api endpoints
|
|
|
|
|
EXTERNAL_API = values.DictValue(
|
|
|
|
|
default={
|
|
|
|
|
"users": {
|
|
|
|
|
"enabled": True,
|
|
|
|
|
"actions": ["get_me"],
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
environ_name="EXTERNAL_API",
|
|
|
|
|
environ_prefix=None,
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
OIDC_RS_PRIVATE_KEY_STR = values.Value(
|
|
|
|
|
default=None,
|
|
|
|
|
environ_name="OIDC_RS_PRIVATE_KEY_STR",
|
|
|
|
|
environ_prefix=None,
|
|
|
|
|
)
|
|
|
|
|
OIDC_RS_ENCRYPTION_KEY_TYPE = values.Value(
|
|
|
|
|
default="RSA",
|
|
|
|
|
environ_name="OIDC_RS_ENCRYPTION_KEY_TYPE",
|
|
|
|
|
environ_prefix=None,
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
ALLOW_LOGOUT_GET_METHOD = values.BooleanValue(
|
|
|
|
|
default=True, environ_name="ALLOW_LOGOUT_GET_METHOD", environ_prefix=None
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
# Logging
|
|
|
|
|
# We want to make it easy to log to console but by default we log production
|
|
|
|
|
# to Sentry and don't want to log to console.
|
|
|
|
|
LOGGING = {
|
|
|
|
|
"version": 1,
|
|
|
|
|
"disable_existing_loggers": False,
|
|
|
|
|
"formatters": {
|
|
|
|
|
"simple": {
|
|
|
|
|
"format": "{asctime} {name} {levelname} {message}",
|
|
|
|
|
"style": "{",
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
"handlers": {
|
|
|
|
|
"console": {
|
|
|
|
|
"class": "logging.StreamHandler",
|
|
|
|
|
"formatter": "simple",
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
# Override root logger to send it to console
|
|
|
|
|
"root": {
|
|
|
|
|
"handlers": ["console"],
|
|
|
|
|
"level": values.Value(
|
|
|
|
|
"INFO", environ_name="LOGGING_LEVEL_LOGGERS_ROOT", environ_prefix=None
|
|
|
|
|
),
|
|
|
|
|
},
|
|
|
|
|
"loggers": {
|
|
|
|
|
"core": {
|
|
|
|
|
"handlers": ["console"],
|
|
|
|
|
"level": values.Value(
|
|
|
|
|
"INFO",
|
|
|
|
|
environ_name="LOGGING_LEVEL_LOGGERS_APP",
|
|
|
|
|
environ_prefix=None,
|
|
|
|
|
),
|
|
|
|
|
"propagate": True,
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
API_USERS_LIST_LIMIT = values.PositiveIntegerValue(
|
|
|
|
|
default=5,
|
|
|
|
|
environ_name="API_USERS_LIST_LIMIT",
|
|
|
|
|
environ_prefix=None,
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
# Storage compute
|
|
|
|
|
|
|
|
|
|
# pylint: disable=invalid-name
|
|
|
|
|
@property
|
|
|
|
|
def ENVIRONMENT(self):
|
|
|
|
|
"""Environment in which the application is launched."""
|
|
|
|
|
return self.__class__.__name__.lower()
|
|
|
|
|
|
|
|
|
|
# pylint: disable=invalid-name
|
|
|
|
|
@property
|
|
|
|
|
def RELEASE(self):
|
|
|
|
|
"""
|
|
|
|
|
Return the release information.
|
|
|
|
|
|
|
|
|
|
Delegate to the module function to enable easier testing.
|
|
|
|
|
"""
|
|
|
|
|
return get_release()
|
|
|
|
|
|
|
|
|
|
# pylint: disable=invalid-name
|
|
|
|
|
@property
|
|
|
|
|
def PARLER_LANGUAGES(self):
|
|
|
|
|
"""
|
|
|
|
|
Return languages for Parler computed from the LANGUAGES and LANGUAGE_CODE settings.
|
|
|
|
|
"""
|
|
|
|
|
return {
|
|
|
|
|
self.SITE_ID: tuple({"code": code} for code, _name in self.LANGUAGES),
|
|
|
|
|
"default": {
|
|
|
|
|
"fallbacks": [self.LANGUAGE_CODE],
|
|
|
|
|
"hide_untranslated": False,
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
|
def post_setup(cls):
|
|
|
|
|
"""Post setup configuration.
|
|
|
|
|
This is the place where you can configure settings that require other
|
|
|
|
|
settings to be loaded.
|
|
|
|
|
"""
|
|
|
|
|
super().post_setup()
|
|
|
|
|
|
|
|
|
|
# The SENTRY_DSN setting should be available to activate sentry for an environment
|
|
|
|
|
if cls.SENTRY_DSN is not None:
|
|
|
|
|
sentry_sdk.init(
|
|
|
|
|
dsn=cls.SENTRY_DSN,
|
|
|
|
|
environment=cls.__name__.lower(),
|
|
|
|
|
release=get_release(),
|
|
|
|
|
integrations=[DjangoIntegration()],
|
|
|
|
|
)
|
|
|
|
|
sentry_sdk.set_tag("application", "backend")
|
|
|
|
|
|
|
|
|
|
if (
|
|
|
|
|
cls.OIDC_FALLBACK_TO_EMAIL_FOR_IDENTIFICATION
|
|
|
|
|
and cls.OIDC_ALLOW_DUPLICATE_EMAILS
|
|
|
|
|
):
|
|
|
|
|
raise ValueError(
|
|
|
|
|
"Both OIDC_FALLBACK_TO_EMAIL_FOR_IDENTIFICATION and "
|
|
|
|
|
"OIDC_ALLOW_DUPLICATE_EMAILS cannot be set to True simultaneously. "
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class Build(Base):
|
|
|
|
|
"""Settings used when the application is built.
|
|
|
|
|
|
|
|
|
|
This environment should not be used to run the application. Just to build it with non-blocking
|
|
|
|
|
settings.
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
SESSION_CACHE_ALIAS = "default"
|
|
|
|
|
CACHES = {
|
|
|
|
|
"default": {"BACKEND": "django.core.cache.backends.locmem.LocMemCache"},
|
|
|
|
|
}
|
|
|
|
|
SECRET_KEY = values.Value("DummyKey")
|
|
|
|
|
STORAGES = {
|
|
|
|
|
"default": {
|
|
|
|
|
"BACKEND": "django.core.files.storage.FileSystemStorage",
|
|
|
|
|
},
|
|
|
|
|
"staticfiles": {
|
|
|
|
|
"BACKEND": values.Value(
|
|
|
|
|
"whitenoise.storage.CompressedManifestStaticFilesStorage",
|
|
|
|
|
environ_name="STORAGES_STATICFILES_BACKEND",
|
|
|
|
|
),
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class Development(Base):
|
|
|
|
|
"""
|
|
|
|
|
Development environment settings
|
|
|
|
|
|
|
|
|
|
We set DEBUG to True and configure the server to respond from all hosts.
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
ALLOWED_HOSTS = ["*"]
|
|
|
|
|
CORS_ALLOW_ALL_ORIGINS = True
|
|
|
|
|
CSRF_TRUSTED_ORIGINS = [
|
✨(all) add organizations, resources, channels, and infra migration (#34)
Add multi-tenant organization model populated from OIDC claims with
org-scoped user discovery, CalDAV principal filtering, and cross-org
isolation at the SabreDAV layer.
Add bookable resource principals (rooms, equipment) with CalDAV
auto-scheduling that handles conflict detection, auto-accept/decline,
and org-scoped booking enforcement. Fixes #14.
Replace CalendarSubscriptionToken with a unified Channel model
supporting CalDAV integration tokens and iCal feed URLs, with
encrypted token storage and role-based access control. Fixes #16.
Migrate task queue from Celery to Dramatiq with async ICS import,
progress tracking, and task status polling endpoint.
Replace nginx with Caddy for both the reverse proxy and frontend
static serving. Switch frontend package manager from yarn/pnpm to
npm and upgrade Node to 24, Next.js to 16, TypeScript to 5.9.
Harden security with fail-closed entitlements, RSVP rate limiting
and token expiry, CalDAV proxy path validation blocking internal
API routes, channel path scope enforcement, and ETag-based
conflict prevention.
Add frontend pages for resource management and integration channel
CRUD, with resource booking in the event modal.
Restructure CalDAV paths to /calendars/users/ and
/calendars/resources/ with nested principal collections in SabreDAV.
2026-03-09 09:09:34 +01:00
|
|
|
"http://localhost:8930",
|
2026-01-25 20:33:11 +01:00
|
|
|
"http://localhost:3000",
|
2026-01-09 00:51:25 +01:00
|
|
|
]
|
|
|
|
|
DEBUG = True
|
|
|
|
|
LOAD_E2E_URLS = True
|
|
|
|
|
|
|
|
|
|
SESSION_COOKIE_NAME = "calendar_sessionid"
|
|
|
|
|
|
|
|
|
|
USE_SWAGGER = True
|
|
|
|
|
|
2026-01-25 20:33:11 +01:00
|
|
|
# Email settings for development (mailcatcher)
|
|
|
|
|
EMAIL_HOST = "mailcatcher"
|
|
|
|
|
EMAIL_PORT = 1025
|
|
|
|
|
EMAIL_USE_TLS = False
|
|
|
|
|
EMAIL_USE_SSL = False
|
2026-03-10 01:30:42 +01:00
|
|
|
DEFAULT_FROM_EMAIL = "noreply@example.local"
|
|
|
|
|
CALENDAR_INVITATION_FROM_EMAIL = "noreply@example.local"
|
✨(all) add organizations, resources, channels, and infra migration (#34)
Add multi-tenant organization model populated from OIDC claims with
org-scoped user discovery, CalDAV principal filtering, and cross-org
isolation at the SabreDAV layer.
Add bookable resource principals (rooms, equipment) with CalDAV
auto-scheduling that handles conflict detection, auto-accept/decline,
and org-scoped booking enforcement. Fixes #14.
Replace CalendarSubscriptionToken with a unified Channel model
supporting CalDAV integration tokens and iCal feed URLs, with
encrypted token storage and role-based access control. Fixes #16.
Migrate task queue from Celery to Dramatiq with async ICS import,
progress tracking, and task status polling endpoint.
Replace nginx with Caddy for both the reverse proxy and frontend
static serving. Switch frontend package manager from yarn/pnpm to
npm and upgrade Node to 24, Next.js to 16, TypeScript to 5.9.
Harden security with fail-closed entitlements, RSVP rate limiting
and token expiry, CalDAV proxy path validation blocking internal
API routes, channel path scope enforcement, and ETag-based
conflict prevention.
Add frontend pages for resource management and integration channel
CRUD, with resource booking in the event modal.
Restructure CalDAV paths to /calendars/users/ and
/calendars/resources/ with nested principal collections in SabreDAV.
2026-03-09 09:09:34 +01:00
|
|
|
APP_NAME = "Calendars (dev)"
|
|
|
|
|
APP_URL = "http://localhost:8931"
|
2026-01-25 20:33:11 +01:00
|
|
|
|
2026-01-09 00:51:25 +01:00
|
|
|
DEBUG_TOOLBAR_CONFIG = {
|
|
|
|
|
"SHOW_TOOLBAR_CALLBACK": lambda request: True,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
def __init__(self):
|
|
|
|
|
# pylint: disable=invalid-name
|
|
|
|
|
self.MIDDLEWARE += ["debug_toolbar.middleware.DebugToolbarMiddleware"]
|
|
|
|
|
# pylint: disable=invalid-name
|
|
|
|
|
self.INSTALLED_APPS += [
|
|
|
|
|
"django_extensions",
|
|
|
|
|
"drf_spectacular_sidecar",
|
|
|
|
|
"debug_toolbar",
|
|
|
|
|
"e2e",
|
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class Test(Base):
|
|
|
|
|
"""Test environment settings"""
|
|
|
|
|
|
|
|
|
|
SESSION_CACHE_ALIAS = "default"
|
|
|
|
|
CACHES = {
|
|
|
|
|
"default": {"BACKEND": "django.core.cache.backends.locmem.LocMemCache"},
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
PASSWORD_HASHERS = [
|
|
|
|
|
"django.contrib.auth.hashers.MD5PasswordHasher",
|
|
|
|
|
]
|
|
|
|
|
USE_SWAGGER = True
|
|
|
|
|
|
✨(all) add organizations, resources, channels, and infra migration (#34)
Add multi-tenant organization model populated from OIDC claims with
org-scoped user discovery, CalDAV principal filtering, and cross-org
isolation at the SabreDAV layer.
Add bookable resource principals (rooms, equipment) with CalDAV
auto-scheduling that handles conflict detection, auto-accept/decline,
and org-scoped booking enforcement. Fixes #14.
Replace CalendarSubscriptionToken with a unified Channel model
supporting CalDAV integration tokens and iCal feed URLs, with
encrypted token storage and role-based access control. Fixes #16.
Migrate task queue from Celery to Dramatiq with async ICS import,
progress tracking, and task status polling endpoint.
Replace nginx with Caddy for both the reverse proxy and frontend
static serving. Switch frontend package manager from yarn/pnpm to
npm and upgrade Node to 24, Next.js to 16, TypeScript to 5.9.
Harden security with fail-closed entitlements, RSVP rate limiting
and token expiry, CalDAV proxy path validation blocking internal
API routes, channel path scope enforcement, and ETag-based
conflict prevention.
Add frontend pages for resource management and integration channel
CRUD, with resource booking in the event modal.
Restructure CalDAV paths to /calendars/users/ and
/calendars/resources/ with nested principal collections in SabreDAV.
2026-03-09 09:09:34 +01:00
|
|
|
DRAMATIQ_BROKER = {
|
|
|
|
|
"BROKER": "core.task_utils.EagerBroker",
|
|
|
|
|
"OPTIONS": {},
|
|
|
|
|
"MIDDLEWARE": [
|
|
|
|
|
"dramatiq.middleware.CurrentMessage",
|
|
|
|
|
],
|
|
|
|
|
}
|
|
|
|
|
DRAMATIQ_RESULT_BACKEND = {
|
|
|
|
|
"BACKEND": "dramatiq.results.backends.stub.StubBackend",
|
|
|
|
|
"BACKEND_OPTIONS": {},
|
|
|
|
|
"MIDDLEWARE_OPTIONS": {"result_ttl": 1000 * 60 * 10},
|
|
|
|
|
}
|
2026-01-09 00:51:25 +01:00
|
|
|
|
|
|
|
|
OIDC_STORE_ACCESS_TOKEN = False
|
|
|
|
|
OIDC_STORE_REFRESH_TOKEN = False
|
|
|
|
|
|
|
|
|
|
def __init__(self):
|
|
|
|
|
# pylint: disable=invalid-name
|
|
|
|
|
self.INSTALLED_APPS += ["drf_spectacular_sidecar", "e2e"]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class ContinuousIntegration(Test):
|
|
|
|
|
"""
|
|
|
|
|
Continuous Integration environment settings
|
|
|
|
|
|
|
|
|
|
nota bene: it should inherit from the Test environment.
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class Production(Base):
|
|
|
|
|
"""
|
|
|
|
|
Production environment settings
|
|
|
|
|
|
|
|
|
|
You must define the ALLOWED_HOSTS environment variable in Production
|
|
|
|
|
configuration (and derived configurations):
|
|
|
|
|
ALLOWED_HOSTS=["foo.com", "foo.fr"]
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
# Security
|
|
|
|
|
# Add allowed host from environment variables.
|
|
|
|
|
# The machine hostname is added by default,
|
|
|
|
|
# it makes the application pingable by a load balancer on the same machine by example
|
|
|
|
|
ALLOWED_HOSTS = [
|
|
|
|
|
*values.ListValue([], environ_name="ALLOWED_HOSTS"),
|
|
|
|
|
gethostbyname(gethostname()),
|
|
|
|
|
]
|
|
|
|
|
CSRF_TRUSTED_ORIGINS = values.ListValue([])
|
|
|
|
|
SECURE_BROWSER_XSS_FILTER = True
|
|
|
|
|
SECURE_CONTENT_TYPE_NOSNIFF = True
|
|
|
|
|
|
|
|
|
|
# SECURE_PROXY_SSL_HEADER allows to fix the scheme in Django's HttpRequest
|
|
|
|
|
# object when your application is behind a reverse proxy.
|
|
|
|
|
#
|
|
|
|
|
# Keep this SECURE_PROXY_SSL_HEADER configuration only if :
|
|
|
|
|
# - your Django app is behind a proxy.
|
|
|
|
|
# - your proxy strips the X-Forwarded-Proto header from all incoming requests
|
|
|
|
|
# - Your proxy sets the X-Forwarded-Proto header and sends it to Django
|
|
|
|
|
#
|
|
|
|
|
# In other cases, you should comment the following line to avoid security issues.
|
|
|
|
|
# SECURE_PROXY_SSL_HEADER = ("HTTP_X_FORWARDED_PROTO", "https")
|
|
|
|
|
SECURE_PROXY_SSL_HEADER = ("HTTP_X_FORWARDED_PROTO", "https")
|
|
|
|
|
SECURE_HSTS_SECONDS = 60
|
|
|
|
|
SECURE_HSTS_PRELOAD = True
|
|
|
|
|
SECURE_HSTS_INCLUDE_SUBDOMAINS = True
|
|
|
|
|
SECURE_SSL_REDIRECT = True
|
|
|
|
|
SECURE_REDIRECT_EXEMPT = [
|
|
|
|
|
"^__lbheartbeat__",
|
|
|
|
|
"^__heartbeat__",
|
2026-02-19 18:15:47 +01:00
|
|
|
r"^api/v1\.0/caldav-scheduling-callback/",
|
✨(all) add organizations, resources, channels, and infra migration (#34)
Add multi-tenant organization model populated from OIDC claims with
org-scoped user discovery, CalDAV principal filtering, and cross-org
isolation at the SabreDAV layer.
Add bookable resource principals (rooms, equipment) with CalDAV
auto-scheduling that handles conflict detection, auto-accept/decline,
and org-scoped booking enforcement. Fixes #14.
Replace CalendarSubscriptionToken with a unified Channel model
supporting CalDAV integration tokens and iCal feed URLs, with
encrypted token storage and role-based access control. Fixes #16.
Migrate task queue from Celery to Dramatiq with async ICS import,
progress tracking, and task status polling endpoint.
Replace nginx with Caddy for both the reverse proxy and frontend
static serving. Switch frontend package manager from yarn/pnpm to
npm and upgrade Node to 24, Next.js to 16, TypeScript to 5.9.
Harden security with fail-closed entitlements, RSVP rate limiting
and token expiry, CalDAV proxy path validation blocking internal
API routes, channel path scope enforcement, and ETag-based
conflict prevention.
Add frontend pages for resource management and integration channel
CRUD, with resource booking in the event modal.
Restructure CalDAV paths to /calendars/users/ and
/calendars/resources/ with nested principal collections in SabreDAV.
2026-03-09 09:09:34 +01:00
|
|
|
r"^caldav/",
|
2026-01-09 00:51:25 +01:00
|
|
|
]
|
|
|
|
|
|
|
|
|
|
# Modern browsers require to have the `secure` attribute on cookies with `Samesite=none`
|
|
|
|
|
CSRF_COOKIE_SECURE = True
|
|
|
|
|
SESSION_COOKIE_SECURE = True
|
|
|
|
|
|
|
|
|
|
# Privacy
|
|
|
|
|
SECURE_REFERRER_POLICY = "same-origin"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class Feature(Production):
|
|
|
|
|
"""
|
|
|
|
|
Feature environment settings
|
|
|
|
|
|
|
|
|
|
nota bene: it should inherit from the Production environment.
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class Staging(Production):
|
|
|
|
|
"""
|
|
|
|
|
Staging environment settings
|
|
|
|
|
|
|
|
|
|
nota bene: it should inherit from the Production environment.
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class PreProduction(Production):
|
|
|
|
|
"""
|
|
|
|
|
Pre-production environment settings
|
|
|
|
|
|
|
|
|
|
nota bene: it should inherit from the Production environment.
|
|
|
|
|
"""
|