authentik.blueprints.models

blueprint models

  1"""blueprint models"""
  2
  3from pathlib import Path
  4from uuid import uuid4
  5
  6from django.contrib.contenttypes.fields import GenericRelation
  7from django.contrib.postgres.fields import ArrayField
  8from django.db import models
  9from django.utils.translation import gettext_lazy as _
 10from rest_framework.serializers import Serializer
 11from structlog import get_logger
 12
 13from authentik.blueprints.v1.oci import OCI_PREFIX, BlueprintOCIClient, OCIException
 14from authentik.lib.config import CONFIG
 15from authentik.lib.models import CreatedUpdatedModel, SerializerModel
 16from authentik.lib.sentry import SentryIgnoredException
 17
 18LOGGER = get_logger()
 19
 20
 21class BlueprintRetrievalFailed(SentryIgnoredException):
 22    """Error raised when we are unable to fetch the blueprint contents, whether it be HTTP files
 23    not being accessible or local files not being readable"""
 24
 25
 26class ManagedModel(models.Model):
 27    """Model that can be managed by authentik exclusively"""
 28
 29    managed = models.TextField(
 30        default=None,
 31        null=True,
 32        verbose_name=_("Managed by authentik"),
 33        help_text=_(
 34            "Objects that are managed by authentik. These objects are created and updated "
 35            "automatically. This flag only indicates that an object can be overwritten by "
 36            "migrations. You can still modify the objects via the API, but expect changes "
 37            "to be overwritten in a later update."
 38        ),
 39        unique=True,
 40    )
 41
 42    class Meta:
 43        abstract = True
 44
 45
 46class BlueprintInstanceStatus(models.TextChoices):
 47    """Instance status"""
 48
 49    SUCCESSFUL = "successful"
 50    WARNING = "warning"
 51    ERROR = "error"
 52    ORPHANED = "orphaned"
 53    UNKNOWN = "unknown"
 54
 55
 56class BlueprintInstance(SerializerModel, ManagedModel, CreatedUpdatedModel):
 57    """Instance of a single blueprint. Can be parameterized via context attribute when
 58    blueprint in `path` has inputs."""
 59
 60    instance_uuid = models.UUIDField(primary_key=True, editable=False, default=uuid4)
 61
 62    name = models.TextField(unique=True)
 63    metadata = models.JSONField(default=dict)
 64    path = models.TextField(default="", blank=True)
 65    content = models.TextField(default="", blank=True)
 66    context = models.JSONField(default=dict)
 67    last_applied = models.DateTimeField(auto_now=True)
 68    last_applied_hash = models.TextField()
 69    status = models.TextField(
 70        choices=BlueprintInstanceStatus.choices, default=BlueprintInstanceStatus.UNKNOWN
 71    )
 72    enabled = models.BooleanField(default=True)
 73    managed_models = ArrayField(models.TextField(), default=list)
 74
 75    # Manual link to tasks instead of using TasksModel because of loop imports
 76    tasks = GenericRelation(
 77        "authentik_tasks.Task",
 78        content_type_field="rel_obj_content_type",
 79        object_id_field="rel_obj_id",
 80    )
 81
 82    class Meta:
 83        verbose_name = _("Blueprint Instance")
 84        verbose_name_plural = _("Blueprint Instances")
 85        unique_together = (
 86            (
 87                "name",
 88                "path",
 89            ),
 90        )
 91
 92    def __str__(self) -> str:
 93        return f"Blueprint Instance {self.name}"
 94
 95    def retrieve_oci(self) -> str:
 96        """Get blueprint from an OCI registry"""
 97        client = BlueprintOCIClient(self.path.replace(OCI_PREFIX, "https://"))
 98        try:
 99            manifests = client.fetch_manifests()
100            return client.fetch_blobs(manifests)
101        except OCIException as exc:
102            raise BlueprintRetrievalFailed(exc) from exc
103
104    def retrieve_file(self) -> str:
105        """Get blueprint from path"""
106        try:
107            base = Path(CONFIG.get("blueprints_dir"))
108            full_path = base.joinpath(Path(self.path)).resolve()
109            if not str(full_path).startswith(str(base.resolve())):
110                raise BlueprintRetrievalFailed("Invalid blueprint path")
111            with full_path.open("r", encoding="utf-8") as _file:
112                return _file.read()
113        except OSError as exc:
114            raise BlueprintRetrievalFailed(exc) from exc
115
116    def retrieve(self) -> str:
117        """Retrieve blueprint contents"""
118        if self.path.startswith(OCI_PREFIX):
119            return self.retrieve_oci()
120        if self.path != "":
121            return self.retrieve_file()
122        return self.content
123
124    @property
125    def serializer(self) -> Serializer:
126        from authentik.blueprints.api import BlueprintInstanceSerializer
127
128        return BlueprintInstanceSerializer
LOGGER = <BoundLoggerLazyProxy(logger=None, wrapper_class=None, processors=None, context_class=None, initial_values={}, logger_factory_args=())>
class BlueprintRetrievalFailed(authentik.lib.sentry.SentryIgnoredException):
22class BlueprintRetrievalFailed(SentryIgnoredException):
23    """Error raised when we are unable to fetch the blueprint contents, whether it be HTTP files
24    not being accessible or local files not being readable"""

Error raised when we are unable to fetch the blueprint contents, whether it be HTTP files not being accessible or local files not being readable

class ManagedModel(django.db.models.base.Model):
27class ManagedModel(models.Model):
28    """Model that can be managed by authentik exclusively"""
29
30    managed = models.TextField(
31        default=None,
32        null=True,
33        verbose_name=_("Managed by authentik"),
34        help_text=_(
35            "Objects that are managed by authentik. These objects are created and updated "
36            "automatically. This flag only indicates that an object can be overwritten by "
37            "migrations. You can still modify the objects via the API, but expect changes "
38            "to be overwritten in a later update."
39        ),
40        unique=True,
41    )
42
43    class Meta:
44        abstract = True

Model that can be managed by authentik exclusively

def managed(unknown):

A wrapper for a deferred-loading field. When the value is read from this object the first time, the query is executed.

class ManagedModel.Meta:
43    class Meta:
44        abstract = True
abstract = False
class BlueprintInstanceStatus(django.db.models.enums.TextChoices):
47class BlueprintInstanceStatus(models.TextChoices):
48    """Instance status"""
49
50    SUCCESSFUL = "successful"
51    WARNING = "warning"
52    ERROR = "error"
53    ORPHANED = "orphaned"
54    UNKNOWN = "unknown"

Instance status

 57class BlueprintInstance(SerializerModel, ManagedModel, CreatedUpdatedModel):
 58    """Instance of a single blueprint. Can be parameterized via context attribute when
 59    blueprint in `path` has inputs."""
 60
 61    instance_uuid = models.UUIDField(primary_key=True, editable=False, default=uuid4)
 62
 63    name = models.TextField(unique=True)
 64    metadata = models.JSONField(default=dict)
 65    path = models.TextField(default="", blank=True)
 66    content = models.TextField(default="", blank=True)
 67    context = models.JSONField(default=dict)
 68    last_applied = models.DateTimeField(auto_now=True)
 69    last_applied_hash = models.TextField()
 70    status = models.TextField(
 71        choices=BlueprintInstanceStatus.choices, default=BlueprintInstanceStatus.UNKNOWN
 72    )
 73    enabled = models.BooleanField(default=True)
 74    managed_models = ArrayField(models.TextField(), default=list)
 75
 76    # Manual link to tasks instead of using TasksModel because of loop imports
 77    tasks = GenericRelation(
 78        "authentik_tasks.Task",
 79        content_type_field="rel_obj_content_type",
 80        object_id_field="rel_obj_id",
 81    )
 82
 83    class Meta:
 84        verbose_name = _("Blueprint Instance")
 85        verbose_name_plural = _("Blueprint Instances")
 86        unique_together = (
 87            (
 88                "name",
 89                "path",
 90            ),
 91        )
 92
 93    def __str__(self) -> str:
 94        return f"Blueprint Instance {self.name}"
 95
 96    def retrieve_oci(self) -> str:
 97        """Get blueprint from an OCI registry"""
 98        client = BlueprintOCIClient(self.path.replace(OCI_PREFIX, "https://"))
 99        try:
100            manifests = client.fetch_manifests()
101            return client.fetch_blobs(manifests)
102        except OCIException as exc:
103            raise BlueprintRetrievalFailed(exc) from exc
104
105    def retrieve_file(self) -> str:
106        """Get blueprint from path"""
107        try:
108            base = Path(CONFIG.get("blueprints_dir"))
109            full_path = base.joinpath(Path(self.path)).resolve()
110            if not str(full_path).startswith(str(base.resolve())):
111                raise BlueprintRetrievalFailed("Invalid blueprint path")
112            with full_path.open("r", encoding="utf-8") as _file:
113                return _file.read()
114        except OSError as exc:
115            raise BlueprintRetrievalFailed(exc) from exc
116
117    def retrieve(self) -> str:
118        """Retrieve blueprint contents"""
119        if self.path.startswith(OCI_PREFIX):
120            return self.retrieve_oci()
121        if self.path != "":
122            return self.retrieve_file()
123        return self.content
124
125    @property
126    def serializer(self) -> Serializer:
127        from authentik.blueprints.api import BlueprintInstanceSerializer
128
129        return BlueprintInstanceSerializer

Instance of a single blueprint. Can be parameterized via context attribute when blueprint in path has inputs.

def instance_uuid(unknown):

A wrapper for a deferred-loading field. When the value is read from this object the first time, the query is executed.

def name(unknown):

A wrapper for a deferred-loading field. When the value is read from this object the first time, the query is executed.

def metadata(unknown):

A wrapper for a deferred-loading field. When the value is read from this object the first time, the query is executed.

def path(unknown):

A wrapper for a deferred-loading field. When the value is read from this object the first time, the query is executed.

def content(unknown):

A wrapper for a deferred-loading field. When the value is read from this object the first time, the query is executed.

def context(unknown):

A wrapper for a deferred-loading field. When the value is read from this object the first time, the query is executed.

def last_applied(unknown):

A wrapper for a deferred-loading field. When the value is read from this object the first time, the query is executed.

def last_applied_hash(unknown):

A wrapper for a deferred-loading field. When the value is read from this object the first time, the query is executed.

def status(unknown):

A wrapper for a deferred-loading field. When the value is read from this object the first time, the query is executed.

def enabled(unknown):

A wrapper for a deferred-loading field. When the value is read from this object the first time, the query is executed.

def managed_models(unknown):

A wrapper for a deferred-loading field. When the value is read from this object the first time, the query is executed.

tasks

Accessor to the related objects manager on the one-to-many relation created by GenericRelation.

In the example::

class Post(Model):
    comments = GenericRelation(Comment)

post.comments is a ReverseGenericManyToOneDescriptor instance.

def retrieve_oci(self) -> str:
 96    def retrieve_oci(self) -> str:
 97        """Get blueprint from an OCI registry"""
 98        client = BlueprintOCIClient(self.path.replace(OCI_PREFIX, "https://"))
 99        try:
100            manifests = client.fetch_manifests()
101            return client.fetch_blobs(manifests)
102        except OCIException as exc:
103            raise BlueprintRetrievalFailed(exc) from exc

Get blueprint from an OCI registry

def retrieve_file(self) -> str:
105    def retrieve_file(self) -> str:
106        """Get blueprint from path"""
107        try:
108            base = Path(CONFIG.get("blueprints_dir"))
109            full_path = base.joinpath(Path(self.path)).resolve()
110            if not str(full_path).startswith(str(base.resolve())):
111                raise BlueprintRetrievalFailed("Invalid blueprint path")
112            with full_path.open("r", encoding="utf-8") as _file:
113                return _file.read()
114        except OSError as exc:
115            raise BlueprintRetrievalFailed(exc) from exc

Get blueprint from path

def retrieve(self) -> str:
117    def retrieve(self) -> str:
118        """Retrieve blueprint contents"""
119        if self.path.startswith(OCI_PREFIX):
120            return self.retrieve_oci()
121        if self.path != "":
122            return self.retrieve_file()
123        return self.content

Retrieve blueprint contents

serializer: rest_framework.serializers.Serializer
125    @property
126    def serializer(self) -> Serializer:
127        from authentik.blueprints.api import BlueprintInstanceSerializer
128
129        return BlueprintInstanceSerializer

Get serializer for this model

def managed(unknown):

A wrapper for a deferred-loading field. When the value is read from this object the first time, the query is executed.

def created(unknown):

A wrapper for a deferred-loading field. When the value is read from this object the first time, the query is executed.

def last_updated(unknown):

A wrapper for a deferred-loading field. When the value is read from this object the first time, the query is executed.

def get_next_by_last_applied(unknown):

Method descriptor with partial application of the given arguments and keywords.

Supports wrapping existing descriptors and handles non-descriptor callables as instance methods.

def get_previous_by_last_applied(unknown):

Method descriptor with partial application of the given arguments and keywords.

Supports wrapping existing descriptors and handles non-descriptor callables as instance methods.

def get_status_display(unknown):

Method descriptor with partial application of the given arguments and keywords.

Supports wrapping existing descriptors and handles non-descriptor callables as instance methods.

def get_next_by_created(unknown):

Method descriptor with partial application of the given arguments and keywords.

Supports wrapping existing descriptors and handles non-descriptor callables as instance methods.

def get_previous_by_created(unknown):

Method descriptor with partial application of the given arguments and keywords.

Supports wrapping existing descriptors and handles non-descriptor callables as instance methods.

def get_next_by_last_updated(unknown):

Method descriptor with partial application of the given arguments and keywords.

Supports wrapping existing descriptors and handles non-descriptor callables as instance methods.

def get_previous_by_last_updated(unknown):

Method descriptor with partial application of the given arguments and keywords.

Supports wrapping existing descriptors and handles non-descriptor callables as instance methods.

def objects(unknown):

The type of the None singleton.

class BlueprintInstance.DoesNotExist(django.core.exceptions.ObjectDoesNotExist):

The requested object does not exist

class BlueprintInstance.MultipleObjectsReturned(django.core.exceptions.MultipleObjectsReturned):

The query returned multiple objects when only one was expected.