diff --git a/authentik/blueprints/v1/importer.py b/authentik/blueprints/v1/importer.py index 7f9981189c..1b2ce79602 100644 --- a/authentik/blueprints/v1/importer.py +++ b/authentik/blueprints/v1/importer.py @@ -13,6 +13,7 @@ from django.db.models import Model from django.db.models.query_utils import Q from django.db.transaction import atomic from django.db.utils import IntegrityError +from guardian.shortcuts import assign_perm from rest_framework.exceptions import ValidationError from rest_framework.serializers import BaseSerializer, Serializer from structlog.stdlib import BoundLogger, get_logger @@ -27,11 +28,14 @@ from authentik.blueprints.v1.common import ( EntryInvalidError, ) from authentik.blueprints.v1.meta.registry import BaseMetaModel, registry +from authentik.core.models import User from authentik.enterprise.license import LicenseKey from authentik.events.logs import LogEvent, capture_logs from authentik.events.utils import cleanse_dict from authentik.lib.models import SerializerModel, excluded_models from authentik.lib.sentry import SentryIgnoredException +from authentik.lib.utils.reflection import get_apps +from authentik.rbac.models import Role # Context set when the serializer is created in a blueprint context # Update website/docs/customize/blueprints/v1/models.md when used @@ -58,6 +62,16 @@ def transaction_rollback(): pass +def rbac_models() -> dict: + models = {} + for app in get_apps(): + for model in app.get_models(): + if not is_model_allowed(model): + continue + models[model._meta.model_name] = app.label + return models + + class Importer: """Import Blueprint from raw dict or YAML/JSON""" @@ -76,7 +90,10 @@ class Importer: def default_context(self): """Default context""" - return {"goauthentik.io/enterprise/licensed": LicenseKey.get_total().is_valid()} + return { + "goauthentik.io/enterprise/licensed": LicenseKey.get_total().status().is_valid, + "goauthentik.io/rbac/models": rbac_models(), + } @staticmethod def from_string(yaml_input: str, context: dict | None = None) -> "Importer": @@ -143,7 +160,10 @@ class Importer: return None model_app_label, model_name = entry.get_model(self._import).split(".") - model: type[SerializerModel] = registry.get_model(model_app_label, model_name) + try: + model: type[SerializerModel] = registry.get_model(model_app_label, model_name) + except LookupError as exc: + raise EntryInvalidError.from_entry(exc, entry) from exc # Don't use isinstance since we don't want to check for inheritance if not is_model_allowed(model): raise EntryInvalidError.from_entry(f"Model {model} not allowed", entry) @@ -223,10 +243,7 @@ class Importer: try: full_data = self.__update_pks_for_attrs(entry.get_attrs(self._import)) except ValueError as exc: - raise EntryInvalidError.from_entry( - exc, - entry, - ) from exc + raise EntryInvalidError.from_entry(exc, entry) from exc always_merger.merge(full_data, updated_identifiers) serializer_kwargs["data"] = full_data @@ -247,6 +264,15 @@ class Importer: ) from exc return serializer + def _apply_permissions(self, instance: Model, entry: BlueprintEntry): + """Apply object-level permissions for an entry""" + for perm in entry.get_permissions(self._import): + if perm.user is not None: + assign_perm(perm.permission, User.objects.get(pk=perm.user), instance) + if perm.role is not None: + role = Role.objects.get(pk=perm.role) + role.assign_permission(perm.permission, obj=instance) + def apply(self) -> bool: """Apply (create/update) models yaml, in database transaction""" try: @@ -311,6 +337,7 @@ class Importer: if "pk" in entry.identifiers: self.__pk_map[entry.identifiers["pk"]] = instance.pk entry._state = BlueprintEntryState(instance) + self._apply_permissions(instance, entry) elif state == BlueprintEntryDesiredState.ABSENT: instance: Model | None = serializer.instance if instance.pk: diff --git a/authentik/lib/models.py b/authentik/lib/models.py index 94e08f3031..4800ffba57 100644 --- a/authentik/lib/models.py +++ b/authentik/lib/models.py @@ -111,6 +111,7 @@ class DomainlessFormattedURLValidator(DomainlessURLValidator): __internal_models = [] + def internal_model(cls): __internal_models.append(cls) return cls @@ -132,6 +133,4 @@ def excluded_models() -> list[type[Model]]: Permission, UserObjectPermission, ] - return tuple( - static + [x for x in apps.get_models() if x in __internal_models] - ) + return tuple(static + [x for x in apps.get_models() if x in __internal_models])