core: add user metrics API
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
This commit is contained in:
parent
3668850e8f
commit
4c49209f71
|
@ -13,7 +13,7 @@ from rest_framework.permissions import IsAdminUser
|
||||||
from rest_framework.request import Request
|
from rest_framework.request import Request
|
||||||
from rest_framework.response import Response
|
from rest_framework.response import Response
|
||||||
from rest_framework.serializers import Serializer
|
from rest_framework.serializers import Serializer
|
||||||
from rest_framework.viewsets import ViewSet
|
from rest_framework.viewsets import ReadOnlyModelViewSet
|
||||||
|
|
||||||
from authentik.events.models import Event, EventAction
|
from authentik.events.models import Event, EventAction
|
||||||
|
|
||||||
|
@ -81,7 +81,7 @@ class LoginMetricsSerializer(Serializer):
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
|
|
||||||
class AdministrationMetricsViewSet(ViewSet):
|
class AdministrationMetricsViewSet(ReadOnlyModelViewSet):
|
||||||
"""Login Metrics per 1h"""
|
"""Login Metrics per 1h"""
|
||||||
|
|
||||||
permission_classes = [IsAdminUser]
|
permission_classes = [IsAdminUser]
|
||||||
|
|
|
@ -1,10 +1,12 @@
|
||||||
{% load static %}
|
{% load static %}
|
||||||
|
{% load i18n %}
|
||||||
|
|
||||||
<!doctype html> <!-- Important: must specify -->
|
<!doctype html> <!-- Important: must specify -->
|
||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8"> <!-- Important: rapi-doc uses utf8 charecters -->
|
<meta charset="utf-8"> <!-- Important: rapi-doc uses utf8 charecters -->
|
||||||
<script type="module" src="{% static 'dist/rapidoc-min.js' %}"></script>
|
<script type="module" src="{% static 'dist/rapidoc-min.js' %}"></script>
|
||||||
|
<title>{% block title %}{% trans title|default:config.authentik.branding.title %}{% endblock %}</title>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<rapi-doc
|
<rapi-doc
|
||||||
|
|
|
@ -1,14 +1,17 @@
|
||||||
"""User API Views"""
|
"""User API Views"""
|
||||||
from drf_yasg2.utils import swagger_auto_schema
|
from django.db.models.base import Model
|
||||||
|
from drf_yasg2.utils import swagger_auto_schema, swagger_serializer_method
|
||||||
from guardian.utils import get_anonymous_user
|
from guardian.utils import get_anonymous_user
|
||||||
from rest_framework.decorators import action
|
from rest_framework.decorators import action
|
||||||
from rest_framework.fields import CharField
|
from rest_framework.fields import CharField, SerializerMethodField
|
||||||
from rest_framework.request import Request
|
from rest_framework.request import Request
|
||||||
from rest_framework.response import Response
|
from rest_framework.response import Response
|
||||||
from rest_framework.serializers import BooleanField, ModelSerializer
|
from rest_framework.serializers import BooleanField, ModelSerializer, Serializer
|
||||||
from rest_framework.viewsets import ModelViewSet
|
from rest_framework.viewsets import ModelViewSet
|
||||||
|
|
||||||
|
from authentik.admin.api.metrics import CoordinateSerializer, get_events_per_1h
|
||||||
from authentik.core.models import User
|
from authentik.core.models import User
|
||||||
|
from authentik.events.models import EventAction
|
||||||
|
|
||||||
|
|
||||||
class UserSerializer(ModelSerializer):
|
class UserSerializer(ModelSerializer):
|
||||||
|
@ -33,6 +36,42 @@ class UserSerializer(ModelSerializer):
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
class UserMetricsSerializer(Serializer):
|
||||||
|
"""User Metrics"""
|
||||||
|
|
||||||
|
logins_per_1h = SerializerMethodField()
|
||||||
|
logins_failed_per_1h = SerializerMethodField()
|
||||||
|
authorizations_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"""
|
||||||
|
request = self.context["request"]._request
|
||||||
|
return get_events_per_1h(action=EventAction.LOGIN, user__pk=request.user.pk)
|
||||||
|
|
||||||
|
@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"""
|
||||||
|
request = self.context["request"]._request
|
||||||
|
return get_events_per_1h(
|
||||||
|
action=EventAction.LOGIN_FAILED, context__username=request.user.username
|
||||||
|
)
|
||||||
|
|
||||||
|
@swagger_serializer_method(serializer_or_field=CoordinateSerializer(many=True))
|
||||||
|
def get_authorizations_per_1h(self, _):
|
||||||
|
"""Get failed logins per hour for the last 24 hours"""
|
||||||
|
request = self.context["request"]._request
|
||||||
|
return get_events_per_1h(
|
||||||
|
action=EventAction.AUTHORIZE_APPLICATION, user__pk=request.user.pk
|
||||||
|
)
|
||||||
|
|
||||||
|
def create(self, validated_data: dict) -> Model:
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
def update(self, instance: Model, validated_data: dict) -> Model:
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
|
||||||
class UserViewSet(ModelViewSet):
|
class UserViewSet(ModelViewSet):
|
||||||
"""User Viewset"""
|
"""User Viewset"""
|
||||||
|
|
||||||
|
@ -50,3 +89,11 @@ class UserViewSet(ModelViewSet):
|
||||||
def me(self, request: Request) -> Response:
|
def me(self, request: Request) -> Response:
|
||||||
"""Get information about current user"""
|
"""Get information about current user"""
|
||||||
return Response(UserSerializer(request.user).data)
|
return Response(UserSerializer(request.user).data)
|
||||||
|
|
||||||
|
@swagger_auto_schema(responses={200: UserMetricsSerializer(many=False)})
|
||||||
|
@action(detail=False)
|
||||||
|
def metrics(self, request: Request) -> Response:
|
||||||
|
"""User metrics per 1h"""
|
||||||
|
serializer = UserMetricsSerializer(True)
|
||||||
|
serializer.context["request"] = request
|
||||||
|
return Response(serializer.data)
|
||||||
|
|
107
swagger.yaml
107
swagger.yaml
|
@ -24,7 +24,27 @@ paths:
|
||||||
get:
|
get:
|
||||||
operationId: admin_metrics_list
|
operationId: admin_metrics_list
|
||||||
description: Login Metrics per 1h
|
description: Login Metrics per 1h
|
||||||
parameters: []
|
parameters:
|
||||||
|
- name: ordering
|
||||||
|
in: query
|
||||||
|
description: Which field to use when ordering the results.
|
||||||
|
required: false
|
||||||
|
type: string
|
||||||
|
- name: search
|
||||||
|
in: query
|
||||||
|
description: A search term.
|
||||||
|
required: false
|
||||||
|
type: string
|
||||||
|
- name: page
|
||||||
|
in: query
|
||||||
|
description: Page Index
|
||||||
|
required: false
|
||||||
|
type: integer
|
||||||
|
- name: page_size
|
||||||
|
in: query
|
||||||
|
description: Page Size
|
||||||
|
required: false
|
||||||
|
type: integer
|
||||||
responses:
|
responses:
|
||||||
'200':
|
'200':
|
||||||
description: Login Metrics per 1h
|
description: Login Metrics per 1h
|
||||||
|
@ -33,6 +53,21 @@ paths:
|
||||||
tags:
|
tags:
|
||||||
- admin
|
- admin
|
||||||
parameters: []
|
parameters: []
|
||||||
|
/admin/metrics/{id}/:
|
||||||
|
get:
|
||||||
|
operationId: admin_metrics_read
|
||||||
|
description: Login Metrics per 1h
|
||||||
|
parameters: []
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: ''
|
||||||
|
tags:
|
||||||
|
- admin
|
||||||
|
parameters:
|
||||||
|
- name: id
|
||||||
|
in: path
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
/admin/system_tasks/:
|
/admin/system_tasks/:
|
||||||
get:
|
get:
|
||||||
operationId: admin_system_tasks_list
|
operationId: admin_system_tasks_list
|
||||||
|
@ -1617,6 +1652,54 @@ paths:
|
||||||
tags:
|
tags:
|
||||||
- core
|
- core
|
||||||
parameters: []
|
parameters: []
|
||||||
|
/core/users/metrics/:
|
||||||
|
get:
|
||||||
|
operationId: core_users_metrics
|
||||||
|
description: User metrics per 1h
|
||||||
|
parameters:
|
||||||
|
- name: username
|
||||||
|
in: query
|
||||||
|
description: ''
|
||||||
|
required: false
|
||||||
|
type: string
|
||||||
|
- name: name
|
||||||
|
in: query
|
||||||
|
description: ''
|
||||||
|
required: false
|
||||||
|
type: string
|
||||||
|
- name: is_active
|
||||||
|
in: query
|
||||||
|
description: ''
|
||||||
|
required: false
|
||||||
|
type: string
|
||||||
|
- name: ordering
|
||||||
|
in: query
|
||||||
|
description: Which field to use when ordering the results.
|
||||||
|
required: false
|
||||||
|
type: string
|
||||||
|
- name: search
|
||||||
|
in: query
|
||||||
|
description: A search term.
|
||||||
|
required: false
|
||||||
|
type: string
|
||||||
|
- name: page
|
||||||
|
in: query
|
||||||
|
description: Page Index
|
||||||
|
required: false
|
||||||
|
type: integer
|
||||||
|
- name: page_size
|
||||||
|
in: query
|
||||||
|
description: Page Size
|
||||||
|
required: false
|
||||||
|
type: integer
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: User Metrics
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/UserMetrics'
|
||||||
|
tags:
|
||||||
|
- core
|
||||||
|
parameters: []
|
||||||
/core/users/{id}/:
|
/core/users/{id}/:
|
||||||
get:
|
get:
|
||||||
operationId: core_users_read
|
operationId: core_users_read
|
||||||
|
@ -10981,6 +11064,28 @@ definitions:
|
||||||
$ref: '#/definitions/User'
|
$ref: '#/definitions/User'
|
||||||
application:
|
application:
|
||||||
$ref: '#/definitions/Application'
|
$ref: '#/definitions/Application'
|
||||||
|
UserMetrics:
|
||||||
|
description: User Metrics
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
logins_per_1h:
|
||||||
|
description: ''
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
$ref: '#/definitions/Coordinate'
|
||||||
|
readOnly: true
|
||||||
|
logins_failed_per_1h:
|
||||||
|
description: ''
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
$ref: '#/definitions/Coordinate'
|
||||||
|
readOnly: true
|
||||||
|
authorizations_per_1h:
|
||||||
|
description: ''
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
$ref: '#/definitions/Coordinate'
|
||||||
|
readOnly: true
|
||||||
CertificateKeyPair:
|
CertificateKeyPair:
|
||||||
description: CertificateKeyPair Serializer
|
description: CertificateKeyPair Serializer
|
||||||
required:
|
required:
|
||||||
|
|
|
@ -197,6 +197,7 @@ class TestProviderOAuth2OIDCImplicit(SeleniumTestCase):
|
||||||
self.container = self.setup_client()
|
self.container = self.setup_client()
|
||||||
|
|
||||||
self.driver.get("http://localhost:9009/implicit/")
|
self.driver.get("http://localhost:9009/implicit/")
|
||||||
|
sleep(2)
|
||||||
self.login()
|
self.login()
|
||||||
|
|
||||||
self.wait.until(
|
self.wait.until(
|
||||||
|
|
Reference in a new issue