Compare commits
	
		
			4 Commits
		
	
	
		
			docusaurus
			...
			api/nested
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 8128d8dab5 | |||
| f4a68c7878 | |||
| 7ab17822e3 | |||
| 76da77f26e | 
							
								
								
									
										67
									
								
								authentik/api/v3/routers.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										67
									
								
								authentik/api/v3/routers.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,67 @@ | ||||
| from rest_framework.routers import DefaultRouter as UpstreamDefaultRouter | ||||
| from rest_framework.viewsets import ViewSet | ||||
| from rest_framework_nested.routers import NestedMixin | ||||
|  | ||||
|  | ||||
| class DefaultRouter(UpstreamDefaultRouter): | ||||
|     include_format_suffixes = False | ||||
|  | ||||
|  | ||||
| class NestedRouter(DefaultRouter): | ||||
|  | ||||
|     def __init__(self, *args, **kwargs): | ||||
|         super().__init__(*args, **kwargs) | ||||
|         self.nested_routers = [] | ||||
|  | ||||
|     class nested: | ||||
|         def __init__(self, parent: "NestedRouter", prefix: str): | ||||
|             self.parent = parent | ||||
|             self.prefix = prefix | ||||
|             self.inner = None | ||||
|  | ||||
|         def nested(self, lookup: str, prefix: str, viewset: type[ViewSet]): | ||||
|             if not self.inner: | ||||
|                 self.inner = NestedDefaultRouter(self.parent, self.prefix, lookup=lookup) | ||||
|             self.inner.register(prefix, viewset) | ||||
|             return self | ||||
|  | ||||
|         @property | ||||
|         def urls(self): | ||||
|             return self.parent.urls | ||||
|  | ||||
|     def register(self, prefix, viewset, basename=None): | ||||
|         super().register(prefix, viewset, basename) | ||||
|         nested_router = self.nested(self, prefix) | ||||
|         self.nested_routers.append(nested_router) | ||||
|         return nested_router | ||||
|  | ||||
|     def get_urls(self): | ||||
|         urls = super().get_urls() | ||||
|         for nested in self.nested_routers: | ||||
|             if not nested.inner: | ||||
|                 continue | ||||
|             urls.extend(nested.inner.urls) | ||||
|         return urls | ||||
|  | ||||
|  | ||||
| class NestedDefaultRouter(NestedMixin, DefaultRouter): | ||||
|     ... | ||||
|     # def __init__(self, *args, **kwargs): | ||||
|     #     self.args = args | ||||
|     #     self.kwargs = kwargs | ||||
|     #     self.routes = [] | ||||
|  | ||||
|     # def register(self, *args, **kwargs): | ||||
|     #     self.routes.append((args, kwargs)) | ||||
|  | ||||
|     # @property | ||||
|     # def urls(self): | ||||
|     #     class r(NestedMixin, DefaultRouter): | ||||
|     #         ... | ||||
|     #     router = r(*self.args, **self.kwargs) | ||||
|     #     for route_args, route_kwrags in self.routes: | ||||
|     #         router.register(*route_args, **route_kwrags) | ||||
|     #     return router | ||||
|  | ||||
|  | ||||
| root_router = DefaultRouter() | ||||
| @ -6,18 +6,15 @@ from django.urls import path | ||||
| from django.urls.resolvers import URLPattern | ||||
| from django.views.decorators.cache import cache_page | ||||
| from drf_spectacular.views import SpectacularAPIView | ||||
| from rest_framework import routers | ||||
| from structlog.stdlib import get_logger | ||||
|  | ||||
| from authentik.api.v3.config import ConfigView | ||||
| from authentik.api.v3.routers import root_router | ||||
| from authentik.api.views import APIBrowserView | ||||
| from authentik.lib.utils.reflection import get_apps | ||||
|  | ||||
| LOGGER = get_logger() | ||||
|  | ||||
| router = routers.DefaultRouter() | ||||
| router.include_format_suffixes = False | ||||
|  | ||||
| _other_urls = [] | ||||
| for _authentik_app in get_apps(): | ||||
|     try: | ||||
| @ -38,7 +35,7 @@ for _authentik_app in get_apps(): | ||||
|         if isinstance(url, URLPattern): | ||||
|             _other_urls.append(url) | ||||
|         else: | ||||
|             router.register(*url) | ||||
|             root_router.register(*url) | ||||
|     LOGGER.debug( | ||||
|         "Mounted API URLs", | ||||
|         app_name=_authentik_app.name, | ||||
| @ -49,7 +46,7 @@ urlpatterns = ( | ||||
|     [ | ||||
|         path("", APIBrowserView.as_view(), name="schema-browser"), | ||||
|     ] | ||||
|     + router.urls | ||||
|     + root_router.urls | ||||
|     + _other_urls | ||||
|     + [ | ||||
|         path("root/config/", ConfigView.as_view(), name="config"), | ||||
|  | ||||
| @ -6,7 +6,7 @@ from djangoql.ast import Name | ||||
| from djangoql.exceptions import DjangoQLError | ||||
| from djangoql.queryset import apply_search | ||||
| from djangoql.schema import DjangoQLSchema | ||||
| from rest_framework.filters import BaseFilterBackend, SearchFilter | ||||
| from rest_framework.filters import SearchFilter | ||||
| from rest_framework.request import Request | ||||
| from structlog.stdlib import get_logger | ||||
|  | ||||
| @ -39,7 +39,8 @@ class BaseSchema(DjangoQLSchema): | ||||
|         return super().resolve_name(name) | ||||
|  | ||||
|  | ||||
| class QLSearch(BaseFilterBackend): | ||||
| # Inherits from SearchFilter to keep the schema correctly | ||||
| class QLSearch(SearchFilter): | ||||
|     """rest_framework search filter which uses DjangoQL""" | ||||
|  | ||||
|     def __init__(self): | ||||
|  | ||||
| @ -40,9 +40,16 @@ class ConnectionTokenViewSet( | ||||
| ): | ||||
|     """ConnectionToken Viewset""" | ||||
|  | ||||
|     queryset = ConnectionToken.objects.all().select_related("session", "endpoint") | ||||
|     queryset = ConnectionToken.objects.none() | ||||
|     serializer_class = ConnectionTokenSerializer | ||||
|     filterset_fields = ["endpoint", "session__user", "provider"] | ||||
|     search_fields = ["endpoint__name", "provider__name"] | ||||
|     ordering = ["endpoint__name", "provider__name"] | ||||
|     filterset_fields = ["endpoint", "session__user"] | ||||
|     search_fields = ["endpoint__name", "session__user__username"] | ||||
|     ordering = ["endpoint__name", "session__user__username"] | ||||
|     owner_field = "session__user" | ||||
|  | ||||
|     def get_queryset(self): | ||||
|         return ( | ||||
|             ConnectionToken.objects.all() | ||||
|             .select_related("session", "endpoint") | ||||
|             .filter(provider=self.kwargs["provider_pk"]) | ||||
|         ) | ||||
|  | ||||
| @ -22,9 +22,9 @@ from authentik.rbac.filters import ObjectFilter | ||||
| LOGGER = get_logger() | ||||
|  | ||||
|  | ||||
| def user_endpoint_cache_key(user_pk: str) -> str: | ||||
| def user_endpoint_cache_key(user_pk: str, provider_pk: str) -> str: | ||||
|     """Cache key where endpoint list for user is saved""" | ||||
|     return f"goauthentik.io/providers/rac/endpoint_access/{user_pk}" | ||||
|     return f"goauthentik.io/providers/rac/endpoint_access/{user_pk}/{provider_pk}" | ||||
|  | ||||
|  | ||||
| class EndpointSerializer(ModelSerializer): | ||||
| @ -65,12 +65,15 @@ class EndpointSerializer(ModelSerializer): | ||||
| class EndpointViewSet(UsedByMixin, ModelViewSet): | ||||
|     """Endpoint Viewset""" | ||||
|  | ||||
|     queryset = Endpoint.objects.all() | ||||
|     queryset = Endpoint.objects.none() | ||||
|     serializer_class = EndpointSerializer | ||||
|     filterset_fields = ["name", "provider"] | ||||
|     filterset_fields = ["name"] | ||||
|     search_fields = ["name", "protocol"] | ||||
|     ordering = ["name", "protocol"] | ||||
|  | ||||
|     def get_queryset(self): | ||||
|         return Endpoint.objects.filter(provider=self.kwargs["provider_pk"]) | ||||
|  | ||||
|     def _filter_queryset_for_list(self, queryset: QuerySet) -> QuerySet: | ||||
|         """Custom filter_queryset method which ignores guardian, but still supports sorting""" | ||||
|         for backend in list(self.filter_backends): | ||||
| @ -120,14 +123,11 @@ class EndpointViewSet(UsedByMixin, ModelViewSet): | ||||
|         if not should_cache: | ||||
|             allowed_endpoints = self._get_allowed_endpoints(queryset) | ||||
|         if should_cache: | ||||
|             allowed_endpoints = cache.get(user_endpoint_cache_key(self.request.user.pk)) | ||||
|             key = user_endpoint_cache_key(self.request.user.pk, self.kwargs["provider_pk"]) | ||||
|             allowed_endpoints = cache.get(key) | ||||
|             if not allowed_endpoints: | ||||
|                 LOGGER.debug("Caching allowed endpoint list") | ||||
|                 allowed_endpoints = self._get_allowed_endpoints(queryset) | ||||
|                 cache.set( | ||||
|                     user_endpoint_cache_key(self.request.user.pk), | ||||
|                     allowed_endpoints, | ||||
|                     timeout=86400, | ||||
|                 ) | ||||
|                 cache.set(key, allowed_endpoints, timeout=86400) | ||||
|         serializer = self.get_serializer(allowed_endpoints, many=True) | ||||
|         return self.get_paginated_response(serializer.data) | ||||
|  | ||||
| @ -43,5 +43,5 @@ def pre_delete_connection_token_disconnect(sender, instance: ConnectionToken, ** | ||||
| @receiver([post_save, post_delete], sender=Endpoint) | ||||
| def post_save_post_delete_endpoint(**_): | ||||
|     """Clear user's endpoint cache upon endpoint creation or deletion""" | ||||
|     keys = cache.keys(user_endpoint_cache_key("*")) | ||||
|     keys = cache.keys(user_endpoint_cache_key("*", "*")) | ||||
|     cache.delete_many(keys) | ||||
|  | ||||
| @ -2,6 +2,7 @@ | ||||
|  | ||||
| from django.urls import path | ||||
|  | ||||
| from authentik.api.v3.routers import NestedRouter | ||||
| from authentik.outposts.channels import TokenOutpostMiddleware | ||||
| from authentik.providers.rac.api.connection_tokens import ConnectionTokenViewSet | ||||
| from authentik.providers.rac.api.endpoints import EndpointViewSet | ||||
| @ -38,8 +39,10 @@ websocket_urlpatterns = [ | ||||
| ] | ||||
|  | ||||
| api_urlpatterns = [ | ||||
|     ("providers/rac", RACProviderViewSet), | ||||
|     *NestedRouter() | ||||
|     .register("providers/rac", RACProviderViewSet) | ||||
|     .nested("provider", "endpoints", EndpointViewSet) | ||||
|     .nested("provider", "connection_tokens", ConnectionTokenViewSet) | ||||
|     .urls, | ||||
|     ("propertymappings/provider/rac", RACPropertyMappingViewSet), | ||||
|     ("rac/endpoints", EndpointViewSet), | ||||
|     ("rac/connection_tokens", ConnectionTokenViewSet), | ||||
| ] | ||||
|  | ||||
| @ -28,6 +28,7 @@ dependencies = [ | ||||
|     "djangorestframework-guardian==0.3.0", | ||||
|     "djangorestframework==3.16.0", | ||||
|     "docker==7.1.0", | ||||
|     "drf-nested-routers==0.94.2", | ||||
|     "drf-orjson-renderer==1.7.3", | ||||
|     "drf-spectacular==0.28.0", | ||||
|     "dumb-init==1.2.5.post1", | ||||
|  | ||||
							
								
								
									
										1092
									
								
								schema.yml
									
									
									
									
									
								
							
							
						
						
									
										1092
									
								
								schema.yml
									
									
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										15
									
								
								uv.lock
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										15
									
								
								uv.lock
									
									
									
										generated
									
									
									
								
							| @ -191,6 +191,7 @@ dependencies = [ | ||||
|     { name = "djangorestframework" }, | ||||
|     { name = "djangorestframework-guardian" }, | ||||
|     { name = "docker" }, | ||||
|     { name = "drf-nested-routers" }, | ||||
|     { name = "drf-orjson-renderer" }, | ||||
|     { name = "drf-spectacular" }, | ||||
|     { name = "dumb-init" }, | ||||
| @ -290,6 +291,7 @@ requires-dist = [ | ||||
|     { name = "djangorestframework", git = "https://github.com/goauthentik/django-rest-framework?rev=896722bab969fabc74a08b827da59409cf9f1a4e" }, | ||||
|     { name = "djangorestframework-guardian", specifier = "==0.3.0" }, | ||||
|     { name = "docker", specifier = "==7.1.0" }, | ||||
|     { name = "drf-nested-routers", specifier = "==0.94.2" }, | ||||
|     { name = "drf-orjson-renderer", specifier = "==1.7.3" }, | ||||
|     { name = "drf-spectacular", specifier = "==0.28.0" }, | ||||
|     { name = "dumb-init", specifier = "==1.2.5.post1" }, | ||||
| @ -1190,6 +1192,19 @@ wheels = [ | ||||
|     { url = "https://files.pythonhosted.org/packages/2f/71/1f500097efe09e04c3be862ab26c997314237a8b0a16dc3e3047fee23f4c/drf_jsonschema_serializer-3.0.0-py3-none-any.whl", hash = "sha256:d0e5cce095a5638b0bb7867aa060ed59ab9eed2f54ba5058dd9b483c9c887ed5", size = 8994, upload-time = "2024-06-26T13:09:59.929Z" }, | ||||
| ] | ||||
|  | ||||
| [[package]] | ||||
| name = "drf-nested-routers" | ||||
| version = "0.94.2" | ||||
| source = { registry = "https://pypi.org/simple" } | ||||
| dependencies = [ | ||||
|     { name = "django" }, | ||||
|     { name = "djangorestframework" }, | ||||
| ] | ||||
| sdist = { url = "https://files.pythonhosted.org/packages/f6/98/2d29f3ecd337255bc2775b9addef347b6fd30ff7b3757649d0e50602ba08/drf_nested_routers-0.94.2.tar.gz", hash = "sha256:aa70923b716dc47cd93b8129b06be6c15706b405cf5f718f59cb8eed01de59cc", size = 22845, upload-time = "2025-05-14T17:03:50.896Z" } | ||||
| wheels = [ | ||||
|     { url = "https://files.pythonhosted.org/packages/62/dc/6bdb857a631fe6558db18a009c93ae16c3ad94fef0b7be7a3aa35c3264fa/drf_nested_routers-0.94.2-py2.py3-none-any.whl", hash = "sha256:74dbdceeae2a32f8668ba0df8e3eeabeb9b1c64d2621d914901ae653e4e3bcff", size = 36367, upload-time = "2025-05-14T17:03:49.257Z" }, | ||||
| ] | ||||
|  | ||||
| [[package]] | ||||
| name = "drf-orjson-renderer" | ||||
| version = "1.7.3" | ||||
|  | ||||
| @ -12,7 +12,7 @@ import { customElement, property } from "lit/decorators.js"; | ||||
|  | ||||
| import PFDescriptionList from "@patternfly/patternfly/components/DescriptionList/description-list.css"; | ||||
|  | ||||
| import { ConnectionToken, RACProvider, RacApi } from "@goauthentik/api"; | ||||
| import { ConnectionToken, ProvidersApi, RACProvider } from "@goauthentik/api"; | ||||
|  | ||||
| @customElement("ak-rac-connection-token-list") | ||||
| export class ConnectionTokenListPage extends Table<ConnectionToken> { | ||||
| @ -37,9 +37,9 @@ export class ConnectionTokenListPage extends Table<ConnectionToken> { | ||||
|     } | ||||
|  | ||||
|     async apiEndpoint(): Promise<PaginatedResponse<ConnectionToken>> { | ||||
|         return new RacApi(DEFAULT_CONFIG).racConnectionTokensList({ | ||||
|         return new ProvidersApi(DEFAULT_CONFIG).providersRacConnectionTokensList({ | ||||
|             ...(await this.defaultEndpointConfig()), | ||||
|             provider: this.provider?.pk, | ||||
|             providerPk: this.provider!.pk, | ||||
|             sessionUser: this.userId, | ||||
|         }); | ||||
|     } | ||||
| @ -56,12 +56,14 @@ export class ConnectionTokenListPage extends Table<ConnectionToken> { | ||||
|                 ]; | ||||
|             }} | ||||
|             .usedBy=${(item: ConnectionToken) => { | ||||
|                 return new RacApi(DEFAULT_CONFIG).racConnectionTokensUsedByList({ | ||||
|                 return new ProvidersApi(DEFAULT_CONFIG).providersRacConnectionTokensUsedByList({ | ||||
|                     providerPk: this.provider!.pk, | ||||
|                     connectionTokenUuid: item.pk || "", | ||||
|                 }); | ||||
|             }} | ||||
|             .delete=${(item: ConnectionToken) => { | ||||
|                 return new RacApi(DEFAULT_CONFIG).racConnectionTokensDestroy({ | ||||
|                 return new ProvidersApi(DEFAULT_CONFIG).providersRacConnectionTokensDestroy({ | ||||
|                     providerPk: this.provider!.pk, | ||||
|                     connectionTokenUuid: item.pk || "", | ||||
|                 }); | ||||
|             }} | ||||
|  | ||||
| @ -12,7 +12,7 @@ import { TemplateResult, html } from "lit"; | ||||
| import { customElement, property } from "lit/decorators.js"; | ||||
| import { ifDefined } from "lit/directives/if-defined.js"; | ||||
|  | ||||
| import { AuthModeEnum, Endpoint, ProtocolEnum, RacApi } from "@goauthentik/api"; | ||||
| import { AuthModeEnum, Endpoint, ProtocolEnum, ProvidersApi } from "@goauthentik/api"; | ||||
|  | ||||
| import { propertyMappingsProvider, propertyMappingsSelector } from "./RACProviderFormHelpers.js"; | ||||
|  | ||||
| @ -22,7 +22,8 @@ export class EndpointForm extends ModelForm<Endpoint, string> { | ||||
|     providerID?: number; | ||||
|  | ||||
|     loadInstance(pk: string): Promise<Endpoint> { | ||||
|         return new RacApi(DEFAULT_CONFIG).racEndpointsRetrieve({ | ||||
|         return new ProvidersApi(DEFAULT_CONFIG).providersRacEndpointsRetrieve({ | ||||
|             providerPk: this.providerID!, | ||||
|             pbmUuid: pk, | ||||
|         }); | ||||
|     } | ||||
| @ -41,12 +42,14 @@ export class EndpointForm extends ModelForm<Endpoint, string> { | ||||
|             data.provider = this.instance.provider; | ||||
|         } | ||||
|         if (this.instance) { | ||||
|             return new RacApi(DEFAULT_CONFIG).racEndpointsPartialUpdate({ | ||||
|             return new ProvidersApi(DEFAULT_CONFIG).providersRacEndpointsPartialUpdate({ | ||||
|                 providerPk: this.providerID!, | ||||
|                 pbmUuid: this.instance.pk || "", | ||||
|                 patchedEndpointRequest: data, | ||||
|             }); | ||||
|         } | ||||
|         return new RacApi(DEFAULT_CONFIG).racEndpointsCreate({ | ||||
|         return new ProvidersApi(DEFAULT_CONFIG).providersRacEndpointsCreate({ | ||||
|             providerPk: this.providerID!, | ||||
|             endpointRequest: data, | ||||
|         }); | ||||
|     } | ||||
|  | ||||
| @ -17,8 +17,8 @@ import PFDescriptionList from "@patternfly/patternfly/components/DescriptionList | ||||
|  | ||||
| import { | ||||
|     Endpoint, | ||||
|     ProvidersApi, | ||||
|     RACProvider, | ||||
|     RacApi, | ||||
|     RbacPermissionsAssignedByUsersListModelEnum, | ||||
| } from "@goauthentik/api"; | ||||
|  | ||||
| @ -43,9 +43,9 @@ export class EndpointListPage extends Table<Endpoint> { | ||||
|     } | ||||
|  | ||||
|     async apiEndpoint(): Promise<PaginatedResponse<Endpoint>> { | ||||
|         return new RacApi(DEFAULT_CONFIG).racEndpointsList({ | ||||
|         return new ProvidersApi(DEFAULT_CONFIG).providersRacEndpointsList({ | ||||
|             ...(await this.defaultEndpointConfig()), | ||||
|             provider: this.provider?.pk, | ||||
|             providerPk: this.provider!.pk, | ||||
|             superuserFullList: true, | ||||
|         }); | ||||
|     } | ||||
| @ -70,12 +70,14 @@ export class EndpointListPage extends Table<Endpoint> { | ||||
|                 ]; | ||||
|             }} | ||||
|             .usedBy=${(item: Endpoint) => { | ||||
|                 return new RacApi(DEFAULT_CONFIG).racEndpointsUsedByList({ | ||||
|                 return new ProvidersApi(DEFAULT_CONFIG).providersRacEndpointsUsedByList({ | ||||
|                     providerPk: this.provider!.pk, | ||||
|                     pbmUuid: item.pk, | ||||
|                 }); | ||||
|             }} | ||||
|             .delete=${(item: Endpoint) => { | ||||
|                 return new RacApi(DEFAULT_CONFIG).racEndpointsDestroy({ | ||||
|                 return new ProvidersApi(DEFAULT_CONFIG).providersRacEndpointsDestroy({ | ||||
|                     providerPk: this.provider!.pk, | ||||
|                     pbmUuid: item.pk, | ||||
|                 }); | ||||
|             }} | ||||
|  | ||||
| @ -6,7 +6,7 @@ import { msg } from "@lit/localize"; | ||||
| import { TemplateResult, html } from "lit"; | ||||
| import { customElement, property } from "lit/decorators.js"; | ||||
|  | ||||
| import { Application, Endpoint, RacApi } from "@goauthentik/api"; | ||||
| import { Application, Endpoint, ProvidersApi } from "@goauthentik/api"; | ||||
|  | ||||
| @customElement("ak-library-rac-endpoint-launch") | ||||
| export class RACLaunchEndpointModal extends TableModal<Endpoint> { | ||||
| @ -30,9 +30,9 @@ export class RACLaunchEndpointModal extends TableModal<Endpoint> { | ||||
|     app?: Application; | ||||
|  | ||||
|     async apiEndpoint(): Promise<PaginatedResponse<Endpoint>> { | ||||
|         const endpoints = await new RacApi(DEFAULT_CONFIG).racEndpointsList({ | ||||
|         const endpoints = await new ProvidersApi(DEFAULT_CONFIG).providersRacEndpointsList({ | ||||
|             ...(await this.defaultEndpointConfig()), | ||||
|             provider: this.app?.provider || 0, | ||||
|             providerPk: this.app?.provider || 0, | ||||
|         }); | ||||
|         if (this.open && endpoints.pagination.count === 1) { | ||||
|             this.clickHandler(endpoints.results[0]); | ||||
|  | ||||
		Reference in New Issue
	
	Block a user
	