stages: authenticator_endpoint_gdtc (#10477)

* rework

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* add loading overlay for chrome

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* start docs

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* Apply suggestions from code review

Co-authored-by: Tana M Berry <tanamarieberry@yahoo.com>
Signed-off-by: Jens L. <jens@beryju.org>

* save data

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* fix web ui, prevent deletion

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* fix

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* text fixes

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

---------

Signed-off-by: Jens Langhammer <jens@goauthentik.io>
Signed-off-by: Jens L. <jens@beryju.org>
Co-authored-by: Tana M Berry <tanamarieberry@yahoo.com>
This commit is contained in:
Jens L.
2024-10-22 22:46:46 +02:00
committed by GitHub
parent 0e4e7ccb4b
commit cec3fdb612
30 changed files with 1803 additions and 31 deletions

View File

@ -51,6 +51,10 @@ from authentik.enterprise.providers.microsoft_entra.models import (
MicrosoftEntraProviderUser,
)
from authentik.enterprise.providers.rac.models import ConnectionToken
from authentik.enterprise.stages.authenticator_endpoint_gdtc.models import (
EndpointDevice,
EndpointDeviceConnection,
)
from authentik.events.logs import LogEvent, capture_logs
from authentik.events.models import SystemTask
from authentik.events.utils import cleanse_dict
@ -119,6 +123,8 @@ def excluded_models() -> list[type[Model]]:
GoogleWorkspaceProviderGroup,
MicrosoftEntraProviderUser,
MicrosoftEntraProviderGroup,
EndpointDevice,
EndpointDeviceConnection,
)

View File

@ -6,7 +6,6 @@ from rest_framework.fields import (
BooleanField,
CharField,
DateTimeField,
IntegerField,
SerializerMethodField,
)
from rest_framework.permissions import IsAuthenticated
@ -15,6 +14,7 @@ from rest_framework.response import Response
from rest_framework.viewsets import ViewSet
from authentik.core.api.utils import MetaNameSerializer
from authentik.enterprise.stages.authenticator_endpoint_gdtc.models import EndpointDevice
from authentik.rbac.decorators import permission_required
from authentik.stages.authenticator import device_classes, devices_for_user
from authentik.stages.authenticator.models import Device
@ -24,7 +24,7 @@ from authentik.stages.authenticator_webauthn.models import WebAuthnDevice
class DeviceSerializer(MetaNameSerializer):
"""Serializer for Duo authenticator devices"""
pk = IntegerField()
pk = CharField()
name = CharField()
type = SerializerMethodField()
confirmed = BooleanField()
@ -41,6 +41,8 @@ class DeviceSerializer(MetaNameSerializer):
"""Get extra description"""
if isinstance(instance, WebAuthnDevice):
return instance.device_type.description
if isinstance(instance, EndpointDevice):
return instance.data.get("deviceSignals", {}).get("deviceModel")
return ""

View File

@ -4,6 +4,7 @@ import code
import platform
import sys
import traceback
from pprint import pprint
from django.apps import apps
from django.core.management.base import BaseCommand
@ -34,7 +35,9 @@ class Command(BaseCommand):
def get_namespace(self):
"""Prepare namespace with all models"""
namespace = {}
namespace = {
"pprint": pprint,
}
# Gather Django models and constants from each app
for app in apps.get_app_configs():

View File

@ -29,7 +29,7 @@ class TestDevicesAPI(APITestCase):
self.assertEqual(response.status_code, 200)
body = loads(response.content.decode())
self.assertEqual(len(body), 1)
self.assertEqual(body[0]["pk"], self.device1.pk)
self.assertEqual(body[0]["pk"], str(self.device1.pk))
def test_user_api_as_admin(self):
"""Test user API"""
@ -54,4 +54,6 @@ class TestDevicesAPI(APITestCase):
self.assertEqual(response.status_code, 200)
body = loads(response.content.decode())
self.assertEqual(len(body), 2)
self.assertEqual({body[0]["pk"], body[1]["pk"]}, {self.device1.pk, self.device2.pk})
self.assertEqual(
{body[0]["pk"], body[1]["pk"]}, {str(self.device1.pk), str(self.device2.pk)}
)

View File

@ -17,6 +17,7 @@ TENANT_APPS = [
"authentik.enterprise.providers.google_workspace",
"authentik.enterprise.providers.microsoft_entra",
"authentik.enterprise.providers.rac",
"authentik.enterprise.stages.authenticator_endpoint_gdtc",
"authentik.enterprise.stages.source",
]

View File

@ -0,0 +1,82 @@
"""AuthenticatorEndpointGDTCStage API Views"""
from django_filters.rest_framework.backends import DjangoFilterBackend
from rest_framework import mixins
from rest_framework.filters import OrderingFilter, SearchFilter
from rest_framework.permissions import IsAdminUser
from rest_framework.serializers import ModelSerializer
from rest_framework.viewsets import GenericViewSet, ModelViewSet
from structlog.stdlib import get_logger
from authentik.api.authorization import OwnerFilter, OwnerPermissions
from authentik.core.api.used_by import UsedByMixin
from authentik.enterprise.api import EnterpriseRequiredMixin
from authentik.enterprise.stages.authenticator_endpoint_gdtc.models import (
AuthenticatorEndpointGDTCStage,
EndpointDevice,
)
from authentik.flows.api.stages import StageSerializer
LOGGER = get_logger()
class AuthenticatorEndpointGDTCStageSerializer(EnterpriseRequiredMixin, StageSerializer):
"""AuthenticatorEndpointGDTCStage Serializer"""
class Meta:
model = AuthenticatorEndpointGDTCStage
fields = StageSerializer.Meta.fields + [
"configure_flow",
"friendly_name",
"credentials",
]
class AuthenticatorEndpointGDTCStageViewSet(UsedByMixin, ModelViewSet):
"""AuthenticatorEndpointGDTCStage Viewset"""
queryset = AuthenticatorEndpointGDTCStage.objects.all()
serializer_class = AuthenticatorEndpointGDTCStageSerializer
filterset_fields = [
"name",
"configure_flow",
]
search_fields = ["name"]
ordering = ["name"]
class EndpointDeviceSerializer(ModelSerializer):
"""Serializer for Endpoint authenticator devices"""
class Meta:
model = EndpointDevice
fields = ["pk", "name"]
depth = 2
class EndpointDeviceViewSet(
mixins.RetrieveModelMixin,
mixins.ListModelMixin,
UsedByMixin,
GenericViewSet,
):
"""Viewset for Endpoint authenticator devices"""
queryset = EndpointDevice.objects.all()
serializer_class = EndpointDeviceSerializer
search_fields = ["name"]
filterset_fields = ["name"]
ordering = ["name"]
permission_classes = [OwnerPermissions]
filter_backends = [OwnerFilter, DjangoFilterBackend, OrderingFilter, SearchFilter]
class EndpointAdminDeviceViewSet(ModelViewSet):
"""Viewset for Endpoint authenticator devices (for admins)"""
permission_classes = [IsAdminUser]
queryset = EndpointDevice.objects.all()
serializer_class = EndpointDeviceSerializer
search_fields = ["name"]
filterset_fields = ["name"]
ordering = ["name"]

View File

@ -0,0 +1,13 @@
"""authentik Endpoint app config"""
from authentik.enterprise.apps import EnterpriseConfig
class AuthentikStageAuthenticatorEndpointConfig(EnterpriseConfig):
"""authentik endpoint config"""
name = "authentik.enterprise.stages.authenticator_endpoint_gdtc"
label = "authentik_stages_authenticator_endpoint_gdtc"
verbose_name = "authentik Enterprise.Stages.Authenticator.Endpoint GDTC"
default = True
mountpoint = "endpoint/gdtc/"

View File

@ -0,0 +1,115 @@
# Generated by Django 5.0.9 on 2024-10-22 11:40
import django.db.models.deletion
import uuid
from django.conf import settings
from django.db import migrations, models
class Migration(migrations.Migration):
initial = True
dependencies = [
("authentik_flows", "0027_auto_20231028_1424"),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
migrations.CreateModel(
name="AuthenticatorEndpointGDTCStage",
fields=[
(
"stage_ptr",
models.OneToOneField(
auto_created=True,
on_delete=django.db.models.deletion.CASCADE,
parent_link=True,
primary_key=True,
serialize=False,
to="authentik_flows.stage",
),
),
("friendly_name", models.TextField(null=True)),
("credentials", models.JSONField()),
(
"configure_flow",
models.ForeignKey(
blank=True,
help_text="Flow used by an authenticated user to configure this Stage. If empty, user will not be able to configure this stage.",
null=True,
on_delete=django.db.models.deletion.SET_NULL,
to="authentik_flows.flow",
),
),
],
options={
"verbose_name": "Endpoint Authenticator Google Device Trust Connector Stage",
"verbose_name_plural": "Endpoint Authenticator Google Device Trust Connector Stages",
},
bases=("authentik_flows.stage", models.Model),
),
migrations.CreateModel(
name="EndpointDevice",
fields=[
("created", models.DateTimeField(auto_now_add=True)),
("last_updated", models.DateTimeField(auto_now=True)),
(
"name",
models.CharField(
help_text="The human-readable name of this device.", max_length=64
),
),
(
"confirmed",
models.BooleanField(default=True, help_text="Is this device ready for use?"),
),
("last_used", models.DateTimeField(null=True)),
("uuid", models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)),
(
"host_identifier",
models.TextField(
help_text="A unique identifier for the endpoint device, usually the device serial number",
unique=True,
),
),
("data", models.JSONField()),
(
"user",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL
),
),
],
options={
"verbose_name": "Endpoint Device",
"verbose_name_plural": "Endpoint Devices",
},
),
migrations.CreateModel(
name="EndpointDeviceConnection",
fields=[
(
"id",
models.AutoField(
auto_created=True, primary_key=True, serialize=False, verbose_name="ID"
),
),
("attributes", models.JSONField()),
(
"device",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
to="authentik_stages_authenticator_endpoint_gdtc.endpointdevice",
),
),
(
"stage",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
to="authentik_stages_authenticator_endpoint_gdtc.authenticatorendpointgdtcstage",
),
),
],
),
]

View File

@ -0,0 +1,101 @@
"""Endpoint stage"""
from uuid import uuid4
from django.contrib.auth import get_user_model
from django.db import models
from django.utils.translation import gettext_lazy as _
from google.oauth2.service_account import Credentials
from rest_framework.serializers import BaseSerializer, Serializer
from authentik.core.types import UserSettingSerializer
from authentik.flows.models import ConfigurableStage, FriendlyNamedStage, Stage
from authentik.flows.stage import StageView
from authentik.lib.models import SerializerModel
from authentik.stages.authenticator.models import Device
class AuthenticatorEndpointGDTCStage(ConfigurableStage, FriendlyNamedStage, Stage):
"""Setup Google Chrome Device-trust connection"""
credentials = models.JSONField()
def google_credentials(self):
return {
"credentials": Credentials.from_service_account_info(
self.credentials, scopes=["https://www.googleapis.com/auth/verifiedaccess"]
),
}
@property
def serializer(self) -> type[BaseSerializer]:
from authentik.enterprise.stages.authenticator_endpoint_gdtc.api import (
AuthenticatorEndpointGDTCStageSerializer,
)
return AuthenticatorEndpointGDTCStageSerializer
@property
def view(self) -> type[StageView]:
from authentik.enterprise.stages.authenticator_endpoint_gdtc.stage import (
AuthenticatorEndpointStageView,
)
return AuthenticatorEndpointStageView
@property
def component(self) -> str:
return "ak-stage-authenticator-endpoint-gdtc-form"
def ui_user_settings(self) -> UserSettingSerializer | None:
return UserSettingSerializer(
data={
"title": self.friendly_name or str(self._meta.verbose_name),
"component": "ak-user-settings-authenticator-endpoint",
}
)
def __str__(self) -> str:
return f"Endpoint Authenticator Google Device Trust Connector Stage {self.name}"
class Meta:
verbose_name = _("Endpoint Authenticator Google Device Trust Connector Stage")
verbose_name_plural = _("Endpoint Authenticator Google Device Trust Connector Stages")
class EndpointDevice(SerializerModel, Device):
"""Endpoint Device for a single user"""
uuid = models.UUIDField(primary_key=True, default=uuid4)
host_identifier = models.TextField(
unique=True,
help_text="A unique identifier for the endpoint device, usually the device serial number",
)
user = models.ForeignKey(get_user_model(), on_delete=models.CASCADE)
data = models.JSONField()
@property
def serializer(self) -> Serializer:
from authentik.enterprise.stages.authenticator_endpoint_gdtc.api import (
EndpointDeviceSerializer,
)
return EndpointDeviceSerializer
def __str__(self):
return str(self.name) or str(self.user_id)
class Meta:
verbose_name = _("Endpoint Device")
verbose_name_plural = _("Endpoint Devices")
class EndpointDeviceConnection(models.Model):
device = models.ForeignKey(EndpointDevice, on_delete=models.CASCADE)
stage = models.ForeignKey(AuthenticatorEndpointGDTCStage, on_delete=models.CASCADE)
attributes = models.JSONField()
def __str__(self) -> str:
return f"Endpoint device connection {self.device_id} to {self.stage_id}"

View File

@ -0,0 +1,32 @@
from django.http import HttpResponse
from django.urls import reverse
from django.utils.translation import gettext_lazy as _
from authentik.flows.challenge import (
Challenge,
ChallengeResponse,
FrameChallenge,
FrameChallengeResponse,
)
from authentik.flows.stage import ChallengeStageView
class AuthenticatorEndpointStageView(ChallengeStageView):
"""Endpoint stage"""
response_class = FrameChallengeResponse
def get_challenge(self, *args, **kwargs) -> Challenge:
return FrameChallenge(
data={
"component": "xak-flow-frame",
"url": self.request.build_absolute_uri(
reverse("authentik_stages_authenticator_endpoint_gdtc:chrome")
),
"loading_overlay": True,
"loading_text": _("Verifying your browser..."),
}
)
def challenge_valid(self, response: ChallengeResponse) -> HttpResponse:
return self.executor.stage_ok()

View File

@ -0,0 +1,9 @@
<html>
<script>
window.parent.postMessage({
message: "submit",
source: "goauthentik.io",
context: "flow-executor"
});
</script>
</html>

View File

@ -0,0 +1,26 @@
"""API URLs"""
from django.urls import path
from authentik.enterprise.stages.authenticator_endpoint_gdtc.api import (
AuthenticatorEndpointGDTCStageViewSet,
EndpointAdminDeviceViewSet,
EndpointDeviceViewSet,
)
from authentik.enterprise.stages.authenticator_endpoint_gdtc.views.dtc import (
GoogleChromeDeviceTrustConnector,
)
urlpatterns = [
path("chrome/", GoogleChromeDeviceTrustConnector.as_view(), name="chrome"),
]
api_urlpatterns = [
("authenticators/endpoint", EndpointDeviceViewSet),
(
"authenticators/admin/endpoint",
EndpointAdminDeviceViewSet,
"admin-endpointdevice",
),
("stages/authenticator/endpoint_gdtc", AuthenticatorEndpointGDTCStageViewSet),
]

View File

@ -0,0 +1,84 @@
from json import dumps, loads
from typing import Any
from django.http import HttpRequest, HttpResponse, HttpResponseRedirect
from django.template.response import TemplateResponse
from django.urls import reverse
from django.views import View
from googleapiclient.discovery import build
from authentik.enterprise.stages.authenticator_endpoint_gdtc.models import (
AuthenticatorEndpointGDTCStage,
EndpointDevice,
EndpointDeviceConnection,
)
from authentik.flows.planner import PLAN_CONTEXT_PENDING_USER, FlowPlan
from authentik.flows.views.executor import SESSION_KEY_PLAN
from authentik.stages.password.stage import PLAN_CONTEXT_METHOD, PLAN_CONTEXT_METHOD_ARGS
# Header we get from chrome that initiates verified access
HEADER_DEVICE_TRUST = "X-Device-Trust"
# Header we send to the client with the challenge
HEADER_ACCESS_CHALLENGE = "X-Verified-Access-Challenge"
# Header we get back from the client that we verify with google
HEADER_ACCESS_CHALLENGE_RESPONSE = "X-Verified-Access-Challenge-Response"
# Header value for x-device-trust that initiates the flow
DEVICE_TRUST_VERIFIED_ACCESS = "VerifiedAccess"
class GoogleChromeDeviceTrustConnector(View):
"""Google Chrome Device-trust connector based endpoint authenticator"""
def get_flow_plan(self) -> FlowPlan:
flow_plan: FlowPlan = self.request.session[SESSION_KEY_PLAN]
return flow_plan
def setup(self, request: HttpRequest, *args: Any, **kwargs: Any) -> None:
super().setup(request, *args, **kwargs)
stage: AuthenticatorEndpointGDTCStage = self.get_flow_plan().bindings[0].stage
self.google_client = build(
"verifiedaccess",
"v2",
cache_discovery=False,
**stage.google_credentials(),
)
def get(self, request: HttpRequest) -> HttpResponse:
x_device_trust = request.headers.get(HEADER_DEVICE_TRUST)
x_access_challenge_response = request.headers.get(HEADER_ACCESS_CHALLENGE_RESPONSE)
if x_device_trust == "VerifiedAccess" and x_access_challenge_response is None:
challenge = self.google_client.challenge().generate().execute()
res = HttpResponseRedirect(
self.request.build_absolute_uri(
reverse("authentik_stages_authenticator_endpoint_gdtc:chrome")
)
)
res[HEADER_ACCESS_CHALLENGE] = dumps(challenge)
return res
if x_access_challenge_response:
response = (
self.google_client.challenge()
.verify(body=loads(x_access_challenge_response))
.execute()
)
# Remove deprecated string representation of deviceSignals
response.pop("deviceSignal", None)
flow_plan: FlowPlan = self.get_flow_plan()
device, _ = EndpointDevice.objects.update_or_create(
host_identifier=response["deviceSignals"]["serialNumber"],
user=flow_plan.context.get(PLAN_CONTEXT_PENDING_USER),
defaults={"name": response["deviceSignals"]["hostname"], "data": response},
)
EndpointDeviceConnection.objects.update_or_create(
device=device,
stage=flow_plan.bindings[0].stage,
defaults={
"attributes": response,
},
)
flow_plan.context.setdefault(PLAN_CONTEXT_METHOD, "trusted_endpoint")
flow_plan.context.setdefault(PLAN_CONTEXT_METHOD_ARGS, {})
flow_plan.context[PLAN_CONTEXT_METHOD_ARGS].setdefault("endpoints", [])
flow_plan.context[PLAN_CONTEXT_METHOD_ARGS]["endpoints"].append(response)
request.session[SESSION_KEY_PLAN] = flow_plan
return TemplateResponse(request, "stages/authenticator_endpoint/google_chrome_dtc.html")

View File

@ -8,7 +8,7 @@ from uuid import UUID
from django.core.serializers.json import DjangoJSONEncoder
from django.db import models
from django.http import JsonResponse
from rest_framework.fields import CharField, ChoiceField, DictField
from rest_framework.fields import BooleanField, CharField, ChoiceField, DictField
from rest_framework.request import Request
from authentik.core.api.utils import PassiveSerializer
@ -160,6 +160,20 @@ class AutoSubmitChallengeResponse(ChallengeResponse):
component = CharField(default="ak-stage-autosubmit")
class FrameChallenge(Challenge):
"""Challenge type to render a frame"""
component = CharField(default="xak-flow-frame")
url = CharField()
loading_overlay = BooleanField(default=False)
loading_text = CharField()
class FrameChallengeResponse(ChallengeResponse):
component = CharField(default="xak-flow-frame")
class DataclassEncoder(DjangoJSONEncoder):
"""Convert any dataclass to json"""

View File

@ -38,6 +38,7 @@ LANGUAGE_COOKIE_NAME = "authentik_language"
SESSION_COOKIE_NAME = "authentik_session"
SESSION_COOKIE_DOMAIN = CONFIG.get("cookie_domain", None)
APPEND_SLASH = False
X_FRAME_OPTIONS = "SAMEORIGIN"
AUTHENTICATION_BACKENDS = [
"django.contrib.auth.backends.ModelBackend",

View File

@ -68,7 +68,7 @@ class AuthenticatorAttachment(models.TextChoices):
class AuthenticatorWebAuthnStage(ConfigurableStage, FriendlyNamedStage, Stage):
"""WebAuthn stage"""
"""Stage to enroll WebAuthn-based authenticators."""
user_verification = models.TextField(
choices=UserVerification.choices,

View File

@ -3361,6 +3361,46 @@
}
}
},
{
"type": "object",
"required": [
"model",
"identifiers"
],
"properties": {
"model": {
"const": "authentik_stages_authenticator_endpoint_gdtc.authenticatorendpointgdtcstage"
},
"id": {
"type": "string"
},
"state": {
"type": "string",
"enum": [
"absent",
"present",
"created",
"must_created"
],
"default": "present"
},
"conditions": {
"type": "array",
"items": {
"type": "boolean"
}
},
"permissions": {
"$ref": "#/$defs/model_authentik_stages_authenticator_endpoint_gdtc.authenticatorendpointgdtcstage_permissions"
},
"attrs": {
"$ref": "#/$defs/model_authentik_stages_authenticator_endpoint_gdtc.authenticatorendpointgdtcstage"
},
"identifiers": {
"$ref": "#/$defs/model_authentik_stages_authenticator_endpoint_gdtc.authenticatorendpointgdtcstage"
}
}
},
{
"type": "object",
"required": [
@ -4304,6 +4344,7 @@
"authentik.enterprise.providers.google_workspace",
"authentik.enterprise.providers.microsoft_entra",
"authentik.enterprise.providers.rac",
"authentik.enterprise.stages.authenticator_endpoint_gdtc",
"authentik.enterprise.stages.source",
"authentik.events"
],
@ -4400,6 +4441,7 @@
"authentik_providers_rac.racprovider",
"authentik_providers_rac.endpoint",
"authentik_providers_rac.racpropertymapping",
"authentik_stages_authenticator_endpoint_gdtc.authenticatorendpointgdtcstage",
"authentik_stages_source.sourcestage",
"authentik_events.event",
"authentik_events.notificationtransport",
@ -6451,6 +6493,18 @@
"authentik_stages_authenticator_duo.delete_duodevice",
"authentik_stages_authenticator_duo.view_authenticatorduostage",
"authentik_stages_authenticator_duo.view_duodevice",
"authentik_stages_authenticator_endpoint_gdtc.add_authenticatorendpointgdtcstage",
"authentik_stages_authenticator_endpoint_gdtc.add_endpointdevice",
"authentik_stages_authenticator_endpoint_gdtc.add_endpointdeviceconnection",
"authentik_stages_authenticator_endpoint_gdtc.change_authenticatorendpointgdtcstage",
"authentik_stages_authenticator_endpoint_gdtc.change_endpointdevice",
"authentik_stages_authenticator_endpoint_gdtc.change_endpointdeviceconnection",
"authentik_stages_authenticator_endpoint_gdtc.delete_authenticatorendpointgdtcstage",
"authentik_stages_authenticator_endpoint_gdtc.delete_endpointdevice",
"authentik_stages_authenticator_endpoint_gdtc.delete_endpointdeviceconnection",
"authentik_stages_authenticator_endpoint_gdtc.view_authenticatorendpointgdtcstage",
"authentik_stages_authenticator_endpoint_gdtc.view_endpointdevice",
"authentik_stages_authenticator_endpoint_gdtc.view_endpointdeviceconnection",
"authentik_stages_authenticator_sms.add_authenticatorsmsstage",
"authentik_stages_authenticator_sms.add_smsdevice",
"authentik_stages_authenticator_sms.change_authenticatorsmsstage",
@ -12107,6 +12161,18 @@
"authentik_stages_authenticator_duo.delete_duodevice",
"authentik_stages_authenticator_duo.view_authenticatorduostage",
"authentik_stages_authenticator_duo.view_duodevice",
"authentik_stages_authenticator_endpoint_gdtc.add_authenticatorendpointgdtcstage",
"authentik_stages_authenticator_endpoint_gdtc.add_endpointdevice",
"authentik_stages_authenticator_endpoint_gdtc.add_endpointdeviceconnection",
"authentik_stages_authenticator_endpoint_gdtc.change_authenticatorendpointgdtcstage",
"authentik_stages_authenticator_endpoint_gdtc.change_endpointdevice",
"authentik_stages_authenticator_endpoint_gdtc.change_endpointdeviceconnection",
"authentik_stages_authenticator_endpoint_gdtc.delete_authenticatorendpointgdtcstage",
"authentik_stages_authenticator_endpoint_gdtc.delete_endpointdevice",
"authentik_stages_authenticator_endpoint_gdtc.delete_endpointdeviceconnection",
"authentik_stages_authenticator_endpoint_gdtc.view_authenticatorendpointgdtcstage",
"authentik_stages_authenticator_endpoint_gdtc.view_endpointdevice",
"authentik_stages_authenticator_endpoint_gdtc.view_endpointdeviceconnection",
"authentik_stages_authenticator_sms.add_authenticatorsmsstage",
"authentik_stages_authenticator_sms.add_smsdevice",
"authentik_stages_authenticator_sms.change_authenticatorsmsstage",
@ -12997,6 +13063,144 @@
}
}
},
"model_authentik_stages_authenticator_endpoint_gdtc.authenticatorendpointgdtcstage": {
"type": "object",
"properties": {
"name": {
"type": "string",
"minLength": 1,
"title": "Name"
},
"flow_set": {
"type": "array",
"items": {
"type": "object",
"properties": {
"name": {
"type": "string",
"minLength": 1,
"title": "Name"
},
"slug": {
"type": "string",
"maxLength": 50,
"minLength": 1,
"pattern": "^[-a-zA-Z0-9_]+$",
"title": "Slug",
"description": "Visible in the URL."
},
"title": {
"type": "string",
"minLength": 1,
"title": "Title",
"description": "Shown as the Title in Flow pages."
},
"designation": {
"type": "string",
"enum": [
"authentication",
"authorization",
"invalidation",
"enrollment",
"unenrollment",
"recovery",
"stage_configuration"
],
"title": "Designation",
"description": "Decides what this Flow is used for. For example, the Authentication flow is redirect to when an un-authenticated user visits authentik."
},
"policy_engine_mode": {
"type": "string",
"enum": [
"all",
"any"
],
"title": "Policy engine mode"
},
"compatibility_mode": {
"type": "boolean",
"title": "Compatibility mode",
"description": "Enable compatibility mode, increases compatibility with password managers on mobile devices."
},
"layout": {
"type": "string",
"enum": [
"stacked",
"content_left",
"content_right",
"sidebar_left",
"sidebar_right"
],
"title": "Layout"
},
"denied_action": {
"type": "string",
"enum": [
"message_continue",
"message",
"continue"
],
"title": "Denied action",
"description": "Configure what should happen when a flow denies access to a user."
}
},
"required": [
"name",
"slug",
"title",
"designation"
]
},
"title": "Flow set"
},
"configure_flow": {
"type": "string",
"format": "uuid",
"title": "Configure flow",
"description": "Flow used by an authenticated user to configure this Stage. If empty, user will not be able to configure this stage."
},
"friendly_name": {
"type": [
"string",
"null"
],
"minLength": 1,
"title": "Friendly name"
},
"credentials": {
"type": "object",
"additionalProperties": true,
"title": "Credentials"
}
},
"required": []
},
"model_authentik_stages_authenticator_endpoint_gdtc.authenticatorendpointgdtcstage_permissions": {
"type": "array",
"items": {
"type": "object",
"required": [
"permission"
],
"properties": {
"permission": {
"type": "string",
"enum": [
"add_authenticatorendpointgdtcstage",
"change_authenticatorendpointgdtcstage",
"delete_authenticatorendpointgdtcstage",
"view_authenticatorendpointgdtcstage"
]
},
"user": {
"type": "integer"
},
"role": {
"type": "string"
}
}
}
},
"model_authentik_stages_source.sourcestage": {
"type": "object",
"properties": {

View File

@ -636,6 +636,238 @@ paths:
schema:
$ref: '#/components/schemas/GenericError'
description: ''
/authenticators/admin/endpoint/:
get:
operationId: authenticators_admin_endpoint_list
description: Viewset for Endpoint authenticator devices (for admins)
parameters:
- in: query
name: name
schema:
type: string
- name: ordering
required: false
in: query
description: Which field to use when ordering the results.
schema:
type: string
- name: page
required: false
in: query
description: A page number within the paginated result set.
schema:
type: integer
- name: page_size
required: false
in: query
description: Number of results to return per page.
schema:
type: integer
- name: search
required: false
in: query
description: A search term.
schema:
type: string
tags:
- authenticators
security:
- authentik: []
responses:
'200':
content:
application/json:
schema:
$ref: '#/components/schemas/PaginatedEndpointDeviceList'
description: ''
'400':
content:
application/json:
schema:
$ref: '#/components/schemas/ValidationError'
description: ''
'403':
content:
application/json:
schema:
$ref: '#/components/schemas/GenericError'
description: ''
post:
operationId: authenticators_admin_endpoint_create
description: Viewset for Endpoint authenticator devices (for admins)
tags:
- authenticators
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/EndpointDeviceRequest'
required: true
security:
- authentik: []
responses:
'201':
content:
application/json:
schema:
$ref: '#/components/schemas/EndpointDevice'
description: ''
'400':
content:
application/json:
schema:
$ref: '#/components/schemas/ValidationError'
description: ''
'403':
content:
application/json:
schema:
$ref: '#/components/schemas/GenericError'
description: ''
/authenticators/admin/endpoint/{uuid}/:
get:
operationId: authenticators_admin_endpoint_retrieve
description: Viewset for Endpoint authenticator devices (for admins)
parameters:
- in: path
name: uuid
schema:
type: string
format: uuid
description: A UUID string identifying this Endpoint Device.
required: true
tags:
- authenticators
security:
- authentik: []
responses:
'200':
content:
application/json:
schema:
$ref: '#/components/schemas/EndpointDevice'
description: ''
'400':
content:
application/json:
schema:
$ref: '#/components/schemas/ValidationError'
description: ''
'403':
content:
application/json:
schema:
$ref: '#/components/schemas/GenericError'
description: ''
put:
operationId: authenticators_admin_endpoint_update
description: Viewset for Endpoint authenticator devices (for admins)
parameters:
- in: path
name: uuid
schema:
type: string
format: uuid
description: A UUID string identifying this Endpoint Device.
required: true
tags:
- authenticators
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/EndpointDeviceRequest'
required: true
security:
- authentik: []
responses:
'200':
content:
application/json:
schema:
$ref: '#/components/schemas/EndpointDevice'
description: ''
'400':
content:
application/json:
schema:
$ref: '#/components/schemas/ValidationError'
description: ''
'403':
content:
application/json:
schema:
$ref: '#/components/schemas/GenericError'
description: ''
patch:
operationId: authenticators_admin_endpoint_partial_update
description: Viewset for Endpoint authenticator devices (for admins)
parameters:
- in: path
name: uuid
schema:
type: string
format: uuid
description: A UUID string identifying this Endpoint Device.
required: true
tags:
- authenticators
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/PatchedEndpointDeviceRequest'
security:
- authentik: []
responses:
'200':
content:
application/json:
schema:
$ref: '#/components/schemas/EndpointDevice'
description: ''
'400':
content:
application/json:
schema:
$ref: '#/components/schemas/ValidationError'
description: ''
'403':
content:
application/json:
schema:
$ref: '#/components/schemas/GenericError'
description: ''
delete:
operationId: authenticators_admin_endpoint_destroy
description: Viewset for Endpoint authenticator devices (for admins)
parameters:
- in: path
name: uuid
schema:
type: string
format: uuid
description: A UUID string identifying this Endpoint Device.
required: true
tags:
- authenticators
security:
- authentik: []
responses:
'204':
description: No response body
'400':
content:
application/json:
schema:
$ref: '#/components/schemas/ValidationError'
description: ''
'403':
content:
application/json:
schema:
$ref: '#/components/schemas/GenericError'
description: ''
/authenticators/admin/sms/:
get:
operationId: authenticators_admin_sms_list
@ -1809,6 +2041,134 @@ paths:
schema:
$ref: '#/components/schemas/GenericError'
description: ''
/authenticators/endpoint/:
get:
operationId: authenticators_endpoint_list
description: Viewset for Endpoint authenticator devices
parameters:
- in: query
name: name
schema:
type: string
- name: ordering
required: false
in: query
description: Which field to use when ordering the results.
schema:
type: string
- name: page
required: false
in: query
description: A page number within the paginated result set.
schema:
type: integer
- name: page_size
required: false
in: query
description: Number of results to return per page.
schema:
type: integer
- name: search
required: false
in: query
description: A search term.
schema:
type: string
tags:
- authenticators
security:
- authentik: []
responses:
'200':
content:
application/json:
schema:
$ref: '#/components/schemas/PaginatedEndpointDeviceList'
description: ''
'400':
content:
application/json:
schema:
$ref: '#/components/schemas/ValidationError'
description: ''
'403':
content:
application/json:
schema:
$ref: '#/components/schemas/GenericError'
description: ''
/authenticators/endpoint/{uuid}/:
get:
operationId: authenticators_endpoint_retrieve
description: Viewset for Endpoint authenticator devices
parameters:
- in: path
name: uuid
schema:
type: string
format: uuid
description: A UUID string identifying this Endpoint Device.
required: true
tags:
- authenticators
security:
- authentik: []
responses:
'200':
content:
application/json:
schema:
$ref: '#/components/schemas/EndpointDevice'
description: ''
'400':
content:
application/json:
schema:
$ref: '#/components/schemas/ValidationError'
description: ''
'403':
content:
application/json:
schema:
$ref: '#/components/schemas/GenericError'
description: ''
/authenticators/endpoint/{uuid}/used_by/:
get:
operationId: authenticators_endpoint_used_by_list
description: Get a list of all objects that use this object
parameters:
- in: path
name: uuid
schema:
type: string
format: uuid
description: A UUID string identifying this Endpoint Device.
required: true
tags:
- authenticators
security:
- authentik: []
responses:
'200':
content:
application/json:
schema:
type: array
items:
$ref: '#/components/schemas/UsedBy'
description: ''
'400':
content:
application/json:
schema:
$ref: '#/components/schemas/ValidationError'
description: ''
'403':
content:
application/json:
schema:
$ref: '#/components/schemas/GenericError'
description: ''
/authenticators/sms/:
get:
operationId: authenticators_sms_list
@ -22725,6 +23085,7 @@ paths:
- authentik_sources_scim.scimsourcepropertymapping
- authentik_stages_authenticator_duo.authenticatorduostage
- authentik_stages_authenticator_duo.duodevice
- authentik_stages_authenticator_endpoint_gdtc.authenticatorendpointgdtcstage
- authentik_stages_authenticator_sms.authenticatorsmsstage
- authentik_stages_authenticator_sms.smsdevice
- authentik_stages_authenticator_static.authenticatorstaticstage
@ -22959,6 +23320,7 @@ paths:
- authentik_sources_scim.scimsourcepropertymapping
- authentik_stages_authenticator_duo.authenticatorduostage
- authentik_stages_authenticator_duo.duodevice
- authentik_stages_authenticator_endpoint_gdtc.authenticatorendpointgdtcstage
- authentik_stages_authenticator_sms.authenticatorsmsstage
- authentik_stages_authenticator_sms.smsdevice
- authentik_stages_authenticator_static.authenticatorstaticstage
@ -29031,6 +29393,285 @@ paths:
schema:
$ref: '#/components/schemas/GenericError'
description: ''
/stages/authenticator/endpoint_gdtc/:
get:
operationId: stages_authenticator_endpoint_gdtc_list
description: AuthenticatorEndpointGDTCStage Viewset
parameters:
- in: query
name: configure_flow
schema:
type: string
format: uuid
- in: query
name: name
schema:
type: string
- name: ordering
required: false
in: query
description: Which field to use when ordering the results.
schema:
type: string
- name: page
required: false
in: query
description: A page number within the paginated result set.
schema:
type: integer
- name: page_size
required: false
in: query
description: Number of results to return per page.
schema:
type: integer
- name: search
required: false
in: query
description: A search term.
schema:
type: string
tags:
- stages
security:
- authentik: []
responses:
'200':
content:
application/json:
schema:
$ref: '#/components/schemas/PaginatedAuthenticatorEndpointGDTCStageList'
description: ''
'400':
content:
application/json:
schema:
$ref: '#/components/schemas/ValidationError'
description: ''
'403':
content:
application/json:
schema:
$ref: '#/components/schemas/GenericError'
description: ''
post:
operationId: stages_authenticator_endpoint_gdtc_create
description: AuthenticatorEndpointGDTCStage Viewset
tags:
- stages
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/AuthenticatorEndpointGDTCStageRequest'
required: true
security:
- authentik: []
responses:
'201':
content:
application/json:
schema:
$ref: '#/components/schemas/AuthenticatorEndpointGDTCStage'
description: ''
'400':
content:
application/json:
schema:
$ref: '#/components/schemas/ValidationError'
description: ''
'403':
content:
application/json:
schema:
$ref: '#/components/schemas/GenericError'
description: ''
/stages/authenticator/endpoint_gdtc/{stage_uuid}/:
get:
operationId: stages_authenticator_endpoint_gdtc_retrieve
description: AuthenticatorEndpointGDTCStage Viewset
parameters:
- in: path
name: stage_uuid
schema:
type: string
format: uuid
description: A UUID string identifying this Endpoint Authenticator Google
Device Trust Connector Stage.
required: true
tags:
- stages
security:
- authentik: []
responses:
'200':
content:
application/json:
schema:
$ref: '#/components/schemas/AuthenticatorEndpointGDTCStage'
description: ''
'400':
content:
application/json:
schema:
$ref: '#/components/schemas/ValidationError'
description: ''
'403':
content:
application/json:
schema:
$ref: '#/components/schemas/GenericError'
description: ''
put:
operationId: stages_authenticator_endpoint_gdtc_update
description: AuthenticatorEndpointGDTCStage Viewset
parameters:
- in: path
name: stage_uuid
schema:
type: string
format: uuid
description: A UUID string identifying this Endpoint Authenticator Google
Device Trust Connector Stage.
required: true
tags:
- stages
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/AuthenticatorEndpointGDTCStageRequest'
required: true
security:
- authentik: []
responses:
'200':
content:
application/json:
schema:
$ref: '#/components/schemas/AuthenticatorEndpointGDTCStage'
description: ''
'400':
content:
application/json:
schema:
$ref: '#/components/schemas/ValidationError'
description: ''
'403':
content:
application/json:
schema:
$ref: '#/components/schemas/GenericError'
description: ''
patch:
operationId: stages_authenticator_endpoint_gdtc_partial_update
description: AuthenticatorEndpointGDTCStage Viewset
parameters:
- in: path
name: stage_uuid
schema:
type: string
format: uuid
description: A UUID string identifying this Endpoint Authenticator Google
Device Trust Connector Stage.
required: true
tags:
- stages
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/PatchedAuthenticatorEndpointGDTCStageRequest'
security:
- authentik: []
responses:
'200':
content:
application/json:
schema:
$ref: '#/components/schemas/AuthenticatorEndpointGDTCStage'
description: ''
'400':
content:
application/json:
schema:
$ref: '#/components/schemas/ValidationError'
description: ''
'403':
content:
application/json:
schema:
$ref: '#/components/schemas/GenericError'
description: ''
delete:
operationId: stages_authenticator_endpoint_gdtc_destroy
description: AuthenticatorEndpointGDTCStage Viewset
parameters:
- in: path
name: stage_uuid
schema:
type: string
format: uuid
description: A UUID string identifying this Endpoint Authenticator Google
Device Trust Connector Stage.
required: true
tags:
- stages
security:
- authentik: []
responses:
'204':
description: No response body
'400':
content:
application/json:
schema:
$ref: '#/components/schemas/ValidationError'
description: ''
'403':
content:
application/json:
schema:
$ref: '#/components/schemas/GenericError'
description: ''
/stages/authenticator/endpoint_gdtc/{stage_uuid}/used_by/:
get:
operationId: stages_authenticator_endpoint_gdtc_used_by_list
description: Get a list of all objects that use this object
parameters:
- in: path
name: stage_uuid
schema:
type: string
format: uuid
description: A UUID string identifying this Endpoint Authenticator Google
Device Trust Connector Stage.
required: true
tags:
- stages
security:
- authentik: []
responses:
'200':
content:
application/json:
schema:
type: array
items:
$ref: '#/components/schemas/UsedBy'
description: ''
'400':
content:
application/json:
schema:
$ref: '#/components/schemas/ValidationError'
description: ''
'403':
content:
application/json:
schema:
$ref: '#/components/schemas/GenericError'
description: ''
/stages/authenticator/sms/:
get:
operationId: stages_authenticator_sms_list
@ -35915,6 +36556,7 @@ components:
- authentik.enterprise.providers.google_workspace
- authentik.enterprise.providers.microsoft_entra
- authentik.enterprise.providers.rac
- authentik.enterprise.stages.authenticator_endpoint_gdtc
- authentik.enterprise.stages.source
- authentik.events
type: string
@ -36380,6 +37022,80 @@ components:
- client_id
- client_secret
- name
AuthenticatorEndpointGDTCStage:
type: object
description: AuthenticatorEndpointGDTCStage Serializer
properties:
pk:
type: string
format: uuid
readOnly: true
title: Stage uuid
name:
type: string
component:
type: string
description: Get object type so that we know how to edit the object
readOnly: true
verbose_name:
type: string
description: Return object's verbose_name
readOnly: true
verbose_name_plural:
type: string
description: Return object's plural verbose_name
readOnly: true
meta_model_name:
type: string
description: Return internal model name
readOnly: true
flow_set:
type: array
items:
$ref: '#/components/schemas/FlowSet'
configure_flow:
type: string
format: uuid
nullable: true
description: Flow used by an authenticated user to configure this Stage.
If empty, user will not be able to configure this stage.
friendly_name:
type: string
nullable: true
credentials: {}
required:
- component
- credentials
- meta_model_name
- name
- pk
- verbose_name
- verbose_name_plural
AuthenticatorEndpointGDTCStageRequest:
type: object
description: AuthenticatorEndpointGDTCStage Serializer
properties:
name:
type: string
minLength: 1
flow_set:
type: array
items:
$ref: '#/components/schemas/FlowSetRequest'
configure_flow:
type: string
format: uuid
nullable: true
description: Flow used by an authenticated user to configure this Stage.
If empty, user will not be able to configure this stage.
friendly_name:
type: string
nullable: true
minLength: 1
credentials: {}
required:
- credentials
- name
AuthenticatorSMSChallenge:
type: object
description: SMS Setup challenge
@ -37629,6 +38345,7 @@ components:
- $ref: '#/components/schemas/DummyChallenge'
- $ref: '#/components/schemas/EmailChallenge'
- $ref: '#/components/schemas/FlowErrorChallenge'
- $ref: '#/components/schemas/FrameChallenge'
- $ref: '#/components/schemas/IdentificationChallenge'
- $ref: '#/components/schemas/OAuthDeviceCodeChallenge'
- $ref: '#/components/schemas/OAuthDeviceCodeFinishChallenge'
@ -37656,6 +38373,7 @@ components:
ak-stage-dummy: '#/components/schemas/DummyChallenge'
ak-stage-email: '#/components/schemas/EmailChallenge'
ak-stage-flow-error: '#/components/schemas/FlowErrorChallenge'
xak-flow-frame: '#/components/schemas/FrameChallenge'
ak-stage-identification: '#/components/schemas/IdentificationChallenge'
ak-provider-oauth2-device-code: '#/components/schemas/OAuthDeviceCodeChallenge'
ak-provider-oauth2-device-code-finish: '#/components/schemas/OAuthDeviceCodeFinishChallenge'
@ -38316,7 +39034,7 @@ components:
description: Return internal model name
readOnly: true
pk:
type: integer
type: string
name:
type: string
type:
@ -38929,6 +39647,35 @@ components:
- protocol
- provider
- provider_obj
EndpointDevice:
type: object
description: Serializer for Endpoint authenticator devices
properties:
pk:
type: string
format: uuid
title: Uuid
name:
type: string
description: The human-readable name of this device.
maxLength: 64
required:
- name
EndpointDeviceRequest:
type: object
description: Serializer for Endpoint authenticator devices
properties:
pk:
type: string
format: uuid
title: Uuid
name:
type: string
minLength: 1
description: The human-readable name of this device.
maxLength: 64
required:
- name
EndpointRequest:
type: object
description: Endpoint Serializer
@ -39525,6 +40272,7 @@ components:
- $ref: '#/components/schemas/ConsentChallengeResponseRequest'
- $ref: '#/components/schemas/DummyChallengeResponseRequest'
- $ref: '#/components/schemas/EmailChallengeResponseRequest'
- $ref: '#/components/schemas/FrameChallengeResponseRequest'
- $ref: '#/components/schemas/IdentificationChallengeResponseRequest'
- $ref: '#/components/schemas/OAuthDeviceCodeChallengeResponseRequest'
- $ref: '#/components/schemas/OAuthDeviceCodeFinishChallengeResponseRequest'
@ -39547,6 +40295,7 @@ components:
ak-stage-consent: '#/components/schemas/ConsentChallengeResponseRequest'
ak-stage-dummy: '#/components/schemas/DummyChallengeResponseRequest'
ak-stage-email: '#/components/schemas/EmailChallengeResponseRequest'
xak-flow-frame: '#/components/schemas/FrameChallengeResponseRequest'
ak-stage-identification: '#/components/schemas/IdentificationChallengeResponseRequest'
ak-provider-oauth2-device-code: '#/components/schemas/OAuthDeviceCodeChallengeResponseRequest'
ak-provider-oauth2-device-code-finish: '#/components/schemas/OAuthDeviceCodeFinishChallengeResponseRequest'
@ -39903,6 +40652,39 @@ components:
required:
- href
- name
FrameChallenge:
type: object
description: Challenge type to render a frame
properties:
flow_info:
$ref: '#/components/schemas/ContextualFlowInfo'
component:
type: string
default: xak-flow-frame
response_errors:
type: object
additionalProperties:
type: array
items:
$ref: '#/components/schemas/ErrorDetail'
url:
type: string
loading_overlay:
type: boolean
default: false
loading_text:
type: string
required:
- loading_text
- url
FrameChallengeResponseRequest:
type: object
description: Base class for all challenge responses
properties:
component:
type: string
minLength: 1
default: xak-flow-frame
GenericError:
type: object
description: Generic API Error
@ -42142,6 +42924,7 @@ components:
- authentik_providers_rac.racprovider
- authentik_providers_rac.endpoint
- authentik_providers_rac.racpropertymapping
- authentik_stages_authenticator_endpoint_gdtc.authenticatorendpointgdtcstage
- authentik_stages_source.sourcestage
- authentik_events.event
- authentik_events.notificationtransport
@ -43235,6 +44018,18 @@ components:
required:
- pagination
- results
PaginatedAuthenticatorEndpointGDTCStageList:
type: object
properties:
pagination:
$ref: '#/components/schemas/Pagination'
results:
type: array
items:
$ref: '#/components/schemas/AuthenticatorEndpointGDTCStage'
required:
- pagination
- results
PaginatedAuthenticatorSMSStageList:
type: object
properties:
@ -43451,6 +44246,18 @@ components:
required:
- pagination
- results
PaginatedEndpointDeviceList:
type: object
properties:
pagination:
$ref: '#/components/schemas/Pagination'
results:
type: array
items:
$ref: '#/components/schemas/EndpointDevice'
required:
- pagination
- results
PaginatedEndpointList:
type: object
properties:
@ -45088,6 +45895,28 @@ components:
admin_secret_key:
type: string
writeOnly: true
PatchedAuthenticatorEndpointGDTCStageRequest:
type: object
description: AuthenticatorEndpointGDTCStage Serializer
properties:
name:
type: string
minLength: 1
flow_set:
type: array
items:
$ref: '#/components/schemas/FlowSetRequest'
configure_flow:
type: string
format: uuid
nullable: true
description: Flow used by an authenticated user to configure this Stage.
If empty, user will not be able to configure this stage.
friendly_name:
type: string
nullable: true
minLength: 1
credentials: {}
PatchedAuthenticatorSMSStageRequest:
type: object
description: AuthenticatorSMSStage Serializer
@ -45569,6 +46398,19 @@ components:
activate_user_on_success:
type: boolean
description: Activate users upon completion of stage.
PatchedEndpointDeviceRequest:
type: object
description: Serializer for Endpoint authenticator devices
properties:
pk:
type: string
format: uuid
title: Uuid
name:
type: string
minLength: 1
description: The human-readable name of this device.
maxLength: 64
PatchedEndpointRequest:
type: object
description: Endpoint Serializer

View File

@ -2,6 +2,7 @@ import "@goauthentik/admin/rbac/ObjectPermissionModal";
import "@goauthentik/admin/stages/StageWizard";
import "@goauthentik/admin/stages/authenticator_duo/AuthenticatorDuoStageForm";
import "@goauthentik/admin/stages/authenticator_duo/DuoDeviceImportForm";
import "@goauthentik/admin/stages/authenticator_endpoint_gdtc/AuthenticatorEndpointGDTCStageForm";
import "@goauthentik/admin/stages/authenticator_sms/AuthenticatorSMSStageForm";
import "@goauthentik/admin/stages/authenticator_static/AuthenticatorStaticStageForm";
import "@goauthentik/admin/stages/authenticator_totp/AuthenticatorTOTPStageForm";
@ -25,8 +26,7 @@ import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
import "@goauthentik/elements/forms/DeleteBulkForm";
import "@goauthentik/elements/forms/ModalForm";
import "@goauthentik/elements/forms/ProxyForm";
import { PaginatedResponse } from "@goauthentik/elements/table/Table";
import { TableColumn } from "@goauthentik/elements/table/Table";
import { PaginatedResponse, TableColumn } from "@goauthentik/elements/table/Table";
import { TablePage } from "@goauthentik/elements/table/TablePage";
import "@patternfly/elements/pf-tooltip/pf-tooltip.js";

View File

@ -0,0 +1,75 @@
import { BaseStageForm } from "@goauthentik/admin/stages/BaseStageForm";
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
import { first } from "@goauthentik/common/utils";
import "@goauthentik/elements/CodeMirror";
import { CodeMirrorMode } from "@goauthentik/elements/CodeMirror";
import "@goauthentik/elements/forms/FormGroup";
import "@goauthentik/elements/forms/HorizontalFormElement";
import { msg } from "@lit/localize";
import { TemplateResult, html } from "lit";
import { customElement } from "lit/decorators.js";
import { AuthenticatorEndpointGDTCStage, StagesApi } from "@goauthentik/api";
@customElement("ak-stage-authenticator-endpoint-gdtc-form")
export class AuthenticatorEndpointGDTCStageForm extends BaseStageForm<AuthenticatorEndpointGDTCStage> {
loadInstance(pk: string): Promise<AuthenticatorEndpointGDTCStage> {
return new StagesApi(DEFAULT_CONFIG).stagesAuthenticatorEndpointGdtcRetrieve({
stageUuid: pk,
});
}
async send(data: AuthenticatorEndpointGDTCStage): Promise<AuthenticatorEndpointGDTCStage> {
if (this.instance) {
return new StagesApi(DEFAULT_CONFIG).stagesAuthenticatorEndpointGdtcPartialUpdate({
stageUuid: this.instance.pk || "",
patchedAuthenticatorEndpointGDTCStageRequest: data,
});
} else {
return new StagesApi(DEFAULT_CONFIG).stagesAuthenticatorEndpointGdtcCreate({
authenticatorEndpointGDTCStageRequest: data,
});
}
}
renderForm(): TemplateResult {
return html` <span>
${msg(
"Stage used to verify users' browsers using Google Chrome Device Trust. This stage can be used in authentication/authorization flows.",
)}
</span>
<ak-form-element-horizontal label=${msg("Name")} required name="name">
<input
type="text"
value="${first(this.instance?.name, "")}"
class="pf-c-form-control"
required
/>
</ak-form-element-horizontal>
<ak-form-group expanded>
<span slot="header"> ${msg("Google Verified Access API")} </span>
<div slot="body" class="pf-c-form">
<ak-form-element-horizontal
label=${msg("Credentials")}
required
name="credentials"
>
<ak-codemirror
mode=${CodeMirrorMode.JavaScript}
.value="${first(this.instance?.credentials, {})}"
></ak-codemirror>
<p class="pf-c-form__helper-text">
${msg("Google Cloud credentials file.")}
</p>
</ak-form-element-horizontal>
</div>
</ak-form-group>`;
}
}
declare global {
interface HTMLElementTagNameMap {
"ak-stage-authenticator-endpoint-gdtc-form": AuthenticatorEndpointGDTCStageForm;
}
}

View File

@ -1,11 +1,12 @@
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
import { SentryIgnoredError } from "@goauthentik/common/errors";
import { deviceTypeName } from "@goauthentik/common/labels";
import { getRelativeTime } from "@goauthentik/common/utils";
import "@goauthentik/elements/forms/DeleteBulkForm";
import { PaginatedResponse } from "@goauthentik/elements/table/Table";
import { Table, TableColumn } from "@goauthentik/elements/table/Table";
import { msg } from "@lit/localize";
import { msg, str } from "@lit/localize";
import { TemplateResult, html } from "lit";
import { customElement, property } from "lit/decorators.js";
@ -54,20 +55,21 @@ export class UserDeviceTable extends Table<Device> {
async deleteWrapper(device: Device) {
const api = new AuthenticatorsApi(DEFAULT_CONFIG);
const id = { id: device.pk };
switch (device.type) {
case "authentik_stages_authenticator_duo.DuoDevice":
return api.authenticatorsAdminDuoDestroy(id);
return api.authenticatorsAdminDuoDestroy({ id: parseInt(device.pk, 10) });
case "authentik_stages_authenticator_sms.SMSDevice":
return api.authenticatorsAdminSmsDestroy(id);
return api.authenticatorsAdminSmsDestroy({ id: parseInt(device.pk, 10) });
case "authentik_stages_authenticator_totp.TOTPDevice":
return api.authenticatorsAdminTotpDestroy(id);
return api.authenticatorsAdminTotpDestroy({ id: parseInt(device.pk, 10) });
case "authentik_stages_authenticator_static.StaticDevice":
return api.authenticatorsAdminStaticDestroy(id);
return api.authenticatorsAdminStaticDestroy({ id: parseInt(device.pk, 10) });
case "authentik_stages_authenticator_webauthn.WebAuthnDevice":
return api.authenticatorsAdminWebauthnDestroy(id);
return api.authenticatorsAdminWebauthnDestroy({ id: parseInt(device.pk, 10) });
default:
break;
throw new SentryIgnoredError(
msg(str`Device type ${device.verboseName} cannot be deleted`),
);
}
}

View File

@ -16,6 +16,7 @@ import { themeImage } from "@goauthentik/elements/utils/images";
import "@goauthentik/flow/sources/apple/AppleLoginInit";
import "@goauthentik/flow/sources/plex/PlexLoginInit";
import "@goauthentik/flow/stages/FlowErrorStage";
import "@goauthentik/flow/stages/FlowFrameStage";
import "@goauthentik/flow/stages/RedirectStage";
import { StageHost, SubmitOptions } from "@goauthentik/flow/stages/base";
@ -170,6 +171,19 @@ export class FlowExecutor extends Interface implements StageHost {
this.addEventListener(EVENT_FLOW_INSPECTOR_TOGGLE, () => {
this.inspectorOpen = !this.inspectorOpen;
});
window.addEventListener("message", (event) => {
const msg: {
source?: string;
context?: string;
message: string;
} = event.data;
if (msg.source !== "goauthentik.io" || msg.context !== "flow-executor") {
return;
}
if (msg.message === "submit") {
this.submit({} as FlowChallengeResponseRequest);
}
});
}
async getTheme(): Promise<UiThemeEnum> {
@ -429,6 +443,11 @@ export class FlowExecutor extends Interface implements StageHost {
</ak-stage-redirect>`;
case "xak-flow-shell":
return html`${unsafeHTML((this.challenge as ShellChallenge).body)}`;
case "xak-flow-frame":
return html`<xak-flow-frame
.host=${this as StageHost}
.challenge=${this.challenge}
></xak-flow-frame>`;
default:
return html`Invalid native challenge element`;
}

View File

@ -114,7 +114,7 @@ export class InputPassword extends AKElement {
this.input.type = "password";
this.input.name = this.name;
this.input.placeholder = this.placeholder;
this.input.autofocus = true;
this.input.autofocus = this.grabFocus;
this.input.autocomplete = "current-password";
this.input.classList.add("pf-c-form-control");
this.input.required = true;

View File

@ -0,0 +1,54 @@
import "@goauthentik/elements/EmptyState";
import "@goauthentik/flow/FormStatic";
import { BaseStage } from "@goauthentik/flow/stages/base";
import { CSSResult, TemplateResult, css, html, nothing } from "lit";
import { customElement } from "lit/decorators.js";
import PFForm from "@patternfly/patternfly/components/Form/form.css";
import PFFormControl from "@patternfly/patternfly/components/FormControl/form-control.css";
import PFLogin from "@patternfly/patternfly/components/Login/login.css";
import PFTitle from "@patternfly/patternfly/components/Title/title.css";
import PFBase from "@patternfly/patternfly/patternfly-base.css";
import { FrameChallenge, FrameChallengeResponseRequest } from "@goauthentik/api";
@customElement("xak-flow-frame")
export class FlowFrameStage extends BaseStage<FrameChallenge, FrameChallengeResponseRequest> {
static get styles(): CSSResult[] {
return [PFBase, PFLogin, PFForm, PFFormControl, PFTitle, css``];
}
render(): TemplateResult {
if (!this.challenge) {
return html`<ak-empty-state loading> </ak-empty-state>`;
}
return html` <header class="pf-c-login__main-header">
<h1 class="pf-c-title pf-m-3xl">${this.challenge.flowInfo?.title}</h1>
</header>
<div class="pf-c-login__main-body">
${this.challenge.loadingOverlay
? html`<ak-empty-state
loading
header=${this.challenge.loadingText ?? undefined}
>
</ak-empty-state>`
: nothing}
<iframe
style=${this.challenge.loadingOverlay
? "width:0;height:0;position:absolute;"
: ""}
src=${this.challenge.url}
></iframe>
</div>
<footer class="pf-c-login__main-footer">
<ul class="pf-c-login__main-footer-links"></ul>
</footer>`;
}
}
declare global {
interface HTMLElementTagNameMap {
"xak-flow-frame": FlowFrameStage;
}
}

View File

@ -1,8 +1,9 @@
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
import { SentryIgnoredError } from "@goauthentik/common/errors";
import "@goauthentik/elements/forms/HorizontalFormElement";
import { ModelForm } from "@goauthentik/elements/forms/ModelForm";
import { msg } from "@lit/localize";
import { msg, str } from "@lit/localize";
import { TemplateResult, html } from "lit";
import { customElement, property } from "lit/decorators.js";
import { ifDefined } from "lit/directives/if-defined.js";
@ -10,11 +11,11 @@ import { ifDefined } from "lit/directives/if-defined.js";
import { AuthenticatorsApi, Device } from "@goauthentik/api";
@customElement("ak-user-mfa-form")
export class MFADeviceForm extends ModelForm<Device, number> {
export class MFADeviceForm extends ModelForm<Device, string> {
@property()
deviceType!: string;
async loadInstance(pk: number): Promise<Device> {
async loadInstance(pk: string): Promise<Device> {
const devices = await new AuthenticatorsApi(DEFAULT_CONFIG).authenticatorsAllList();
return devices.filter((device) => {
return device.pk === pk && device.type === this.deviceType;
@ -29,36 +30,38 @@ export class MFADeviceForm extends ModelForm<Device, number> {
switch (this.instance?.type) {
case "authentik_stages_authenticator_duo.DuoDevice":
await new AuthenticatorsApi(DEFAULT_CONFIG).authenticatorsDuoUpdate({
id: this.instance?.pk,
id: parseInt(this.instance?.pk, 10),
duoDeviceRequest: device,
});
break;
case "authentik_stages_authenticator_sms.SMSDevice":
await new AuthenticatorsApi(DEFAULT_CONFIG).authenticatorsSmsUpdate({
id: this.instance?.pk,
id: parseInt(this.instance?.pk, 10),
sMSDeviceRequest: device,
});
break;
case "authentik_stages_authenticator_totp.TOTPDevice":
await new AuthenticatorsApi(DEFAULT_CONFIG).authenticatorsTotpUpdate({
id: this.instance?.pk,
id: parseInt(this.instance?.pk, 10),
tOTPDeviceRequest: device,
});
break;
case "authentik_stages_authenticator_static.StaticDevice":
await new AuthenticatorsApi(DEFAULT_CONFIG).authenticatorsStaticUpdate({
id: this.instance?.pk,
id: parseInt(this.instance?.pk, 10),
staticDeviceRequest: device,
});
break;
case "authentik_stages_authenticator_webauthn.WebAuthnDevice":
await new AuthenticatorsApi(DEFAULT_CONFIG).authenticatorsWebauthnUpdate({
id: this.instance?.pk,
id: parseInt(this.instance?.pk, 10),
webAuthnDeviceRequest: device,
});
break;
default:
break;
throw new SentryIgnoredError(
msg(str`Device type ${device.verboseName} cannot be edited`),
);
}
return device;
}

View File

@ -1,4 +1,5 @@
import { AndNext, DEFAULT_CONFIG } from "@goauthentik/common/api/config";
import { SentryIgnoredError } from "@goauthentik/common/errors";
import { deviceTypeName } from "@goauthentik/common/labels";
import { getRelativeTime } from "@goauthentik/common/utils";
import "@goauthentik/elements/buttons/Dropdown";
@ -10,7 +11,7 @@ import { PaginatedResponse, Table, TableColumn } from "@goauthentik/elements/tab
import "@goauthentik/user/user-settings/mfa/MFADeviceForm";
import "@patternfly/elements/pf-tooltip/pf-tooltip.js";
import { msg } from "@lit/localize";
import { msg, str } from "@lit/localize";
import { TemplateResult, html } from "lit";
import { customElement, property } from "lit/decorators.js";
import { ifDefined } from "lit/directives/if-defined.js";
@ -89,7 +90,7 @@ export class MFADevicesPage extends Table<Device> {
async deleteWrapper(device: Device) {
const api = new AuthenticatorsApi(DEFAULT_CONFIG);
const id = { id: device.pk };
const id = { id: parseInt(device.pk, 10) };
switch (device.type) {
case "authentik_stages_authenticator_duo.DuoDevice":
return api.authenticatorsDuoDestroy(id);
@ -102,7 +103,9 @@ export class MFADevicesPage extends Table<Device> {
case "authentik_stages_authenticator_webauthn.WebAuthnDevice":
return api.authenticatorsWebauthnDestroy(id);
default:
break;
throw new SentryIgnoredError(
msg(str`Device type ${device.verboseName} cannot be deleted`),
);
}
}

View File

@ -0,0 +1,78 @@
---
title: Endpoint Authenticator Google Device Trust Connector Stage
---
<span class="badge badge--primary">Enterprise</span>
<span class="badge badge--version">authentik 2024.10+</span>
---
With this stage, authentik can validate users' Chrome browsers and ensure that users' devices are compliant and up-to-date.
:::info
This stage only works with Google Chrome, as it relies on the [Chrome Verified Access API](https://developers.google.com/chrome/verified-access).
:::
## Configuration
The main steps to set up your Google workspace are as follows:
1. [Create your Google Cloud Project](#create-a-google-cloud-project)
2. [Create a service account](#create-a-service-account)
3. [Set credentials for the service account](#set-credentials-for-the-service-account)
4. [Define access and scope in the Admin Console](#set-credentials-for-the-service-account)
For detailed instructions, refer to Google documentation.
### Create a Google cloud project
1. Open the Google Cloud Console (https://cloud.google.com/cloud-console).
2. In upper left, click the drop-down box to open the **Select a project** modal box, and then select **New Project**.
3. Create a new project and give it a name like "authentik GWS".
4. Use the search bar at the top of your new project page to search for "API Library".
5. On the **API Library** page, use the search bar again to find "Chrome Verified Access API".
6. On the **Chrome Verified Access API** page, click **Enable**.
### Create a service account
1. After the new Chrome Verified Access API is enabled (it might take a few minutes), return to the Google Cloud console home page (click on **Google Cloud** in upper left).
2. Use the search bar to find and navigate to the **IAM** page.
3. On the **IAM** page, click **Service Accounts** in the left navigation pane.
4. At the top of the **Service Accounts** page, click **Create Service Account**.
- Under **Service account details** page, define the **Name** and **Description** for the new service account, and then click **Create and Continue**.
- Under **Grant this service account access to project** you do not need to define a role, so click **Continue**.
- Under **Grant users access to project** you do not need to define a role, so click **Done** to complete the creation of the service account.
### Set credentials for the service account
1. On the **Service accounts** page, click the account that you just created.
2. Click the **Keys** tab at top of the page, the click **Add Key -> Create new key**.
3. In the Create modal box, select JSON as the key type, and then click **Create**.
A pop-up displays with the private key, and the key is saved to your computer as a JSON file.
Later, when you create the stage in authentik, you will add this key in the **Credentials** field.
4. On the service account page, click the **Details** tab, and expand the **Advanced settings** area.
5. Log in to the Admin Console, and then navigate to **Chrome browser -> Connectors**.
6. Click on **New Provider Configuration**.
7. Under Okta, click "Set up".
8. Enter a name.
9. Enter the URL: https://authentik.company/endpoint/gdtc/chrome/
10. Under Service accounts, enter the full name of the service account created above, for example `authentik-gdtc-docs@authentik-enterprise-dev.iam.gserviceaccount.com`.
### Create the stage
1. Log in as an admin to authentik, and go to the Admin interface.
2. In the Admin interface, navigate to **Flows -> Stages**.
3. Click **Create**, and select **Endpoint Authenticator Google Device Trust Connector Stage**, and in the **New stage** modal box, define the following fields:
- **Name**: define a descriptive name, such as "chrome-device-trust".
- **Google Verified Access API**
- **Credentials**: paste the contents of the JSON file (the key) that you downloaded earlier.
4. Click **Finish**.
After creating the stage, it can be used in any flow. Compared to other Authenticator stages, this stage does not require enrollment. Instead of adding an [Authenticator Validation Stage](../authenticator_validate/index.md), this stage only verifies the users' browser.

View File

@ -282,6 +282,7 @@ export default {
},
items: [
"add-secure-apps/flows-stages/stages/authenticator_duo/index",
"add-secure-apps/flows-stages/stages/authenticator_endpoint_gdtc/index",
"add-secure-apps/flows-stages/stages/authenticator_sms/index",
"add-secure-apps/flows-stages/stages/authenticator_static/index",
"add-secure-apps/flows-stages/stages/authenticator_totp/index",