core: add user metrics API

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
This commit is contained in:
Jens Langhammer 2021-03-20 17:30:01 +01:00
parent 3668850e8f
commit 4c49209f71
5 changed files with 161 additions and 6 deletions

View File

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

View File

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

View File

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

View File

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

View File

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