authentik.providers.rac.models
RAC Models
1"""RAC Models""" 2 3from typing import Any 4from uuid import uuid4 5 6from deepmerge import always_merger 7from django.db import models 8from django.db.models import QuerySet 9from django.http import HttpRequest 10from django.templatetags.static import static 11from django.utils.translation import gettext as _ 12from rest_framework.serializers import Serializer 13from structlog.stdlib import get_logger 14 15from authentik.core.expression.exceptions import PropertyMappingExpressionException 16from authentik.core.models import ExpiringModel, PropertyMapping, Provider, User, default_token_key 17from authentik.events.models import Event, EventAction 18from authentik.lib.models import InternallyManagedMixin, SerializerModel 19from authentik.lib.utils.time import timedelta_string_validator 20from authentik.outposts.models import OutpostModel 21from authentik.policies.models import PolicyBindingModel 22 23LOGGER = get_logger() 24 25 26class Protocols(models.TextChoices): 27 """Supported protocols""" 28 29 RDP = "rdp" 30 VNC = "vnc" 31 SSH = "ssh" 32 33 34class AuthenticationMode(models.TextChoices): 35 """Authentication modes""" 36 37 STATIC = "static" 38 PROMPT = "prompt" 39 40 41class RACProvider(OutpostModel, Provider): 42 """Remotely access computers/servers via RDP/SSH/VNC.""" 43 44 settings = models.JSONField(default=dict) 45 auth_mode = models.TextField( 46 choices=AuthenticationMode.choices, default=AuthenticationMode.PROMPT 47 ) 48 connection_expiry = models.TextField( 49 default="hours=8", 50 validators=[timedelta_string_validator], 51 help_text=_( 52 "Determines how long a session lasts. Default of 0 means " 53 "that the sessions lasts until the browser is closed. " 54 "(Format: hours=-1;minutes=-2;seconds=-3)" 55 ), 56 ) 57 delete_token_on_disconnect = models.BooleanField( 58 default=False, 59 help_text=_("When set to true, connection tokens will be deleted upon disconnect."), 60 ) 61 62 @property 63 def launch_url(self) -> str | None: 64 """URL to this provider and initiate authorization for the user. 65 Can return None for providers that are not URL-based""" 66 return "goauthentik.io://providers/rac/launch" 67 68 @property 69 def icon_url(self) -> str | None: 70 return static("authentik/sources/rac.svg") 71 72 @property 73 def component(self) -> str: 74 return "ak-provider-rac-form" 75 76 @property 77 def serializer(self) -> type[Serializer]: 78 from authentik.providers.rac.api.providers import RACProviderSerializer 79 80 return RACProviderSerializer 81 82 class Meta: 83 verbose_name = _("RAC Provider") 84 verbose_name_plural = _("RAC Providers") 85 86 87class Endpoint(SerializerModel, PolicyBindingModel): 88 """Remote-accessible endpoint""" 89 90 name = models.TextField() 91 host = models.TextField() 92 protocol = models.TextField(choices=Protocols.choices) 93 settings = models.JSONField(default=dict) 94 auth_mode = models.TextField(choices=AuthenticationMode.choices) 95 provider = models.ForeignKey("RACProvider", on_delete=models.CASCADE) 96 maximum_connections = models.IntegerField(default=1) 97 98 property_mappings = models.ManyToManyField( 99 "authentik_core.PropertyMapping", default=None, blank=True 100 ) 101 102 @property 103 def serializer(self) -> type[Serializer]: 104 from authentik.providers.rac.api.endpoints import EndpointSerializer 105 106 return EndpointSerializer 107 108 def __str__(self): 109 return f"RAC Endpoint {self.name}" 110 111 class Meta: 112 verbose_name = _("RAC Endpoint") 113 verbose_name_plural = _("RAC Endpoints") 114 115 116class RACPropertyMapping(PropertyMapping): 117 """Configure settings for remote access endpoints.""" 118 119 static_settings = models.JSONField(default=dict) 120 121 def evaluate(self, user: User | None, request: HttpRequest | None, **kwargs) -> Any: 122 """Evaluate `self.expression` using `**kwargs` as Context.""" 123 settings = {} 124 for key, value in self.static_settings.items(): 125 if value and value != "": 126 settings[key] = value 127 if self.expression != "": 128 always_merger.merge(settings, super().evaluate(user, request, **kwargs)) 129 return settings 130 131 @property 132 def component(self) -> str: 133 return "ak-property-mapping-provider-rac-form" 134 135 @property 136 def serializer(self) -> type[Serializer]: 137 from authentik.providers.rac.api.property_mappings import ( 138 RACPropertyMappingSerializer, 139 ) 140 141 return RACPropertyMappingSerializer 142 143 class Meta: 144 verbose_name = _("RAC Provider Property Mapping") 145 verbose_name_plural = _("RAC Provider Property Mappings") 146 147 148class ConnectionToken(InternallyManagedMixin, ExpiringModel): 149 """Token for a single connection to a specified endpoint""" 150 151 connection_token_uuid = models.UUIDField(default=uuid4, primary_key=True) 152 provider = models.ForeignKey(RACProvider, on_delete=models.CASCADE) 153 endpoint = models.ForeignKey(Endpoint, on_delete=models.CASCADE) 154 token = models.TextField(default=default_token_key) 155 settings = models.JSONField(default=dict) 156 session = models.ForeignKey("authentik_core.AuthenticatedSession", on_delete=models.CASCADE) 157 158 def get_settings(self) -> dict: 159 """Get settings""" 160 default_settings = {} 161 if ":" in self.endpoint.host: 162 host, _, port = self.endpoint.host.partition(":") 163 default_settings["hostname"] = host 164 default_settings["port"] = str(port) 165 else: 166 default_settings["hostname"] = self.endpoint.host 167 if self.endpoint.protocol == Protocols.RDP: 168 default_settings["resize-method"] = "display-update" 169 default_settings["client-name"] = f"authentik - {self.session.user}" 170 settings = {} 171 always_merger.merge(settings, default_settings) 172 always_merger.merge(settings, self.endpoint.provider.settings) 173 always_merger.merge(settings, self.endpoint.settings) 174 175 def mapping_evaluator(mappings: QuerySet): 176 for mapping in mappings: 177 mapping: RACPropertyMapping 178 try: 179 mapping_settings = mapping.evaluate( 180 self.session.user, None, endpoint=self.endpoint, provider=self.provider 181 ) 182 always_merger.merge(settings, mapping_settings) 183 except PropertyMappingExpressionException as exc: 184 Event.new( 185 EventAction.CONFIGURATION_ERROR, 186 message=f"Failed to evaluate property-mapping: '{mapping.name}'", 187 provider=self.provider, 188 mapping=mapping, 189 ).set_user(self.session.user).save() 190 LOGGER.warning("Failed to evaluate property mapping", exc=exc) 191 192 mapping_evaluator( 193 RACPropertyMapping.objects.filter(provider__in=[self.provider]).order_by("name") 194 ) 195 mapping_evaluator( 196 RACPropertyMapping.objects.filter(endpoint__in=[self.endpoint]).order_by("name") 197 ) 198 always_merger.merge(settings, self.settings) 199 200 settings["drive-path"] = f"/tmp/connection/{self.token}" # nosec 201 settings["create-drive-path"] = "true" 202 # Ensure all values of the settings dict are strings 203 for key, value in settings.items(): 204 if isinstance(value, str): 205 continue 206 # Special case for bools 207 if isinstance(value, bool): 208 settings[key] = str(value).lower() 209 continue 210 settings[key] = str(value) 211 return settings 212 213 def __str__(self): 214 return f"RAC Connection token {self.session_id} to {self.provider_id}/{self.endpoint_id}" 215 216 class Meta: 217 verbose_name = _("RAC Connection token") 218 verbose_name_plural = _("RAC Connection tokens") 219 indexes = ExpiringModel.Meta.indexes
27class Protocols(models.TextChoices): 28 """Supported protocols""" 29 30 RDP = "rdp" 31 VNC = "vnc" 32 SSH = "ssh"
Supported protocols
35class AuthenticationMode(models.TextChoices): 36 """Authentication modes""" 37 38 STATIC = "static" 39 PROMPT = "prompt"
Authentication modes
42class RACProvider(OutpostModel, Provider): 43 """Remotely access computers/servers via RDP/SSH/VNC.""" 44 45 settings = models.JSONField(default=dict) 46 auth_mode = models.TextField( 47 choices=AuthenticationMode.choices, default=AuthenticationMode.PROMPT 48 ) 49 connection_expiry = models.TextField( 50 default="hours=8", 51 validators=[timedelta_string_validator], 52 help_text=_( 53 "Determines how long a session lasts. Default of 0 means " 54 "that the sessions lasts until the browser is closed. " 55 "(Format: hours=-1;minutes=-2;seconds=-3)" 56 ), 57 ) 58 delete_token_on_disconnect = models.BooleanField( 59 default=False, 60 help_text=_("When set to true, connection tokens will be deleted upon disconnect."), 61 ) 62 63 @property 64 def launch_url(self) -> str | None: 65 """URL to this provider and initiate authorization for the user. 66 Can return None for providers that are not URL-based""" 67 return "goauthentik.io://providers/rac/launch" 68 69 @property 70 def icon_url(self) -> str | None: 71 return static("authentik/sources/rac.svg") 72 73 @property 74 def component(self) -> str: 75 return "ak-provider-rac-form" 76 77 @property 78 def serializer(self) -> type[Serializer]: 79 from authentik.providers.rac.api.providers import RACProviderSerializer 80 81 return RACProviderSerializer 82 83 class Meta: 84 verbose_name = _("RAC Provider") 85 verbose_name_plural = _("RAC Providers")
Remotely access computers/servers via RDP/SSH/VNC.
A wrapper for a deferred-loading field. When the value is read from this object the first time, the query is executed.
A wrapper for a deferred-loading field. When the value is read from this object the first time, the query is executed.
A wrapper for a deferred-loading field. When the value is read from this object the first time, the query is executed.
A wrapper for a deferred-loading field. When the value is read from this object the first time, the query is executed.
63 @property 64 def launch_url(self) -> str | None: 65 """URL to this provider and initiate authorization for the user. 66 Can return None for providers that are not URL-based""" 67 return "goauthentik.io://providers/rac/launch"
URL to this provider and initiate authorization for the user. Can return None for providers that are not URL-based
77 @property 78 def serializer(self) -> type[Serializer]: 79 from authentik.providers.rac.api.providers import RACProviderSerializer 80 81 return RACProviderSerializer
Get serializer for this model
Method descriptor with partial application of the given arguments and keywords.
Supports wrapping existing descriptors and handles non-descriptor callables as instance methods.
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.
Accessor to the related objects manager on the reverse side of a many-to-one relation.
In the example::
class Child(Model):
parent = ForeignKey(Parent, related_name='children')
Parent.children is a ReverseManyToOneDescriptor instance.
Most of the implementation is delegated to a dynamically defined manager
class built by create_forward_many_to_many_manager() defined below.
Accessor to the related objects manager on the reverse side of a many-to-one relation.
In the example::
class Child(Model):
parent = ForeignKey(Parent, related_name='children')
Parent.children is a ReverseManyToOneDescriptor instance.
Most of the implementation is delegated to a dynamically defined manager
class built by create_forward_many_to_many_manager() defined below.
Inherited Members
- authentik.core.models.Provider
- name
- authentication_flow
- invalidation_flow
- property_mappings
- backchannel_application
- is_backchannel
- objects
- authentication_flow_id
- invalidation_flow_id
- backchannel_application_id
- id
- application
- outpost_set
- oauth2provider
- ldapprovider
- racprovider
- radiusprovider
- samlprovider
- scimprovider
- googleworkspaceprovider
- microsoftentraprovider
- ssfprovider
The requested object does not exist
The query returned multiple objects when only one was expected.
88class Endpoint(SerializerModel, PolicyBindingModel): 89 """Remote-accessible endpoint""" 90 91 name = models.TextField() 92 host = models.TextField() 93 protocol = models.TextField(choices=Protocols.choices) 94 settings = models.JSONField(default=dict) 95 auth_mode = models.TextField(choices=AuthenticationMode.choices) 96 provider = models.ForeignKey("RACProvider", on_delete=models.CASCADE) 97 maximum_connections = models.IntegerField(default=1) 98 99 property_mappings = models.ManyToManyField( 100 "authentik_core.PropertyMapping", default=None, blank=True 101 ) 102 103 @property 104 def serializer(self) -> type[Serializer]: 105 from authentik.providers.rac.api.endpoints import EndpointSerializer 106 107 return EndpointSerializer 108 109 def __str__(self): 110 return f"RAC Endpoint {self.name}" 111 112 class Meta: 113 verbose_name = _("RAC Endpoint") 114 verbose_name_plural = _("RAC Endpoints")
Remote-accessible endpoint
A wrapper for a deferred-loading field. When the value is read from this object the first time, the query is executed.
A wrapper for a deferred-loading field. When the value is read from this object the first time, the query is executed.
A wrapper for a deferred-loading field. When the value is read from this object the first time, the query is executed.
A wrapper for a deferred-loading field. When the value is read from this object the first time, the query is executed.
A wrapper for a deferred-loading field. When the value is read from this object the first time, the query is executed.
Accessor to the related object on the forward side of a many-to-one or one-to-one (via ForwardOneToOneDescriptor subclass) relation.
In the example::
class Child(Model):
parent = ForeignKey(Parent, related_name='children')
Child.parent is a ForwardManyToOneDescriptor instance.
A wrapper for a deferred-loading field. When the value is read from this object the first time, the query is executed.
Accessor to the related objects manager on the forward and reverse sides of a many-to-many relation.
In the example::
class Pizza(Model):
toppings = ManyToManyField(Topping, related_name='pizzas')
Pizza.toppings and Topping.pizzas are ManyToManyDescriptor
instances.
Most of the implementation is delegated to a dynamically defined manager
class built by create_forward_many_to_many_manager() defined below.
103 @property 104 def serializer(self) -> type[Serializer]: 105 from authentik.providers.rac.api.endpoints import EndpointSerializer 106 107 return EndpointSerializer
Get serializer for this model
Method descriptor with partial application of the given arguments and keywords.
Supports wrapping existing descriptors and handles non-descriptor callables as instance methods.
Method descriptor with partial application of the given arguments and keywords.
Supports wrapping existing descriptors and handles non-descriptor callables as instance methods.
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.
Accessor to the related objects manager on the reverse side of a many-to-one relation.
In the example::
class Child(Model):
parent = ForeignKey(Parent, related_name='children')
Parent.children is a ReverseManyToOneDescriptor instance.
Most of the implementation is delegated to a dynamically defined manager
class built by create_forward_many_to_many_manager() defined below.
Inherited Members
The requested object does not exist
The query returned multiple objects when only one was expected.
117class RACPropertyMapping(PropertyMapping): 118 """Configure settings for remote access endpoints.""" 119 120 static_settings = models.JSONField(default=dict) 121 122 def evaluate(self, user: User | None, request: HttpRequest | None, **kwargs) -> Any: 123 """Evaluate `self.expression` using `**kwargs` as Context.""" 124 settings = {} 125 for key, value in self.static_settings.items(): 126 if value and value != "": 127 settings[key] = value 128 if self.expression != "": 129 always_merger.merge(settings, super().evaluate(user, request, **kwargs)) 130 return settings 131 132 @property 133 def component(self) -> str: 134 return "ak-property-mapping-provider-rac-form" 135 136 @property 137 def serializer(self) -> type[Serializer]: 138 from authentik.providers.rac.api.property_mappings import ( 139 RACPropertyMappingSerializer, 140 ) 141 142 return RACPropertyMappingSerializer 143 144 class Meta: 145 verbose_name = _("RAC Provider Property Mapping") 146 verbose_name_plural = _("RAC Provider Property Mappings")
Configure settings for remote access endpoints.
A wrapper for a deferred-loading field. When the value is read from this object the first time, the query is executed.
122 def evaluate(self, user: User | None, request: HttpRequest | None, **kwargs) -> Any: 123 """Evaluate `self.expression` using `**kwargs` as Context.""" 124 settings = {} 125 for key, value in self.static_settings.items(): 126 if value and value != "": 127 settings[key] = value 128 if self.expression != "": 129 always_merger.merge(settings, super().evaluate(user, request, **kwargs)) 130 return settings
Evaluate self.expression using **kwargs as Context.
136 @property 137 def serializer(self) -> type[Serializer]: 138 from authentik.providers.rac.api.property_mappings import ( 139 RACPropertyMappingSerializer, 140 ) 141 142 return RACPropertyMappingSerializer
Get serializer for this model
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.
Inherited Members
- authentik.core.models.PropertyMapping
- pm_uuid
- name
- expression
- objects
- managed
- provider_set
- source_userpropertymappings_set
- source_grouppropertymappings_set
- notificationwebhookmapping
- oauthsourcepropertymapping
- scopemapping
- endpoint_set
- racpropertymapping
- radiusproviderpropertymapping
- samlsourcepropertymapping
- samlpropertymapping
- scimprovider_set
- scimmapping
- kerberossourcepropertymapping
- ldapsourcepropertymapping
- plexsourcepropertymapping
- scimsourcepropertymapping
- telegramsourcepropertymapping
- googleworkspaceprovider_set
- googleworkspaceprovidermapping
- microsoftentraprovider_set
- microsoftentraprovidermapping
The requested object does not exist
The query returned multiple objects when only one was expected.
149class ConnectionToken(InternallyManagedMixin, ExpiringModel): 150 """Token for a single connection to a specified endpoint""" 151 152 connection_token_uuid = models.UUIDField(default=uuid4, primary_key=True) 153 provider = models.ForeignKey(RACProvider, on_delete=models.CASCADE) 154 endpoint = models.ForeignKey(Endpoint, on_delete=models.CASCADE) 155 token = models.TextField(default=default_token_key) 156 settings = models.JSONField(default=dict) 157 session = models.ForeignKey("authentik_core.AuthenticatedSession", on_delete=models.CASCADE) 158 159 def get_settings(self) -> dict: 160 """Get settings""" 161 default_settings = {} 162 if ":" in self.endpoint.host: 163 host, _, port = self.endpoint.host.partition(":") 164 default_settings["hostname"] = host 165 default_settings["port"] = str(port) 166 else: 167 default_settings["hostname"] = self.endpoint.host 168 if self.endpoint.protocol == Protocols.RDP: 169 default_settings["resize-method"] = "display-update" 170 default_settings["client-name"] = f"authentik - {self.session.user}" 171 settings = {} 172 always_merger.merge(settings, default_settings) 173 always_merger.merge(settings, self.endpoint.provider.settings) 174 always_merger.merge(settings, self.endpoint.settings) 175 176 def mapping_evaluator(mappings: QuerySet): 177 for mapping in mappings: 178 mapping: RACPropertyMapping 179 try: 180 mapping_settings = mapping.evaluate( 181 self.session.user, None, endpoint=self.endpoint, provider=self.provider 182 ) 183 always_merger.merge(settings, mapping_settings) 184 except PropertyMappingExpressionException as exc: 185 Event.new( 186 EventAction.CONFIGURATION_ERROR, 187 message=f"Failed to evaluate property-mapping: '{mapping.name}'", 188 provider=self.provider, 189 mapping=mapping, 190 ).set_user(self.session.user).save() 191 LOGGER.warning("Failed to evaluate property mapping", exc=exc) 192 193 mapping_evaluator( 194 RACPropertyMapping.objects.filter(provider__in=[self.provider]).order_by("name") 195 ) 196 mapping_evaluator( 197 RACPropertyMapping.objects.filter(endpoint__in=[self.endpoint]).order_by("name") 198 ) 199 always_merger.merge(settings, self.settings) 200 201 settings["drive-path"] = f"/tmp/connection/{self.token}" # nosec 202 settings["create-drive-path"] = "true" 203 # Ensure all values of the settings dict are strings 204 for key, value in settings.items(): 205 if isinstance(value, str): 206 continue 207 # Special case for bools 208 if isinstance(value, bool): 209 settings[key] = str(value).lower() 210 continue 211 settings[key] = str(value) 212 return settings 213 214 def __str__(self): 215 return f"RAC Connection token {self.session_id} to {self.provider_id}/{self.endpoint_id}" 216 217 class Meta: 218 verbose_name = _("RAC Connection token") 219 verbose_name_plural = _("RAC Connection tokens") 220 indexes = ExpiringModel.Meta.indexes
Token for a single connection to a specified endpoint
A wrapper for a deferred-loading field. When the value is read from this object the first time, the query is executed.
Accessor to the related object on the forward side of a many-to-one or one-to-one (via ForwardOneToOneDescriptor subclass) relation.
In the example::
class Child(Model):
parent = ForeignKey(Parent, related_name='children')
Child.parent is a ForwardManyToOneDescriptor instance.
Accessor to the related object on the forward side of a many-to-one or one-to-one (via ForwardOneToOneDescriptor subclass) relation.
In the example::
class Child(Model):
parent = ForeignKey(Parent, related_name='children')
Child.parent is a ForwardManyToOneDescriptor instance.
A wrapper for a deferred-loading field. When the value is read from this object the first time, the query is executed.
A wrapper for a deferred-loading field. When the value is read from this object the first time, the query is executed.
Accessor to the related object on the forward side of a many-to-one or one-to-one (via ForwardOneToOneDescriptor subclass) relation.
In the example::
class Child(Model):
parent = ForeignKey(Parent, related_name='children')
Child.parent is a ForwardManyToOneDescriptor instance.
159 def get_settings(self) -> dict: 160 """Get settings""" 161 default_settings = {} 162 if ":" in self.endpoint.host: 163 host, _, port = self.endpoint.host.partition(":") 164 default_settings["hostname"] = host 165 default_settings["port"] = str(port) 166 else: 167 default_settings["hostname"] = self.endpoint.host 168 if self.endpoint.protocol == Protocols.RDP: 169 default_settings["resize-method"] = "display-update" 170 default_settings["client-name"] = f"authentik - {self.session.user}" 171 settings = {} 172 always_merger.merge(settings, default_settings) 173 always_merger.merge(settings, self.endpoint.provider.settings) 174 always_merger.merge(settings, self.endpoint.settings) 175 176 def mapping_evaluator(mappings: QuerySet): 177 for mapping in mappings: 178 mapping: RACPropertyMapping 179 try: 180 mapping_settings = mapping.evaluate( 181 self.session.user, None, endpoint=self.endpoint, provider=self.provider 182 ) 183 always_merger.merge(settings, mapping_settings) 184 except PropertyMappingExpressionException as exc: 185 Event.new( 186 EventAction.CONFIGURATION_ERROR, 187 message=f"Failed to evaluate property-mapping: '{mapping.name}'", 188 provider=self.provider, 189 mapping=mapping, 190 ).set_user(self.session.user).save() 191 LOGGER.warning("Failed to evaluate property mapping", exc=exc) 192 193 mapping_evaluator( 194 RACPropertyMapping.objects.filter(provider__in=[self.provider]).order_by("name") 195 ) 196 mapping_evaluator( 197 RACPropertyMapping.objects.filter(endpoint__in=[self.endpoint]).order_by("name") 198 ) 199 always_merger.merge(settings, self.settings) 200 201 settings["drive-path"] = f"/tmp/connection/{self.token}" # nosec 202 settings["create-drive-path"] = "true" 203 # Ensure all values of the settings dict are strings 204 for key, value in settings.items(): 205 if isinstance(value, str): 206 continue 207 # Special case for bools 208 if isinstance(value, bool): 209 settings[key] = str(value).lower() 210 continue 211 settings[key] = str(value) 212 return settings
Get settings
A wrapper for a deferred-loading field. When the value is read from this object the first time, the query is executed.
A wrapper for a deferred-loading field. When the value is read from this object the first time, the query is executed.
Inherited Members
The requested object does not exist
The query returned multiple objects when only one was expected.