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 --- takahe/settings.py | 153 +++++++++++++++++++++++++++++++++++++++++++---------- takahe/urls.py | 6 ++- 2 files changed, 131 insertions(+), 28 deletions(-) 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("@/posts//report/", report.SubmitReport.as_view()), path("@/posts//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