authentik.sources.plex.models

Plex source

  1"""Plex source"""
  2
  3from typing import Any
  4
  5from django.contrib.postgres.fields import ArrayField
  6from django.db import models
  7from django.http.request import HttpRequest
  8from django.templatetags.static import static
  9from django.utils.translation import gettext_lazy as _
 10from rest_framework.fields import CharField
 11from rest_framework.serializers import BaseSerializer, Serializer
 12
 13from authentik.core.models import (
 14    GroupSourceConnection,
 15    PropertyMapping,
 16    Source,
 17    UserSourceConnection,
 18)
 19from authentik.core.types import UILoginButton, UserSettingSerializer
 20from authentik.flows.challenge import Challenge, ChallengeResponse
 21from authentik.lib.generators import generate_id
 22from authentik.lib.utils.time import fqdn_rand
 23from authentik.stages.identification.stage import LoginChallengeMixin
 24from authentik.tasks.schedules.common import ScheduleSpec
 25from authentik.tasks.schedules.models import ScheduledModel
 26
 27
 28class PlexAuthenticationChallenge(LoginChallengeMixin, Challenge):
 29    """Challenge shown to the user in identification stage"""
 30
 31    client_id = CharField()
 32    slug = CharField()
 33    component = CharField(default="ak-source-plex")
 34
 35
 36class PlexAuthenticationChallengeResponse(ChallengeResponse):
 37    """Pseudo class for plex response"""
 38
 39    component = CharField(default="ak-source-plex")
 40
 41
 42class PlexSource(ScheduledModel, Source):
 43    """Authenticate against plex.tv"""
 44
 45    client_id = models.TextField(
 46        default=generate_id,
 47        help_text=_("Client identifier used to talk to Plex."),
 48    )
 49    allowed_servers = ArrayField(
 50        models.TextField(),
 51        default=list,
 52        blank=True,
 53        help_text=_(
 54            "Which servers a user has to be a member of to be granted access. "
 55            "Empty list allows every server."
 56        ),
 57    )
 58    allow_friends = models.BooleanField(
 59        default=True,
 60        help_text=_("Allow friends to authenticate, even if you don't share a server."),
 61    )
 62    plex_token = models.TextField(help_text=_("Plex token used to check friends"))
 63
 64    @property
 65    def component(self) -> str:
 66        return "ak-source-plex-form"
 67
 68    @property
 69    def serializer(self) -> type[BaseSerializer]:
 70        from authentik.sources.plex.api.source import PlexSourceSerializer
 71
 72        return PlexSourceSerializer
 73
 74    @property
 75    def property_mapping_type(self) -> type[PropertyMapping]:
 76        return PlexSourcePropertyMapping
 77
 78    @property
 79    def schedule_specs(self) -> list[ScheduleSpec]:
 80        from authentik.sources.plex.tasks import check_plex_token
 81
 82        return [
 83            ScheduleSpec(
 84                actor=check_plex_token,
 85                uid=self.slug,
 86                args=(self.pk,),
 87                crontab=f"{fqdn_rand(self.pk)} */3 * * *",
 88            ),
 89        ]
 90
 91    def get_base_user_properties(self, info: dict[str, Any], **kwargs):
 92        return {
 93            "username": info.get("username"),
 94            "email": info.get("email"),
 95            "name": info.get("title"),
 96        }
 97
 98    def get_base_group_properties(self, group_id: str, **kwargs):
 99        return {
100            "name": group_id,
101        }
102
103    @property
104    def icon_url(self) -> str:
105        icon = super().icon_url
106        if not icon:
107            icon = static("authentik/sources/plex.svg")
108        return icon
109
110    def ui_login_button(self, request: HttpRequest) -> UILoginButton:
111        return UILoginButton(
112            challenge=PlexAuthenticationChallenge(
113                data={
114                    "component": "ak-source-plex",
115                    "client_id": self.client_id,
116                    "slug": self.slug,
117                }
118            ),
119            icon_url=self.icon_url,
120            name=self.name,
121            promoted=self.promoted,
122        )
123
124    def ui_user_settings(self) -> UserSettingSerializer | None:
125        return UserSettingSerializer(
126            data={
127                "title": self.name,
128                "component": "ak-user-settings-source-plex",
129                "configure_url": self.client_id,
130                "icon_url": self.icon_url,
131            }
132        )
133
134    class Meta:
135        verbose_name = _("Plex Source")
136        verbose_name_plural = _("Plex Sources")
137
138
139class PlexSourcePropertyMapping(PropertyMapping):
140    """Map Plex properties to User of Group object attributes"""
141
142    @property
143    def component(self) -> str:
144        return "ak-property-mapping-source-plex-form"
145
146    @property
147    def serializer(self) -> type[Serializer]:
148        from authentik.sources.plex.api.property_mappings import PlexSourcePropertyMappingSerializer
149
150        return PlexSourcePropertyMappingSerializer
151
152    class Meta:
153        verbose_name = _("Plex Source Property Mapping")
154        verbose_name_plural = _("Plex Source Property Mappings")
155
156
157class UserPlexSourceConnection(UserSourceConnection):
158    """Connect user and plex source"""
159
160    plex_token = models.TextField()
161
162    @property
163    def serializer(self) -> type[Serializer]:
164        from authentik.sources.plex.api.source_connection import UserPlexSourceConnectionSerializer
165
166        return UserPlexSourceConnectionSerializer
167
168    class Meta:
169        verbose_name = _("User Plex Source Connection")
170        verbose_name_plural = _("User Plex Source Connections")
171
172
173class GroupPlexSourceConnection(GroupSourceConnection):
174    """Group-source connection"""
175
176    @property
177    def serializer(self) -> type[Serializer]:
178        from authentik.sources.plex.api.source_connection import (
179            GroupPlexSourceConnectionSerializer,
180        )
181
182        return GroupPlexSourceConnectionSerializer
183
184    class Meta:
185        verbose_name = _("Group Plex Source Connection")
186        verbose_name_plural = _("Group Plex Source Connections")
29class PlexAuthenticationChallenge(LoginChallengeMixin, Challenge):
30    """Challenge shown to the user in identification stage"""
31
32    client_id = CharField()
33    slug = CharField()
34    component = CharField(default="ak-source-plex")

Challenge shown to the user in identification stage

client_id
slug
component
class PlexAuthenticationChallengeResponse(authentik.flows.challenge.ChallengeResponse):
37class PlexAuthenticationChallengeResponse(ChallengeResponse):
38    """Pseudo class for plex response"""
39
40    component = CharField(default="ak-source-plex")

Pseudo class for plex response

component
 43class PlexSource(ScheduledModel, Source):
 44    """Authenticate against plex.tv"""
 45
 46    client_id = models.TextField(
 47        default=generate_id,
 48        help_text=_("Client identifier used to talk to Plex."),
 49    )
 50    allowed_servers = ArrayField(
 51        models.TextField(),
 52        default=list,
 53        blank=True,
 54        help_text=_(
 55            "Which servers a user has to be a member of to be granted access. "
 56            "Empty list allows every server."
 57        ),
 58    )
 59    allow_friends = models.BooleanField(
 60        default=True,
 61        help_text=_("Allow friends to authenticate, even if you don't share a server."),
 62    )
 63    plex_token = models.TextField(help_text=_("Plex token used to check friends"))
 64
 65    @property
 66    def component(self) -> str:
 67        return "ak-source-plex-form"
 68
 69    @property
 70    def serializer(self) -> type[BaseSerializer]:
 71        from authentik.sources.plex.api.source import PlexSourceSerializer
 72
 73        return PlexSourceSerializer
 74
 75    @property
 76    def property_mapping_type(self) -> type[PropertyMapping]:
 77        return PlexSourcePropertyMapping
 78
 79    @property
 80    def schedule_specs(self) -> list[ScheduleSpec]:
 81        from authentik.sources.plex.tasks import check_plex_token
 82
 83        return [
 84            ScheduleSpec(
 85                actor=check_plex_token,
 86                uid=self.slug,
 87                args=(self.pk,),
 88                crontab=f"{fqdn_rand(self.pk)} */3 * * *",
 89            ),
 90        ]
 91
 92    def get_base_user_properties(self, info: dict[str, Any], **kwargs):
 93        return {
 94            "username": info.get("username"),
 95            "email": info.get("email"),
 96            "name": info.get("title"),
 97        }
 98
 99    def get_base_group_properties(self, group_id: str, **kwargs):
100        return {
101            "name": group_id,
102        }
103
104    @property
105    def icon_url(self) -> str:
106        icon = super().icon_url
107        if not icon:
108            icon = static("authentik/sources/plex.svg")
109        return icon
110
111    def ui_login_button(self, request: HttpRequest) -> UILoginButton:
112        return UILoginButton(
113            challenge=PlexAuthenticationChallenge(
114                data={
115                    "component": "ak-source-plex",
116                    "client_id": self.client_id,
117                    "slug": self.slug,
118                }
119            ),
120            icon_url=self.icon_url,
121            name=self.name,
122            promoted=self.promoted,
123        )
124
125    def ui_user_settings(self) -> UserSettingSerializer | None:
126        return UserSettingSerializer(
127            data={
128                "title": self.name,
129                "component": "ak-user-settings-source-plex",
130                "configure_url": self.client_id,
131                "icon_url": self.icon_url,
132            }
133        )
134
135    class Meta:
136        verbose_name = _("Plex Source")
137        verbose_name_plural = _("Plex Sources")

Authenticate against plex.tv

def client_id(unknown):

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

def allowed_servers(unknown):

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

def allow_friends(unknown):

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

def plex_token(unknown):

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

component: str
65    @property
66    def component(self) -> str:
67        return "ak-source-plex-form"

Return component used to edit this object

serializer: type[rest_framework.serializers.BaseSerializer]
69    @property
70    def serializer(self) -> type[BaseSerializer]:
71        from authentik.sources.plex.api.source import PlexSourceSerializer
72
73        return PlexSourceSerializer

Get serializer for this model

property_mapping_type: type[authentik.core.models.PropertyMapping]
75    @property
76    def property_mapping_type(self) -> type[PropertyMapping]:
77        return PlexSourcePropertyMapping

Return property mapping type used by this object

schedule_specs: list[authentik.tasks.schedules.common.ScheduleSpec]
79    @property
80    def schedule_specs(self) -> list[ScheduleSpec]:
81        from authentik.sources.plex.tasks import check_plex_token
82
83        return [
84            ScheduleSpec(
85                actor=check_plex_token,
86                uid=self.slug,
87                args=(self.pk,),
88                crontab=f"{fqdn_rand(self.pk)} */3 * * *",
89            ),
90        ]
def get_base_user_properties(self, info: dict[str, typing.Any], **kwargs):
92    def get_base_user_properties(self, info: dict[str, Any], **kwargs):
93        return {
94            "username": info.get("username"),
95            "email": info.get("email"),
96            "name": info.get("title"),
97        }

Get base properties for a user to build final properties upon.

def get_base_group_properties(self, group_id: str, **kwargs):
 99    def get_base_group_properties(self, group_id: str, **kwargs):
100        return {
101            "name": group_id,
102        }

Get base properties for a group to build final properties upon.

icon_url: str
104    @property
105    def icon_url(self) -> str:
106        icon = super().icon_url
107        if not icon:
108            icon = static("authentik/sources/plex.svg")
109        return icon

Get the URL to the source icon

def ui_login_button( self, request: django.http.request.HttpRequest) -> authentik.core.types.UILoginButton:
111    def ui_login_button(self, request: HttpRequest) -> UILoginButton:
112        return UILoginButton(
113            challenge=PlexAuthenticationChallenge(
114                data={
115                    "component": "ak-source-plex",
116                    "client_id": self.client_id,
117                    "slug": self.slug,
118                }
119            ),
120            icon_url=self.icon_url,
121            name=self.name,
122            promoted=self.promoted,
123        )

If source uses a http-based flow, return UI Information about the login button. If source doesn't use http-based flow, return None.

def ui_user_settings(self) -> authentik.core.types.UserSettingSerializer | None:
125    def ui_user_settings(self) -> UserSettingSerializer | None:
126        return UserSettingSerializer(
127            data={
128                "title": self.name,
129                "component": "ak-user-settings-source-plex",
130                "configure_url": self.client_id,
131                "icon_url": self.icon_url,
132            }
133        )

Entrypoint to integrate with User settings. Can either return None if no user settings are available, or UserSettingSerializer.

schedules

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.

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.

source_ptr_id
source_ptr

Accessor to the related object on the forward side of a one-to-one relation.

In the example::

class Restaurant(Model):
    place = OneToOneField(Place, related_name='restaurant')

Restaurant.place is a ForwardOneToOneDescriptor instance.

class PlexSource.DoesNotExist(authentik.core.models.Source.DoesNotExist):

The requested object does not exist

class PlexSource.MultipleObjectsReturned(authentik.core.models.Source.MultipleObjectsReturned):

The query returned multiple objects when only one was expected.

class PlexSourcePropertyMapping(authentik.core.models.PropertyMapping):
140class PlexSourcePropertyMapping(PropertyMapping):
141    """Map Plex properties to User of Group object attributes"""
142
143    @property
144    def component(self) -> str:
145        return "ak-property-mapping-source-plex-form"
146
147    @property
148    def serializer(self) -> type[Serializer]:
149        from authentik.sources.plex.api.property_mappings import PlexSourcePropertyMappingSerializer
150
151        return PlexSourcePropertyMappingSerializer
152
153    class Meta:
154        verbose_name = _("Plex Source Property Mapping")
155        verbose_name_plural = _("Plex Source Property Mappings")

Map Plex properties to User of Group object attributes

component: str
143    @property
144    def component(self) -> str:
145        return "ak-property-mapping-source-plex-form"

Return component used to edit this object

serializer: type[rest_framework.serializers.Serializer]
147    @property
148    def serializer(self) -> type[Serializer]:
149        from authentik.sources.plex.api.property_mappings import PlexSourcePropertyMappingSerializer
150
151        return PlexSourcePropertyMappingSerializer

Get serializer for this model

propertymapping_ptr_id
propertymapping_ptr

Accessor to the related object on the forward side of a one-to-one relation.

In the example::

class Restaurant(Model):
    place = OneToOneField(Place, related_name='restaurant')

Restaurant.place is a ForwardOneToOneDescriptor instance.

class PlexSourcePropertyMapping.DoesNotExist(authentik.core.models.PropertyMapping.DoesNotExist):

The requested object does not exist

class PlexSourcePropertyMapping.MultipleObjectsReturned(authentik.core.models.PropertyMapping.MultipleObjectsReturned):

The query returned multiple objects when only one was expected.

class UserPlexSourceConnection(authentik.core.models.UserSourceConnection):
158class UserPlexSourceConnection(UserSourceConnection):
159    """Connect user and plex source"""
160
161    plex_token = models.TextField()
162
163    @property
164    def serializer(self) -> type[Serializer]:
165        from authentik.sources.plex.api.source_connection import UserPlexSourceConnectionSerializer
166
167        return UserPlexSourceConnectionSerializer
168
169    class Meta:
170        verbose_name = _("User Plex Source Connection")
171        verbose_name_plural = _("User Plex Source Connections")

Connect user and plex source

def plex_token(unknown):

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

serializer: type[rest_framework.serializers.Serializer]
163    @property
164    def serializer(self) -> type[Serializer]:
165        from authentik.sources.plex.api.source_connection import UserPlexSourceConnectionSerializer
166
167        return UserPlexSourceConnectionSerializer

Get serializer for this model

usersourceconnection_ptr_id
usersourceconnection_ptr

Accessor to the related object on the forward side of a one-to-one relation.

In the example::

class Restaurant(Model):
    place = OneToOneField(Place, related_name='restaurant')

Restaurant.place is a ForwardOneToOneDescriptor instance.

class UserPlexSourceConnection.DoesNotExist(authentik.core.models.UserSourceConnection.DoesNotExist):

The requested object does not exist

class UserPlexSourceConnection.MultipleObjectsReturned(authentik.core.models.UserSourceConnection.MultipleObjectsReturned):

The query returned multiple objects when only one was expected.

class GroupPlexSourceConnection(authentik.core.models.GroupSourceConnection):
174class GroupPlexSourceConnection(GroupSourceConnection):
175    """Group-source connection"""
176
177    @property
178    def serializer(self) -> type[Serializer]:
179        from authentik.sources.plex.api.source_connection import (
180            GroupPlexSourceConnectionSerializer,
181        )
182
183        return GroupPlexSourceConnectionSerializer
184
185    class Meta:
186        verbose_name = _("Group Plex Source Connection")
187        verbose_name_plural = _("Group Plex Source Connections")

Group-source connection

serializer: type[rest_framework.serializers.Serializer]
177    @property
178    def serializer(self) -> type[Serializer]:
179        from authentik.sources.plex.api.source_connection import (
180            GroupPlexSourceConnectionSerializer,
181        )
182
183        return GroupPlexSourceConnectionSerializer

Get serializer for this model

groupsourceconnection_ptr_id
groupsourceconnection_ptr

Accessor to the related object on the forward side of a one-to-one relation.

In the example::

class Restaurant(Model):
    place = OneToOneField(Place, related_name='restaurant')

Restaurant.place is a ForwardOneToOneDescriptor instance.

class GroupPlexSourceConnection.DoesNotExist(authentik.core.models.GroupSourceConnection.DoesNotExist):

The requested object does not exist

class GroupPlexSourceConnection.MultipleObjectsReturned(authentik.core.models.GroupSourceConnection.MultipleObjectsReturned):

The query returned multiple objects when only one was expected.