Skip to content

Commit

Permalink
Merge branch 'master' into version-2021.3
Browse files Browse the repository at this point in the history
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>

# Conflicts:
#	docker-compose.yml
#	helm/README.md
#	web/src/authentik.css
#	web/src/flows/FlowExecutor.ts
#	web/src/flows/stages/identification/IdentificationStage.ts
#	website/docs/installation/kubernetes.md
  • Loading branch information
BeryJu committed Mar 13, 2021
2 parents df7119b + fef5a5c commit 2713b05
Show file tree
Hide file tree
Showing 198 changed files with 39,533 additions and 6,088 deletions.
4 changes: 4 additions & 0 deletions .bumpversion.cfg
Expand Up @@ -36,3 +36,7 @@ values =
[bumpversion:file:outpost/pkg/version.go]

[bumpversion:file:web/src/constants.ts]

[bumpversion:file:website/docs/outpusts/manual-deploy-docker-compose.md]

[bumpversion:file:website/docs/outpusts/manual-deploy-kubernetes.md]
3 changes: 3 additions & 0 deletions .github/workflows/release.yml
Expand Up @@ -59,6 +59,9 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v1
- name: prepare ts api client
run: |
docker run --rm -v $(pwd):/local openapitools/openapi-generator-cli generate -i /local/swagger.yaml -g typescript-fetch -o /local/web/src/api --additional-properties=typescriptThreePlus=true
- name: Docker Login Registry
env:
DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }}
Expand Down
1 change: 1 addition & 0 deletions Dockerfile
Expand Up @@ -45,4 +45,5 @@ COPY ./lifecycle/ /lifecycle
USER authentik
STOPSIGNAL SIGINT
ENV TMPDIR /dev/shm/
ENV PYTHONUBUFFERED 1
ENTRYPOINT [ "/lifecycle/bootstrap.sh" ]
178 changes: 115 additions & 63 deletions Pipfile.lock

Large diffs are not rendered by default.

30 changes: 23 additions & 7 deletions authentik/admin/api/metrics.py
Expand Up @@ -7,8 +7,8 @@
from django.db.models.fields import DurationField
from django.db.models.functions import ExtractHour
from django.utils.timezone import now
from drf_yasg2.utils import swagger_auto_schema
from rest_framework.fields import SerializerMethodField
from drf_yasg2.utils import swagger_auto_schema, swagger_serializer_method
from rest_framework.fields import IntegerField, SerializerMethodField
from rest_framework.permissions import IsAdminUser
from rest_framework.request import Request
from rest_framework.response import Response
Expand Down Expand Up @@ -37,23 +37,39 @@ def get_events_per_1h(**filter_kwargs) -> list[dict[str, int]]:
for hour in range(0, -24, -1):
results.append(
{
"x": time.mktime((_now + timedelta(hours=hour)).timetuple()) * 1000,
"y": data[hour * -1],
"x_cord": time.mktime((_now + timedelta(hours=hour)).timetuple())
* 1000,
"y_cord": data[hour * -1],
}
)
return results


class AdministrationMetricsSerializer(Serializer):
class CoordinateSerializer(Serializer):
"""Coordinates for diagrams"""

x_cord = IntegerField(read_only=True)
y_cord = IntegerField(read_only=True)

def create(self, validated_data: dict) -> Model:
raise NotImplementedError

def update(self, instance: Model, validated_data: dict) -> Model:
raise NotImplementedError


class LoginMetricsSerializer(Serializer):
"""Login Metrics per 1h"""

logins_per_1h = SerializerMethodField()
logins_failed_per_1h = SerializerMethodField()

@swagger_serializer_method(serializer_or_field=CoordinateSerializer(many=True))
def get_logins_per_1h(self, _):
"""Get successful logins per hour for the last 24 hours"""
return get_events_per_1h(action=EventAction.LOGIN)

@swagger_serializer_method(serializer_or_field=CoordinateSerializer(many=True))
def get_logins_failed_per_1h(self, _):
"""Get failed logins per hour for the last 24 hours"""
return get_events_per_1h(action=EventAction.LOGIN_FAILED)
Expand All @@ -70,8 +86,8 @@ class AdministrationMetricsViewSet(ViewSet):

permission_classes = [IsAdminUser]

@swagger_auto_schema(responses={200: AdministrationMetricsSerializer(many=True)})
@swagger_auto_schema(responses={200: LoginMetricsSerializer(many=False)})
def list(self, request: Request) -> Response:
"""Login Metrics per 1h"""
serializer = AdministrationMetricsSerializer(True)
serializer = LoginMetricsSerializer(True)
return Response(serializer.data)
4 changes: 2 additions & 2 deletions authentik/admin/api/tasks.py
Expand Up @@ -25,8 +25,8 @@ class TaskSerializer(Serializer):
task_finish_timestamp = DateTimeField(source="finish_timestamp")

status = ChoiceField(
source="result.status.value",
choices=[(x.value, x.name) for x in TaskResultStatus],
source="result.status.name",
choices=[(x.name, x.name) for x in TaskResultStatus],
)
messages = ListField(source="result.messages")

Expand Down
61 changes: 40 additions & 21 deletions authentik/api/v2/urls.py
@@ -1,4 +1,5 @@
"""api v2 urls"""
from django.conf import settings
from django.urls import path, re_path
from drf_yasg2 import openapi
from drf_yasg2.views import get_schema_view
Expand Down Expand Up @@ -54,12 +55,24 @@
from authentik.sources.ldap.api import LDAPPropertyMappingViewSet, LDAPSourceViewSet
from authentik.sources.oauth.api import OAuthSourceViewSet
from authentik.sources.saml.api import SAMLSourceViewSet
from authentik.stages.authenticator_static.api import AuthenticatorStaticStageViewSet
from authentik.stages.authenticator_totp.api import AuthenticatorTOTPStageViewSet
from authentik.stages.authenticator_static.api import (
AuthenticatorStaticStageViewSet,
StaticAdminDeviceViewSet,
StaticDeviceViewSet,
)
from authentik.stages.authenticator_totp.api import (
AuthenticatorTOTPStageViewSet,
TOTPAdminDeviceViewSet,
TOTPDeviceViewSet,
)
from authentik.stages.authenticator_validate.api import (
AuthenticatorValidateStageViewSet,
)
from authentik.stages.authenticator_webauthn.api import AuthenticateWebAuthnStageViewSet
from authentik.stages.authenticator_webauthn.api import (
AuthenticateWebAuthnStageViewSet,
WebAuthnAdminDeviceViewSet,
WebAuthnDeviceViewSet,
)
from authentik.stages.captcha.api import CaptchaStageViewSet
from authentik.stages.consent.api import ConsentStageViewSet
from authentik.stages.deny.api import DenyStageViewSet
Expand Down Expand Up @@ -133,6 +146,13 @@
router.register("propertymappings/saml", SAMLPropertyMappingViewSet)
router.register("propertymappings/scope", ScopeMappingViewSet)

router.register("authenticators/static", StaticDeviceViewSet)
router.register("authenticators/totp", TOTPDeviceViewSet)
router.register("authenticators/webauthn", WebAuthnDeviceViewSet)
router.register("authenticators/admin/static", StaticAdminDeviceViewSet)
router.register("authenticators/admin/totp", TOTPAdminDeviceViewSet)
router.register("authenticators/admin/webauthn", WebAuthnAdminDeviceViewSet)

router.register("stages/all", StageViewSet)
router.register("stages/authenticator/static", AuthenticatorStaticStageViewSet)
router.register("stages/authenticator/totp", AuthenticatorTOTPStageViewSet)
Expand Down Expand Up @@ -164,27 +184,26 @@
name="GNU GPLv3", url="https://github.com/BeryJu/authentik/blob/master/LICENSE"
),
)
SchemaView = get_schema_view(
info,
public=True,
permission_classes=(AllowAny,),
)
SchemaView = get_schema_view(info, public=True, permission_classes=(AllowAny,))

urlpatterns = [
re_path(
r"^swagger(?P<format>\.json|\.yaml)$",
SchemaView.without_ui(cache_timeout=0),
name="schema-json",
),
path(
"swagger/",
SchemaView.with_ui("swagger", cache_timeout=0),
name="schema-swagger-ui",
),
path("redoc/", SchemaView.with_ui("redoc", cache_timeout=0), name="schema-redoc"),
urlpatterns = router.urls + [
path(
"flows/executor/<slug:flow_slug>/",
FlowExecutorView.as_view(),
name="flow-executor",
),
] + router.urls
re_path(
r"^swagger(?P<format>\.json|\.yaml)$",
SchemaView.without_ui(cache_timeout=0),
name="schema-json",
),
]

if settings.DEBUG:
urlpatterns = urlpatterns + [
path(
"swagger/",
SchemaView.with_ui("swagger", cache_timeout=0),
name="schema-swagger-ui",
),
]
4 changes: 3 additions & 1 deletion authentik/core/api/applications.py
Expand Up @@ -2,6 +2,7 @@
from django.core.cache import cache
from django.db.models import QuerySet
from django.http.response import Http404
from drf_yasg2.utils import swagger_auto_schema
from guardian.shortcuts import get_objects_for_user
from rest_framework.decorators import action
from rest_framework.fields import SerializerMethodField
Expand All @@ -13,7 +14,7 @@
from rest_framework_guardian.filters import ObjectPermissionsFilter
from structlog.stdlib import get_logger

from authentik.admin.api.metrics import get_events_per_1h
from authentik.admin.api.metrics import CoordinateSerializer, get_events_per_1h
from authentik.core.api.providers import ProviderSerializer
from authentik.core.models import Application
from authentik.events.models import EventAction
Expand Down Expand Up @@ -109,6 +110,7 @@ def list(self, request: Request) -> Response:
serializer = self.get_serializer(allowed_applications, many=True)
return self.get_paginated_response(serializer.data)

@swagger_auto_schema(responses={200: CoordinateSerializer(many=True)})
@action(detail=True)
def metrics(self, request: Request, slug: str):
"""Metrics for application logins"""
Expand Down
1 change: 1 addition & 0 deletions authentik/core/api/groups.py
Expand Up @@ -21,3 +21,4 @@ class GroupViewSet(ModelViewSet):
serializer_class = GroupSerializer
search_fields = ["name", "is_superuser"]
filterset_fields = ["name", "is_superuser"]
ordering = ["name"]
6 changes: 3 additions & 3 deletions authentik/core/api/utils.py
Expand Up @@ -28,9 +28,9 @@ def get_verbose_name_plural(self, obj: Model) -> str:
class TypeCreateSerializer(Serializer):
"""Types of an object that can be created"""

name = CharField(read_only=True)
description = CharField(read_only=True)
link = CharField(read_only=True)
name = CharField(required=True)
description = CharField(required=True)
link = CharField(required=True)

def create(self, validated_data: dict) -> Model:
raise NotImplementedError
Expand Down
4 changes: 0 additions & 4 deletions authentik/core/templates/generic/delete.html
@@ -1,9 +1,6 @@
{% extends container_template|default:"administration/base.html" %}

{% load i18n %}
{% load authentik_utils %}

{% block content %}
<section class="pf-c-page__main-section pf-m-light">
<div class="pf-c-content">
{% block above_form %}
Expand Down Expand Up @@ -38,4 +35,3 @@ <h1>
<input class="pf-c-button pf-m-danger" type="submit" form="delete-form" value="{% trans 'Delete' %}" />
<a class="pf-c-button pf-m-secondary" href="{% back %}">{% trans "Back" %}</a>
</footer>
{% endblock %}
3 changes: 2 additions & 1 deletion authentik/core/views/user.py
Expand Up @@ -7,6 +7,7 @@
)
from django.contrib.messages.views import SuccessMessageMixin
from django.http.response import HttpResponse
from django.urls import reverse_lazy
from django.utils.translation import gettext as _
from django.views.generic import UpdateView
from django.views.generic.base import TemplateView
Expand Down Expand Up @@ -34,7 +35,7 @@ class UserDetailsView(SuccessMessageMixin, LoginRequiredMixin, UpdateView):
form_class = UserDetailForm

success_message = _("Successfully updated user.")
success_url = "/"
success_url = reverse_lazy("authentik_core:user-details")

def get_object(self):
return self.request.user
Expand Down
2 changes: 1 addition & 1 deletion authentik/events/api/notification.py
Expand Up @@ -13,7 +13,7 @@ class NotificationSerializer(ModelSerializer):

body = ReadOnlyField()
severity = ReadOnlyField()
event = EventSerializer()
event = EventSerializer(required=False)

class Meta:

Expand Down
27 changes: 24 additions & 3 deletions authentik/events/api/notification_transport.py
@@ -1,11 +1,12 @@
"""NotificationTransport API Views"""
from django.http.response import Http404
from drf_yasg2.utils import no_body, swagger_auto_schema
from guardian.shortcuts import get_objects_for_user
from rest_framework.decorators import action
from rest_framework.fields import SerializerMethodField
from rest_framework.fields import CharField, ListField, SerializerMethodField
from rest_framework.request import Request
from rest_framework.response import Response
from rest_framework.serializers import ModelSerializer
from rest_framework.serializers import ModelSerializer, Serializer
from rest_framework.viewsets import ModelViewSet

from authentik.events.models import (
Expand Down Expand Up @@ -38,12 +39,28 @@ class Meta:
]


class NotificationTransportTestSerializer(Serializer):
"""Notification test serializer"""

messages = ListField(child=CharField())

def create(self, request: Request) -> Response:
raise NotImplementedError

def update(self, request: Request) -> Response:
raise NotImplementedError


class NotificationTransportViewSet(ModelViewSet):
"""NotificationTransport Viewset"""

queryset = NotificationTransport.objects.all()
serializer_class = NotificationTransportSerializer

@swagger_auto_schema(
responses={200: NotificationTransportTestSerializer(many=False)},
request_body=no_body,
)
@action(detail=True, methods=["post"])
# pylint: disable=invalid-name
def test(self, request: Request, pk=None) -> Response:
Expand All @@ -61,6 +78,10 @@ def test(self, request: Request, pk=None) -> Response:
user=request.user,
)
try:
return Response(transport.send(notification))
response = NotificationTransportTestSerializer(
data={"messages": transport.send(notification)}
)
response.is_valid()
return Response(response.data)
except NotificationTransportError as exc:
return Response(str(exc.__cause__ or None), status=503)
5 changes: 4 additions & 1 deletion authentik/events/geo.py
Expand Up @@ -12,7 +12,10 @@ def get_geoip_reader() -> Optional[Reader]:
path = CONFIG.y("authentik.geoip")
if path == "" or not path:
return None
return Reader(path)
try:
return Reader(path)
except OSError:
return None


GEOIP_READER = get_geoip_reader()
6 changes: 4 additions & 2 deletions authentik/flows/challenge.py
Expand Up @@ -38,7 +38,9 @@ class Challenge(Serializer):
"""Challenge that gets sent to the client based on which stage
is currently active"""

type = ChoiceField(choices=list(ChallengeTypes))
type = ChoiceField(
choices=[(x.name, x.name) for x in ChallengeTypes],
)
component = CharField(required=False)
title = CharField(required=False)

Expand Down Expand Up @@ -90,7 +92,7 @@ class ChallengeResponse(Serializer):

stage: Optional["StageView"]

def __init__(self, instance, data, **kwargs):
def __init__(self, instance=None, data=None, **kwargs):
self.stage = kwargs.pop("stage", None)
super().__init__(instance=instance, data=data, **kwargs)

Expand Down
5 changes: 3 additions & 2 deletions authentik/flows/stage.py
Expand Up @@ -4,6 +4,7 @@
from django.http.request import QueryDict
from django.http.response import HttpResponse
from django.views.generic.base import View
from rest_framework.request import Request
from structlog.stdlib import get_logger

from authentik.core.models import DEFAULT_AVATAR, User
Expand Down Expand Up @@ -67,9 +68,9 @@ def get(self, request: HttpRequest, *args, **kwargs) -> HttpResponse:
return HttpChallengeResponse(challenge)

# pylint: disable=unused-argument
def post(self, request: HttpRequest, *args, **kwargs) -> HttpResponse:
def post(self, request: Request, *args, **kwargs) -> HttpResponse:
"""Handle challenge response"""
challenge: ChallengeResponse = self.get_response_instance(data=request.POST)
challenge: ChallengeResponse = self.get_response_instance(data=request.data)
if not challenge.is_valid():
return self.challenge_invalid(challenge)
return self.challenge_valid(challenge)
Expand Down

0 comments on commit 2713b05

Please sign in to comment.