Skip to content

Commit

Permalink
rework
Browse files Browse the repository at this point in the history
Signed-off-by: Jens Langhammer <[email protected]>
  • Loading branch information
BeryJu committed Oct 14, 2024
1 parent 6b79190 commit cdb7010
Show file tree
Hide file tree
Showing 21 changed files with 1,609 additions and 13,617 deletions.
1 change: 1 addition & 0 deletions authentik/enterprise/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,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",
]

Expand Down
83 changes: 83 additions & 0 deletions authentik/enterprise/stages/authenticator_endpoint_gdtc/api.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
"""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.stages.authenticator_endpoint_gdtc.models import (
AuthenticatorEndpointGDTCStage,
EndpointDevice,
)
from authentik.flows.api.stages import StageSerializer

LOGGER = get_logger()


class AuthenticatorEndpointGDTCStageSerializer(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.UpdateModelMixin,
mixins.DestroyModelMixin,
UsedByMixin,
mixins.ListModelMixin,
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"]
13 changes: 13 additions & 0 deletions authentik/enterprise/stages/authenticator_endpoint_gdtc/apps.py
Original file line number Diff line number Diff line change
@@ -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/"
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
# Generated by Django 5.0.9 on 2024-10-14 17:08

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,
),
),
("hostname", models.TextField()),
(
"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",
),
),
],
),
]
Empty file.
102 changes: 102 additions & 0 deletions authentik/enterprise/stages/authenticator_endpoint_gdtc/models.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
"""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 {

Check warning on line 24 in authentik/enterprise/stages/authenticator_endpoint_gdtc/models.py

View check run for this annotation

Codecov / codecov/patch

authentik/enterprise/stages/authenticator_endpoint_gdtc/models.py#L24

Added line #L24 was not covered by tests
"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 (

Check warning on line 32 in authentik/enterprise/stages/authenticator_endpoint_gdtc/models.py

View check run for this annotation

Codecov / codecov/patch

authentik/enterprise/stages/authenticator_endpoint_gdtc/models.py#L32

Added line #L32 was not covered by tests
AuthenticatorEndpointStageSerializer,
)

return AuthenticatorEndpointStageSerializer

Check warning on line 36 in authentik/enterprise/stages/authenticator_endpoint_gdtc/models.py

View check run for this annotation

Codecov / codecov/patch

authentik/enterprise/stages/authenticator_endpoint_gdtc/models.py#L36

Added line #L36 was not covered by tests

@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"

Check warning on line 48 in authentik/enterprise/stages/authenticator_endpoint_gdtc/models.py

View check run for this annotation

Codecov / codecov/patch

authentik/enterprise/stages/authenticator_endpoint_gdtc/models.py#L48

Added line #L48 was not covered by tests

def ui_user_settings(self) -> UserSettingSerializer | None:
return UserSettingSerializer(

Check warning on line 51 in authentik/enterprise/stages/authenticator_endpoint_gdtc/models.py

View check run for this annotation

Codecov / codecov/patch

authentik/enterprise/stages/authenticator_endpoint_gdtc/models.py#L51

Added line #L51 was not covered by tests
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",
)

hostname = models.TextField()

user = models.ForeignKey(get_user_model(), on_delete=models.CASCADE)

@property
def serializer(self) -> Serializer:
from authentik.enterprise.stages.authenticator_endpoint_gdtc.api import (

Check warning on line 81 in authentik/enterprise/stages/authenticator_endpoint_gdtc/models.py

View check run for this annotation

Codecov / codecov/patch

authentik/enterprise/stages/authenticator_endpoint_gdtc/models.py#L81

Added line #L81 was not covered by tests
EndpointDeviceSerializer,
)

return EndpointDeviceSerializer

Check warning on line 85 in authentik/enterprise/stages/authenticator_endpoint_gdtc/models.py

View check run for this annotation

Codecov / codecov/patch

authentik/enterprise/stages/authenticator_endpoint_gdtc/models.py#L85

Added line #L85 was not covered by tests

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}"
29 changes: 29 additions & 0 deletions authentik/enterprise/stages/authenticator_endpoint_gdtc/stage.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
from django.http import HttpResponse
from django.urls import reverse

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(

Check warning on line 19 in authentik/enterprise/stages/authenticator_endpoint_gdtc/stage.py

View check run for this annotation

Codecov / codecov/patch

authentik/enterprise/stages/authenticator_endpoint_gdtc/stage.py#L19

Added line #L19 was not covered by tests
data={
"component": "xak-flow-frame",
"url": self.request.build_absolute_uri(
reverse("authentik_stages_authenticator_endpoint_gdtc:chrome")
),
}
)

def challenge_valid(self, response: ChallengeResponse) -> HttpResponse:
return self.executor.stage_ok()

Check warning on line 29 in authentik/enterprise/stages/authenticator_endpoint_gdtc/stage.py

View check run for this annotation

Codecov / codecov/patch

authentik/enterprise/stages/authenticator_endpoint_gdtc/stage.py#L29

Added line #L29 was not covered by tests
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<html>
<script>
window.parent.postMessage({
message: "submit",
source: "goauthentik.io",
context: "flow-executor"
});
</script>
</html>
26 changes: 26 additions & 0 deletions authentik/enterprise/stages/authenticator_endpoint_gdtc/urls.py
Original file line number Diff line number Diff line change
@@ -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.google_chrome.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),
]
Empty file.
Empty file.
Loading

0 comments on commit cdb7010

Please sign in to comment.