From 7819d7980bddb77ed19ad5d252461be16e2aa7df Mon Sep 17 00:00:00 2001
From: Georg Pfuetzenreuter
Date: Sun, 18 Dec 2022 16:41:24 +0100
Subject: Update urls.py + settings.py

- modify paths to match package patches
- add SAML configuration

Signed-off-by: Georg Pfuetzenreuter <mail@georg-pfuetzenreuter.net>
---
 takahe/settings.py | 153 +++++++++++++++++++++++++++++++++++++++++++----------
 takahe/urls.py     |   6 ++-
 2 files changed, 131 insertions(+), 28 deletions(-)

(limited to 'takahe')

diff --git a/takahe/settings.py b/takahe/settings.py
index d284e0e..02f91dd 100644
--- a/takahe/settings.py
+++ b/takahe/settings.py
@@ -12,9 +12,12 @@ import sentry_sdk
 from pydantic import AnyUrl, BaseSettings, EmailStr, Field, validator
 from sentry_sdk.integrations.django import DjangoIntegration
 
-from takahe import __version__
+from takahe.takahe import __version__
 
-BASE_DIR = Path(__file__).resolve().parent.parent
+import saml2
+from saml2.saml import NAMEID_FORMAT_PERSISTENT
+
+BASE_DIR = '/srv/takahe'
 
 
 class CacheBackendUrl(AnyUrl):
@@ -59,10 +62,10 @@ class Settings(BaseSettings):
 
     #: The currently running environment, used for things such as sentry
     #: error reporting.
-    ENVIRONMENT: Environments = "development"
+    ENVIRONMENT: Environments = "production"
 
     #: Should django run in debug mode?
-    DEBUG: bool = False
+    DEBUG: bool = True
 
     #: Set a secret key used for signing values such as sessions. Randomized
     #: by default, so you'll logout everytime the process restarts.
@@ -99,7 +102,7 @@ class Settings(BaseSettings):
     ERROR_EMAILS: list[EmailStr] | None = None
 
     MEDIA_URL: str = "/media/"
-    MEDIA_ROOT: str = str(BASE_DIR / "media")
+    MEDIA_ROOT: str = os.path.join(BASE_DIR, "media")
     MEDIA_BACKEND: MediaBackendUrl | None = None
 
     #: Maximum filesize when uploading images. Increasing this may increase memory utilization
@@ -147,7 +150,7 @@ class Settings(BaseSettings):
 
     class Config:
         env_prefix = "TAKAHE_"
-        env_file = str(BASE_DIR / TAKAHE_ENV_FILE)
+        env_file = '/etc/sysconfig/takahe'
         env_file_encoding = "utf-8"
         # Case sensitivity doesn't work on Windows, so might as well be
         # consistent from the get-go.
@@ -179,16 +182,17 @@ INSTALLED_APPS = [
     "django.contrib.staticfiles",
     "django_htmx",
     "corsheaders",
-    "core",
-    "activities",
-    "api",
-    "mediaproxy",
-    "stator",
-    "users",
+    "takahe.core",
+    "takahe.activities",
+    "takahe.api",
+    "takahe.mediaproxy",
+    "takahe.stator",
+    "takahe.users",
+    "djangosaml2",
 ]
 
 MIDDLEWARE = [
-    "core.middleware.SentryTaggingMiddleware",
+    "takahe.core.middleware.SentryTaggingMiddleware",
     "django.middleware.security.SecurityMiddleware",
     "corsheaders.middleware.CorsMiddleware",
     "whitenoise.middleware.WhiteNoiseMiddleware",
@@ -199,19 +203,20 @@ MIDDLEWARE = [
     "django.contrib.messages.middleware.MessageMiddleware",
     "django.middleware.clickjacking.XFrameOptionsMiddleware",
     "django_htmx.middleware.HtmxMiddleware",
-    "core.middleware.HeadersMiddleware",
-    "core.middleware.ConfigLoadingMiddleware",
-    "api.middleware.ApiTokenMiddleware",
-    "users.middleware.IdentityMiddleware",
-    "activities.middleware.EmojiDefaultsLoadingMiddleware",
+    "takahe.core.middleware.HeadersMiddleware",
+    "takahe.core.middleware.ConfigLoadingMiddleware",
+    "takahe.api.middleware.ApiTokenMiddleware",
+    "takahe.users.middleware.IdentityMiddleware",
+    "takahe.activities.middleware.EmojiDefaultsLoadingMiddleware",
+    "djangosaml2.middleware.SamlSessionMiddleware",
 ]
 
-ROOT_URLCONF = "takahe.urls"
+ROOT_URLCONF = "takahe.takahe.urls"
 
 TEMPLATES = [
     {
         "BACKEND": "django.template.backends.django.DjangoTemplates",
-        "DIRS": [BASE_DIR / "templates"],
+        "DIRS": [os.path.join(BASE_DIR, "templates")],
         "APP_DIRS": True,
         "OPTIONS": {
             "context_processors": [
@@ -219,13 +224,13 @@ TEMPLATES = [
                 "django.template.context_processors.request",
                 "django.contrib.auth.context_processors.auth",
                 "django.contrib.messages.context_processors.messages",
-                "core.context.config_context",
+                "takahe.core.context.config_context",
             ],
         },
     },
 ]
 
-WSGI_APPLICATION = "takahe.wsgi.application"
+WSGI_APPLICATION = "takahe.takahe.wsgi.application"
 
 if SETUP.DATABASE_SERVER:
     DATABASES = {
@@ -272,7 +277,7 @@ DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField"
 
 AUTH_USER_MODEL = "users.User"
 
-LOGIN_URL = "/auth/login/"
+LOGIN_URL = "/saml2/login/"
 LOGOUT_URL = "/auth/logout/"
 LOGIN_REDIRECT_URL = "/"
 LOGOUT_REDIRECT_URL = "/"
@@ -282,7 +287,7 @@ STATICFILES_FINDERS = [
     "django.contrib.staticfiles.finders.AppDirectoriesFinder",
 ]
 
-STATICFILES_DIRS = [BASE_DIR / "static"]
+STATICFILES_DIRS = [os.path.join(BASE_DIR, "static")]
 
 STATICFILES_STORAGE = "django.contrib.staticfiles.storage.ManifestStaticFilesStorage"
 
@@ -290,7 +295,7 @@ SESSION_ENGINE = "django.contrib.sessions.backends.signed_cookies"
 
 WHITENOISE_MAX_AGE = 3600
 
-STATIC_ROOT = BASE_DIR / "static-collected"
+STATIC_ROOT = os.path.join(BASE_DIR, "static-collected")
 
 ALLOWED_HOSTS = SETUP.ALLOWED_HOSTS
 
@@ -355,14 +360,14 @@ if SETUP.MEDIA_BACKEND:
     parsed = urllib.parse.urlparse(SETUP.MEDIA_BACKEND)
     query = urllib.parse.parse_qs(parsed.query)
     if parsed.scheme == "gs":
-        DEFAULT_FILE_STORAGE = "core.uploads.TakaheGoogleCloudStorage"
+        DEFAULT_FILE_STORAGE = "takahe.core.uploads.TakaheGoogleCloudStorage"
         GS_BUCKET_NAME = parsed.path.lstrip("/")
         GS_QUERYSTRING_AUTH = False
         if parsed.hostname is not None:
             port = parsed.port or 443
             GS_CUSTOM_ENDPOINT = f"https://{parsed.hostname}:{port}"
     elif parsed.scheme == "s3":
-        DEFAULT_FILE_STORAGE = "core.uploads.TakaheS3Storage"
+        DEFAULT_FILE_STORAGE = "takahe.core.uploads.TakaheS3Storage"
         AWS_STORAGE_BUCKET_NAME = parsed.path.lstrip("/")
         AWS_QUERYSTRING_AUTH = False
         AWS_DEFAULT_ACL = "public-read"
@@ -393,3 +398,97 @@ TAKAHE_USER_AGENT = (
     f"python-httpx/{httpx.__version__} "
     f"(Takahe/{__version__}; +https://{SETUP.MAIN_DOMAIN}/)"
 )
+
+AUTHENTICATION_BACKENDS = (
+    'django.contrib.auth.backends.ModelBackend',
+    'djangosaml2.backends.Saml2Backend',
+)
+
+SAML_SESSION_COOKIE_NAME = 'takahe_test_session'
+SESSION_COOKIE_SECURE = False
+SESSION_EXPIRE_AT_BROWSER_CLOSE = True
+SAML_DJANGO_USER_MAIN_ATTRIBUTE = 'email'
+
+SAML_ATTRIBUTE_MAPPING = {
+    'email': ('email', ),
+}
+
+SAML_CONFIG = {
+  # full path to the xmlsec1 binary programm
+  'xmlsec_binary': '/usr/bin/xmlsec1',
+
+  # your entity id, usually your subdomain plus the url to the metadata view
+  'entityid': 'https://__REPLACE_ME__/saml2/metadata/',
+
+  'attribute_map_dir': os.path.join(BASE_DIR, 'attribute-maps'),
+
+  'allow_unknown_attributes': False,
+
+  'service': {
+      'sp' : {
+          'name': 'Federated Takahe sample SP',
+          'name_id_format': saml2.saml.NAMEID_FORMAT_PERSISTENT,
+
+          'endpoints': {
+              'assertion_consumer_service': [
+                  ('https://__REPLACE_ME__/saml2/acs/',
+                   saml2.BINDING_HTTP_POST),
+                  ],
+              'single_logout_service': [
+                  ('https://__REPLACE_ME__/saml2/ls/',
+                   saml2.BINDING_HTTP_REDIRECT),
+                  ('https://__REPLACE_ME__/saml2/ls/post',
+                   saml2.BINDING_HTTP_POST),
+                  ],
+              },
+
+          'signing_algorithm':  saml2.xmldsig.SIG_RSA_SHA256,
+          'digest_algorithm':  saml2.xmldsig.DIGEST_SHA256,
+
+          'force_authn': False,
+
+          'name_id_format_allow_create': False,
+
+          'required_attributes': ['uid',
+                                  'email'],
+
+          'want_response_signed': True,
+          'authn_requests_signed': True,
+          'logout_requests_signed': True,
+          'want_assertions_signed': True,
+
+          'only_use_keys_in_metadata': True,
+
+          'allow_unsolicited': False,
+
+  'metadata': {
+# in production, use local file
+#      'local': [os.path.join(BASE_DIR, 'remote_metadata.xml')],
+      'remote': [{"url": "https://libsso.net/realms/LibertaCasa/protocol/saml/descriptor"},],
+      },
+
+  'debug': 1,
+
+  'key_file': '__REPLACE_ME__',  # private part
+  'cert_file': '__REPLACE_ME__',  # public part
+
+  # Encryption
+#  'encryption_keypairs': [{
+#      'key_file': '__REPLACE_ME__',  # private part
+#      'cert_file': '__REPLACE_ME__',  # public part
+#  }],
+}
+
+LOGGING = {
+    'version': 1,
+    'disable_existing_loggers': False,
+    'handlers': {
+        'console': {
+            'class': 'logging.StreamHandler',
+        },
+    },
+    'root': {
+        'handlers': ['console'],
+        'level': 'DEBUG',
+    },
+}
diff --git a/takahe/urls.py b/takahe/urls.py
index d3572a9..ea205c5 100644
--- a/takahe/urls.py
+++ b/takahe/urls.py
@@ -9,6 +9,9 @@ from mediaproxy import views as mediaproxy
 from stator import views as stator
 from users.views import activitypub, admin, auth, identity, report, settings
 
+from django.conf.urls import include
+from django.views.generic.base import RedirectView
+
 urlpatterns = [
     path("", core.homepage),
     path("manifest.json", core.AppManifest.as_view()),
@@ -174,7 +177,7 @@ urlpatterns = [
     path("@<handle>/posts/<int:post_id>/report/", report.SubmitReport.as_view()),
     path("@<handle>/posts/<int:post_id>/edit/", compose.Compose.as_view()),
     # Authentication
-    path("auth/login/", auth.Login.as_view(), name="login"),
+    path("auth/login/", RedirectView.as_view(url='/saml2/login', permanent=False), name='login'),
     path("auth/logout/", auth.Logout.as_view(), name="logout"),
     path("auth/signup/", auth.Signup.as_view(), name="signup"),
     path("auth/reset/", auth.TriggerReset.as_view(), name="trigger_reset"),
@@ -248,4 +251,5 @@ urlpatterns = [
         core.custom_static_serve,
         kwargs={"document_root": djsettings.MEDIA_ROOT},
     ),
+    path('saml2/', include('djangosaml2.urls')),
 ]
-- 
cgit v1.2.3