96 lines
4.5 KiB
YAML
96 lines
4.5 KiB
YAML
# Overrides paperless-ngx's own paperless/adapter.py at the same import path
|
|||
|
|
# (mounted via subPath in deployment.yaml) - settings.py hardcodes
|
||
|
|
# SOCIALACCOUNT_ADAPTER = "paperless.adapter.CustomSocialAccountAdapter", so
|
||
|
|
# no Django setting needs to change, just the file content underneath it.
|
||
|
|
#
|
||
|
|
# Stock CustomSocialAccountAdapter.populate_user() is a stub ("kept in case
|
||
|
|
# global default permissions are implemented in the future" - they aren't),
|
||
|
|
# so every OIDC signup lands with zero permissions and 403s on every API
|
||
|
|
# endpoint. This adds the actual mapping: Authentik's "permissions" claim
|
||
|
|
# (via the permissions scope, requested in PAPERLESS_SOCIALACCOUNT_PROVIDERS,
|
||
|
|
# computed server-side from group membership by authentik-provision.py) ->
|
||
|
|
# "paperless:write" or "*" (homelab-admins) grants is_staff+is_superuser,
|
||
|
|
# same convention already used for MinIO's policy claim and Grafana's
|
||
|
|
# role_attribute_path. Checking the permission string rather than a literal
|
||
|
|
# group name decouples "what grants access" from which group happens to
|
||
|
|
# hold it - same pattern applies to every other service's Role/RoleBinding
|
||
|
|
# in k8s/infra/rbac/.
|
||
|
|
apiVersion: v1
|
||
|
|
kind: ConfigMap
|
||
|
|
metadata:
|
||
|
|
name: paperless-adapter
|
||
|
|
data:
|
||
|
|
adapter.py: |
|
||
|
|
from urllib.parse import quote
|
||
|
|
|
||
|
|
from allauth.account.adapter import DefaultAccountAdapter
|
||
|
|
from allauth.core import context
|
||
|
|
from allauth.socialaccount.adapter import DefaultSocialAccountAdapter
|
||
|
|
from django.conf import settings
|
||
|
|
from django.forms import ValidationError
|
||
|
|
from django.urls import reverse
|
||
|
|
|
||
|
|
REQUIRED_PERMISSIONS = {"paperless:write", "*"}
|
||
|
|
|
||
|
|
|
||
|
|
class CustomAccountAdapter(DefaultAccountAdapter):
|
||
|
|
def is_open_for_signup(self, request):
|
||
|
|
allow_signups = super().is_open_for_signup(request)
|
||
|
|
return getattr(settings, "ACCOUNT_ALLOW_SIGNUPS", allow_signups)
|
||
|
|
|
||
|
|
def pre_authenticate(self, request, **credentials):
|
||
|
|
if settings.DISABLE_REGULAR_LOGIN:
|
||
|
|
raise ValidationError("Regular login is disabled")
|
||
|
|
return super().pre_authenticate(request, **credentials)
|
||
|
|
|
||
|
|
def is_safe_url(self, url):
|
||
|
|
from django.utils.http import url_has_allowed_host_and_scheme
|
||
|
|
|
||
|
|
allowed_hosts = {context.request.get_host()} | set(settings.ALLOWED_HOSTS)
|
||
|
|
if "*" in allowed_hosts:
|
||
|
|
allowed_hosts.remove("*")
|
||
|
|
allowed_hosts.add(context.request.get_host())
|
||
|
|
return url_has_allowed_host_and_scheme(url, allowed_hosts=allowed_hosts)
|
||
|
|
return url_has_allowed_host_and_scheme(url, allowed_hosts=allowed_hosts)
|
||
|
|
|
||
|
|
def get_reset_password_from_key_url(self, key):
|
||
|
|
if settings.PAPERLESS_URL is None:
|
||
|
|
return super().get_reset_password_from_key_url(key)
|
||
|
|
path = reverse(
|
||
|
|
"account_reset_password_from_key",
|
||
|
|
kwargs={"uidb36": "UID", "key": "KEY"},
|
||
|
|
)
|
||
|
|
path = path.replace("UID-KEY", quote(key))
|
||
|
|
return settings.PAPERLESS_URL + path
|
||
|
|
|
||
|
|
|
||
|
|
class CustomSocialAccountAdapter(DefaultSocialAccountAdapter):
|
||
|
|
def is_open_for_signup(self, request, sociallogin):
|
||
|
|
allow_signups = super().is_open_for_signup(request, sociallogin)
|
||
|
|
return getattr(settings, "SOCIALACCOUNT_ALLOW_SIGNUPS", allow_signups)
|
||
|
|
|
||
|
|
def get_connect_redirect_url(self, request, socialaccount):
|
||
|
|
return reverse("base")
|
||
|
|
|
||
|
|
def populate_user(self, request, sociallogin, data):
|
||
|
|
user = super().populate_user(request, sociallogin, data)
|
||
|
|
perms = set(sociallogin.account.extra_data.get("permissions") or [])
|
||
|
|
if perms & REQUIRED_PERMISSIONS:
|
||
|
|
user.is_staff = True
|
||
|
|
user.is_superuser = True
|
||
|
|
return user
|
||
|
|
|
||
|
|
def save_user(self, request, sociallogin, form=None):
|
||
|
|
# populate_user() sets the flags on the in-memory user, but
|
||
|
|
# allauth's default save_user() re-derives is_staff from
|
||
|
|
# ACCOUNT_DEFAULT_HTTP_PROTOCOL-independent defaults and can
|
||
|
|
# overwrite them on save - re-apply after super().save_user()
|
||
|
|
# persists the row, matching the permissions check above exactly.
|
||
|
|
user = super().save_user(request, sociallogin, form)
|
||
|
|
perms = set(sociallogin.account.extra_data.get("permissions") or [])
|
||
|
|
if perms & REQUIRED_PERMISSIONS and not (user.is_staff and user.is_superuser):
|
||
|
|
user.is_staff = True
|
||
|
|
user.is_superuser = True
|
||
|
|
user.save(update_fields=["is_staff", "is_superuser"])
|
||
|
|
return user
|