Health Check for Ticket #50 (#51)

This brings a health check with authentification

Reviewed-on: #51
Co-authored-by: Juergen Edelbluth <jed@noreply.git.codebau.dev>
Co-committed-by: Juergen Edelbluth <jed@noreply.git.codebau.dev>
This commit is contained in:
Jürgen Edelbluth 2024-04-14 22:00:58 +02:00 committed by Jürgen Edelbluth
parent 769241c170
commit 77898d59bc
Signed by: git.codebau.dev
GPG Key ID: F798C6B4352E8035
61 changed files with 482 additions and 85 deletions

View File

@ -3,5 +3,5 @@
<component name="Black">
<option name="sdkName" value="Poetry (platform)" />
</component>
<component name="ProjectRootManager" version="2" project-jdk-name="Poetry (platform)" project-jdk-type="Python SDK" />
<component name="ProjectRootManager" version="2" project-jdk-name="Poetry (platform) (2)" project-jdk-type="Python SDK" />
</project>

View File

@ -14,7 +14,7 @@
</component>
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$" />
<orderEntry type="jdk" jdkName="Poetry (platform)" jdkType="Python SDK" />
<orderEntry type="jdk" jdkName="Poetry (platform) (2)" jdkType="Python SDK" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
<component name="TemplatesService">

View File

@ -0,0 +1,27 @@
*** Settings ***
Documentation Check the registration and login procedures
Resource ../Keywords/django.resource
Library Browser
Library String
Suite Setup Solawi-Suite Inner Setup
Suite Teardown Solawi-Suite Inner Teardown
*** Test Cases ***
Invalid Credentials
Open Page /
${response} = Http /health/
Should Be Equal As Integers ${response.status} 403
Valid Credentials
${token_result} = Run Manage.Py Command create_health_api_access_token
${token_data} = Decode Bytes To String ${token_result.stdout} utf-8
@{splits} = Split String ${token_data} :
Length Should Be ${splits} 4
${header} = Strip String ${splits[1]}
${token_id} = Strip String ${splits[2]}
${token_code} = Strip String ${splits[3]}
${auth_token} = Set Variable ${token_id}:${token_code}
Open Page /
${response} = Http /health/ headers={"${header}": "${auth_token}"}
Should Be Equal As Integers ${response.status} 200

4
health-check.http Normal file
View File

@ -0,0 +1,4 @@
GET {{host}}/health/
Accept: application/json
X-Solawi-Suite-Access-Token: 7zzbekqqt7z3k6d2meyz0gnju1impapy:t78Wjywk9hqTXJYo928MYmRh_DWFdQIjVKEh$d6MCwa$dD29J.Tn3K-SVGpYWrBdZ3VKcXfv0u30QL-.oQJFL2EHWwULR-h6cBbrBaIoW.iNG4yUhn0koEmfNkmlFCFD
###

8
http-client.env.json Normal file
View File

@ -0,0 +1,8 @@
{
"dev": {
"host": "http://localhost:8000"
},
"prod": {
"host": "https://solawi.me"
}
}

33
poetry.lock generated
View File

@ -999,6 +999,24 @@ pubcontrol = ">=3.0,<4"
six = ">=1.10,<2"
Werkzeug = ">=1.0,<4"
[[package]]
name = "django-health-check"
version = "3.18.1"
description = "Run checks on services like databases, queue servers, celery processes, etc."
optional = false
python-versions = ">=3.8"
files = [
{file = "django-health-check-3.18.1.tar.gz", hash = "sha256:44552d55ae8950c9548d3b90f9d9fd5570b57446a19b2a8e674c82f993cb7a2c"},
{file = "django_health_check-3.18.1-py2.py3-none-any.whl", hash = "sha256:2c89a326cd79830e2fc6808823a9e7e874ab23f7aef3ff2c4d1194c998e1dca1"},
]
[package.dependencies]
django = ">=2.2"
[package.extras]
docs = ["sphinx"]
test = ["celery", "pytest", "pytest-cov", "pytest-django", "redis"]
[[package]]
name = "django-ipware"
version = "6.0.5"
@ -1770,13 +1788,13 @@ files = [
[[package]]
name = "locust"
version = "2.24.1"
version = "2.25.0"
description = "Developer friendly load testing framework"
optional = false
python-versions = ">=3.8"
files = [
{file = "locust-2.24.1-py3-none-any.whl", hash = "sha256:7f6ed4dc289aad66c304582e6d25e4de5d7c3b175b580332442ab2be35b9d916"},
{file = "locust-2.24.1.tar.gz", hash = "sha256:094161d44d94839bf1120fd7898b7abb9c143833743ba7c096beb470a236b9a7"},
{file = "locust-2.25.0-py3-none-any.whl", hash = "sha256:35ee14d0a2b91d0d644150d0b628ce4569b0e1fec1c33c55040fa26cc693d085"},
{file = "locust-2.25.0.tar.gz", hash = "sha256:45bc88b3097f0346a46514f99ebf8d8a86f07325366da0b9dc2c3f207499dbc6"},
]
[package.dependencies]
@ -3089,7 +3107,6 @@ files = [
{file = "PyYAML-6.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:bf07ee2fef7014951eeb99f56f39c9bb4af143d8aa3c21b1677805985307da34"},
{file = "PyYAML-6.0.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:855fb52b0dc35af121542a76b9a84f8d1cd886ea97c84703eaa6d88e37a2ad28"},
{file = "PyYAML-6.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40df9b996c2b73138957fe23a16a4f0ba614f4c0efce1e9406a184b6d07fa3a9"},
{file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a08c6f0fe150303c1c6b71ebcd7213c2858041a7e01975da3a99aed1e7a378ef"},
{file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c22bec3fbe2524cde73d7ada88f6566758a8f7227bfbf93a408a9d86bcc12a0"},
{file = "PyYAML-6.0.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8d4e9c88387b0f5c7d5f281e55304de64cf7f9c0021a3525bd3b1c542da3b0e4"},
{file = "PyYAML-6.0.1-cp312-cp312-win32.whl", hash = "sha256:d483d2cdf104e7c9fa60c544d92981f12ad66a457afae824d146093b8c294c54"},
@ -3587,13 +3604,13 @@ tests = ["coverage[toml] (>=5.0.2)", "pytest"]
[[package]]
name = "setuptools"
version = "69.5.0"
version = "69.5.1"
description = "Easily download, build, install, upgrade, and uninstall Python packages"
optional = false
python-versions = ">=3.8"
files = [
{file = "setuptools-69.5.0-py3-none-any.whl", hash = "sha256:3b2dbd8f63dcc6b7c327d0243c2d7dc8c96cc507c016f09221f3787e6e528719"},
{file = "setuptools-69.5.0.tar.gz", hash = "sha256:8d881f842bfc0e29e93bc98a2e650e8845609adff4d2989ba6c748e67b09d5be"},
{file = "setuptools-69.5.1-py3-none-any.whl", hash = "sha256:c636ac361bc47580504644275c9ad802c50415c7522212252c033bd15f301f32"},
{file = "setuptools-69.5.1.tar.gz", hash = "sha256:6c1fccdac05a97e598fb0ae3bbed5904ccb317337a51139dcd51453611bbb987"},
]
[package.extras]
@ -4193,4 +4210,4 @@ testing = ["coverage (>=5.0.3)", "zope.event", "zope.testing"]
[metadata]
lock-version = "2.0"
python-versions = "^3.11"
content-hash = "6878a3edf9d5eada0e4a5f875ebe0c497bfcd4aa0fc89d40d848488e2487c9a7"
content-hash = "7b498b2c2cb0053b8f4710c6b187476c68ec44fe6ed0ed42e213dd471b57905a"

View File

@ -1,6 +1,6 @@
[tool.poetry]
name = "platform"
version = "0.5.2-dev"
version = "0.5.3-dev"
description = "Plattform für die Apps der Solawi."
authors = ["Juergen Edelbluth <solawi@jued.de>"]
license = "Apache License 2.0"
@ -39,6 +39,7 @@ markdown = "^3.6"
django-csp = "^3.8"
django-cors-headers = "^4.3.1"
webauthn = "^2.1.0"
django-health-check = "^3.18.1"
[tool.poetry.group.dev.dependencies]

View File

@ -1,5 +1,5 @@
import django.db.models.deletion
import solawi_apps.db.dbid
import solawi_apps.db_lib.dbid
from django.conf import settings
from django.db import migrations, models
@ -16,7 +16,7 @@ class Migration(migrations.Migration):
migrations.CreateModel(
name='Ahg',
fields=[
('id', models.CharField(default=solawi_apps.db.dbid.generate_id, editable=False, max_length=32, primary_key=True, serialize=False, validators=[solawi_apps.db.dbid.validate_nanoid], verbose_name='ID')),
('id', models.CharField(default=solawi_apps.db_lib.dbid.generate_id, editable=False, max_length=32, primary_key=True, serialize=False, validators=[solawi_apps.db_lib.dbid.validate_nanoid], verbose_name='ID')),
('created', models.DateTimeField(auto_now_add=True, verbose_name='Erstellt')),
('last_modified', models.DateTimeField(auto_now=True, verbose_name='Zuletzt geändert')),
('name', models.CharField(max_length=255, verbose_name='Name')),
@ -33,7 +33,7 @@ class Migration(migrations.Migration):
migrations.CreateModel(
name='HarvestYear',
fields=[
('id', models.CharField(default=solawi_apps.db.dbid.generate_id, editable=False, max_length=32, primary_key=True, serialize=False, validators=[solawi_apps.db.dbid.validate_nanoid], verbose_name='ID')),
('id', models.CharField(default=solawi_apps.db_lib.dbid.generate_id, editable=False, max_length=32, primary_key=True, serialize=False, validators=[solawi_apps.db_lib.dbid.validate_nanoid], verbose_name='ID')),
('created', models.DateTimeField(auto_now_add=True, verbose_name='Erstellt')),
('last_modified', models.DateTimeField(auto_now=True, verbose_name='Zuletzt geändert')),
('year', models.CharField(max_length=255, verbose_name='Jahr')),
@ -50,7 +50,7 @@ class Migration(migrations.Migration):
migrations.CreateModel(
name='Date',
fields=[
('id', models.CharField(default=solawi_apps.db.dbid.generate_id, editable=False, max_length=32, primary_key=True, serialize=False, validators=[solawi_apps.db.dbid.validate_nanoid], verbose_name='ID')),
('id', models.CharField(default=solawi_apps.db_lib.dbid.generate_id, editable=False, max_length=32, primary_key=True, serialize=False, validators=[solawi_apps.db_lib.dbid.validate_nanoid], verbose_name='ID')),
('created', models.DateTimeField(auto_now_add=True, verbose_name='Erstellt')),
('last_modified', models.DateTimeField(auto_now=True, verbose_name='Zuletzt geändert')),
('enabled', models.BooleanField(default=False, verbose_name='Aktiviert')),
@ -69,7 +69,7 @@ class Migration(migrations.Migration):
migrations.CreateModel(
name='Member',
fields=[
('id', models.CharField(default=solawi_apps.db.dbid.generate_id, editable=False, max_length=32, primary_key=True, serialize=False, validators=[solawi_apps.db.dbid.validate_nanoid], verbose_name='ID')),
('id', models.CharField(default=solawi_apps.db_lib.dbid.generate_id, editable=False, max_length=32, primary_key=True, serialize=False, validators=[solawi_apps.db_lib.dbid.validate_nanoid], verbose_name='ID')),
('created', models.DateTimeField(auto_now_add=True, verbose_name='Erstellt')),
('last_modified', models.DateTimeField(auto_now=True, verbose_name='Zuletzt geändert')),
('enabled', models.BooleanField(default=False, verbose_name='Aktiviert')),
@ -90,7 +90,7 @@ class Migration(migrations.Migration):
migrations.CreateModel(
name='Team',
fields=[
('id', models.CharField(default=solawi_apps.db.dbid.generate_id, editable=False, max_length=32, primary_key=True, serialize=False, validators=[solawi_apps.db.dbid.validate_nanoid], verbose_name='ID')),
('id', models.CharField(default=solawi_apps.db_lib.dbid.generate_id, editable=False, max_length=32, primary_key=True, serialize=False, validators=[solawi_apps.db_lib.dbid.validate_nanoid], verbose_name='ID')),
('created', models.DateTimeField(auto_now_add=True, verbose_name='Erstellt')),
('last_modified', models.DateTimeField(auto_now=True, verbose_name='Zuletzt geändert')),
('enabled', models.BooleanField(default=False, verbose_name='Aktiviert')),
@ -108,7 +108,7 @@ class Migration(migrations.Migration):
migrations.CreateModel(
name='PlanMemberLink',
fields=[
('id', models.CharField(default=solawi_apps.db.dbid.generate_id, editable=False, max_length=32, primary_key=True, serialize=False, validators=[solawi_apps.db.dbid.validate_nanoid], verbose_name='ID')),
('id', models.CharField(default=solawi_apps.db_lib.dbid.generate_id, editable=False, max_length=32, primary_key=True, serialize=False, validators=[solawi_apps.db_lib.dbid.validate_nanoid], verbose_name='ID')),
('created', models.DateTimeField(auto_now_add=True, verbose_name='Erstellt')),
('last_modified', models.DateTimeField(auto_now=True, verbose_name='Zuletzt geändert')),
('date_fk', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='ahg.date', verbose_name='Datum')),
@ -124,7 +124,7 @@ class Migration(migrations.Migration):
migrations.CreateModel(
name='PlanTeamLink',
fields=[
('id', models.CharField(default=solawi_apps.db.dbid.generate_id, editable=False, max_length=32, primary_key=True, serialize=False, validators=[solawi_apps.db.dbid.validate_nanoid], verbose_name='ID')),
('id', models.CharField(default=solawi_apps.db_lib.dbid.generate_id, editable=False, max_length=32, primary_key=True, serialize=False, validators=[solawi_apps.db_lib.dbid.validate_nanoid], verbose_name='ID')),
('created', models.DateTimeField(auto_now_add=True, verbose_name='Erstellt')),
('last_modified', models.DateTimeField(auto_now=True, verbose_name='Zuletzt geändert')),
('date_fk', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='ahg.date', verbose_name='Datum')),
@ -140,7 +140,7 @@ class Migration(migrations.Migration):
migrations.CreateModel(
name='TeamMember',
fields=[
('id', models.CharField(default=solawi_apps.db.dbid.generate_id, editable=False, max_length=32, primary_key=True, serialize=False, validators=[solawi_apps.db.dbid.validate_nanoid], verbose_name='ID')),
('id', models.CharField(default=solawi_apps.db_lib.dbid.generate_id, editable=False, max_length=32, primary_key=True, serialize=False, validators=[solawi_apps.db_lib.dbid.validate_nanoid], verbose_name='ID')),
('created', models.DateTimeField(auto_now_add=True, verbose_name='Erstellt')),
('note', models.TextField(blank=True, default=None, max_length=100000, null=True, verbose_name='Notiz')),
('member_fk', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='ahg.member', verbose_name='Mitglied')),

View File

@ -1,7 +1,7 @@
from django.db import models
from django.utils.translation import gettext_lazy as _
from solawi_apps.db.models import NanoIdPkMixin, CreatedMixin, LastModifiedMixin, EnabledMixin, OwnerMixin
from solawi_apps.db_lib.models import NanoIdPkMixin, CreatedMixin, LastModifiedMixin, EnabledMixin, OwnerMixin
class Ahg(NanoIdPkMixin, OwnerMixin, CreatedMixin, LastModifiedMixin, models.Model):

View File

@ -1,7 +1,7 @@
from django.urls import path, register_converter
from solawi_apps.ahg.views.ahgs import AhgListView, AhgCreateView, AhgEditView, AhgDetailView, AhgDeleteView
from solawi_apps.db.dbid import NanoIdConverter
from solawi_apps.db_lib.dbid import NanoIdConverter
register_converter(NanoIdConverter, "nanoid")

View File

@ -1,5 +1,5 @@
import datetime
import solawi_apps.db.dbid
import solawi_apps.db_lib.dbid
from django.db import migrations, models
@ -14,7 +14,7 @@ class Migration(migrations.Migration):
migrations.CreateModel(
name='MemberAnnouncement',
fields=[
('id', models.CharField(default=solawi_apps.db.dbid.generate_id, editable=False, max_length=32, primary_key=True, serialize=False, validators=[solawi_apps.db.dbid.validate_nanoid], verbose_name='ID')),
('id', models.CharField(default=solawi_apps.db_lib.dbid.generate_id, editable=False, max_length=32, primary_key=True, serialize=False, validators=[solawi_apps.db_lib.dbid.validate_nanoid], verbose_name='ID')),
('created', models.DateTimeField(auto_now_add=True, verbose_name='Erstellt')),
('last_modified', models.DateTimeField(auto_now=True, verbose_name='Zuletzt geändert')),
('enabled', models.BooleanField(default=False, verbose_name='Aktiviert')),
@ -34,7 +34,7 @@ class Migration(migrations.Migration):
migrations.CreateModel(
name='PublicAnnouncement',
fields=[
('id', models.CharField(default=solawi_apps.db.dbid.generate_id, editable=False, max_length=32, primary_key=True, serialize=False, validators=[solawi_apps.db.dbid.validate_nanoid], verbose_name='ID')),
('id', models.CharField(default=solawi_apps.db_lib.dbid.generate_id, editable=False, max_length=32, primary_key=True, serialize=False, validators=[solawi_apps.db_lib.dbid.validate_nanoid], verbose_name='ID')),
('created', models.DateTimeField(auto_now_add=True, verbose_name='Erstellt')),
('last_modified', models.DateTimeField(auto_now=True, verbose_name='Zuletzt geändert')),
('enabled', models.BooleanField(default=False, verbose_name='Aktiviert')),

View File

@ -2,7 +2,7 @@ from django.contrib import messages
from django.db import models
from django.utils.translation import gettext_lazy as _
from solawi_apps.db.models import NanoIdPkMixin, EnabledMixin, ActiveFromMixin, ActiveToMixin, CreatedMixin, \
from solawi_apps.db_lib.models import NanoIdPkMixin, EnabledMixin, ActiveFromMixin, ActiveToMixin, CreatedMixin, \
LastModifiedMixin

View File

@ -1,7 +1,7 @@
import datetime
import django.db.models.deletion
import solawi_apps.app_permission.integration
import solawi_apps.db.dbid
import solawi_apps.db_lib.dbid
from django.conf import settings
from django.db import migrations, models
@ -18,7 +18,7 @@ class Migration(migrations.Migration):
migrations.CreateModel(
name='AccessPermission',
fields=[
('id', models.CharField(default=solawi_apps.db.dbid.generate_id, editable=False, max_length=32, primary_key=True, serialize=False, validators=[solawi_apps.db.dbid.validate_nanoid], verbose_name='ID')),
('id', models.CharField(default=solawi_apps.db_lib.dbid.generate_id, editable=False, max_length=32, primary_key=True, serialize=False, validators=[solawi_apps.db_lib.dbid.validate_nanoid], verbose_name='ID')),
('created', models.DateTimeField(auto_now_add=True, verbose_name='Erstellt')),
('last_modified', models.DateTimeField(auto_now=True, verbose_name='Zuletzt geändert')),
('enabled', models.BooleanField(default=False, verbose_name='Aktiviert')),

View File

@ -1,6 +1,6 @@
import datetime
import solawi_apps.app_permission.integration
import solawi_apps.db.dbid
import solawi_apps.db_lib.dbid
from django.db import migrations, models
@ -14,7 +14,7 @@ class Migration(migrations.Migration):
migrations.CreateModel(
name='DefaultPermission',
fields=[
('id', models.CharField(default=solawi_apps.db.dbid.generate_id, editable=False, max_length=32, primary_key=True, serialize=False, validators=[solawi_apps.db.dbid.validate_nanoid], verbose_name='ID')),
('id', models.CharField(default=solawi_apps.db_lib.dbid.generate_id, editable=False, max_length=32, primary_key=True, serialize=False, validators=[solawi_apps.db_lib.dbid.validate_nanoid], verbose_name='ID')),
('created', models.DateTimeField(auto_now_add=True, verbose_name='Erstellt')),
('last_modified', models.DateTimeField(auto_now=True, verbose_name='Zuletzt geändert')),
('enabled', models.BooleanField(default=False, verbose_name='Aktiviert')),

View File

@ -3,7 +3,7 @@ from django.db import models
from django.utils.translation import gettext_lazy as _
from solawi_apps.app_permission.integration import get_all_installed_app_labels
from solawi_apps.db.models import NanoIdPkMixin, EnabledMixin, ActiveFromMixin, ActiveToMixin, CreatedMixin, \
from solawi_apps.db_lib.models import NanoIdPkMixin, EnabledMixin, ActiveFromMixin, ActiveToMixin, CreatedMixin, \
LastModifiedMixin

View File

@ -1,7 +1,7 @@
import datetime
import django.db.models.deletion
import solawi_apps.bidding.pin
import solawi_apps.db.dbid
import solawi_apps.db_lib.dbid
from django.conf import settings
from django.db import migrations, models
@ -18,7 +18,7 @@ class Migration(migrations.Migration):
migrations.CreateModel(
name='Event',
fields=[
('id', models.CharField(default=solawi_apps.db.dbid.generate_id, editable=False, max_length=32, primary_key=True, serialize=False, validators=[solawi_apps.db.dbid.validate_nanoid], verbose_name='ID')),
('id', models.CharField(default=solawi_apps.db_lib.dbid.generate_id, editable=False, max_length=32, primary_key=True, serialize=False, validators=[solawi_apps.db_lib.dbid.validate_nanoid], verbose_name='ID')),
('created', models.DateTimeField(auto_now_add=True, verbose_name='Erstellt')),
('last_modified', models.DateTimeField(auto_now=True, verbose_name='Zuletzt geändert')),
('enabled', models.BooleanField(default=False, verbose_name='Aktiviert')),
@ -39,7 +39,7 @@ class Migration(migrations.Migration):
migrations.CreateModel(
name='FiscalPlan',
fields=[
('id', models.CharField(default=solawi_apps.db.dbid.generate_id, editable=False, max_length=32, primary_key=True, serialize=False, validators=[solawi_apps.db.dbid.validate_nanoid], verbose_name='ID')),
('id', models.CharField(default=solawi_apps.db_lib.dbid.generate_id, editable=False, max_length=32, primary_key=True, serialize=False, validators=[solawi_apps.db_lib.dbid.validate_nanoid], verbose_name='ID')),
('created', models.DateTimeField(auto_now_add=True, verbose_name='Erstellt')),
('required_amount', models.DecimalField(decimal_places=2, max_digits=16, verbose_name='Benötigter Betrag')),
('currency', models.CharField(default='EUR', max_length=5, verbose_name='Währung')),
@ -67,7 +67,7 @@ class Migration(migrations.Migration):
migrations.CreateModel(
name='Round',
fields=[
('id', models.CharField(default=solawi_apps.db.dbid.generate_id, editable=False, max_length=32, primary_key=True, serialize=False, validators=[solawi_apps.db.dbid.validate_nanoid], verbose_name='ID')),
('id', models.CharField(default=solawi_apps.db_lib.dbid.generate_id, editable=False, max_length=32, primary_key=True, serialize=False, validators=[solawi_apps.db_lib.dbid.validate_nanoid], verbose_name='ID')),
('created', models.DateTimeField(auto_now_add=True, verbose_name='Erstellt')),
('last_modified', models.DateTimeField(auto_now=True, verbose_name='Zuletzt geändert')),
('name', models.CharField(max_length=200, verbose_name='Runden-Name')),
@ -84,7 +84,7 @@ class Migration(migrations.Migration):
migrations.CreateModel(
name='Share',
fields=[
('id', models.CharField(default=solawi_apps.db.dbid.generate_id, editable=False, max_length=32, primary_key=True, serialize=False, validators=[solawi_apps.db.dbid.validate_nanoid], verbose_name='ID')),
('id', models.CharField(default=solawi_apps.db_lib.dbid.generate_id, editable=False, max_length=32, primary_key=True, serialize=False, validators=[solawi_apps.db_lib.dbid.validate_nanoid], verbose_name='ID')),
('created', models.DateTimeField(auto_now_add=True, verbose_name='Erstellt')),
('last_modified', models.DateTimeField(auto_now=True, verbose_name='Zuletzt geändert')),
('divider', models.PositiveIntegerField(default=1, verbose_name='Teiler')),
@ -104,7 +104,7 @@ class Migration(migrations.Migration):
migrations.CreateModel(
name='PreBid',
fields=[
('id', models.CharField(default=solawi_apps.db.dbid.generate_id, editable=False, max_length=32, primary_key=True, serialize=False, validators=[solawi_apps.db.dbid.validate_nanoid], verbose_name='ID')),
('id', models.CharField(default=solawi_apps.db_lib.dbid.generate_id, editable=False, max_length=32, primary_key=True, serialize=False, validators=[solawi_apps.db_lib.dbid.validate_nanoid], verbose_name='ID')),
('created', models.DateTimeField(auto_now_add=True, verbose_name='Erstellt')),
('last_modified', models.DateTimeField(auto_now=True, verbose_name='Zuletzt geändert')),
('from_round', models.PositiveIntegerField(default=1, verbose_name='Gültig ab Runde')),
@ -123,7 +123,7 @@ class Migration(migrations.Migration):
migrations.CreateModel(
name='Bid',
fields=[
('id', models.CharField(default=solawi_apps.db.dbid.generate_id, editable=False, max_length=32, primary_key=True, serialize=False, validators=[solawi_apps.db.dbid.validate_nanoid], verbose_name='ID')),
('id', models.CharField(default=solawi_apps.db_lib.dbid.generate_id, editable=False, max_length=32, primary_key=True, serialize=False, validators=[solawi_apps.db_lib.dbid.validate_nanoid], verbose_name='ID')),
('created', models.DateTimeField(auto_now_add=True, verbose_name='Erstellt')),
('last_modified', models.DateTimeField(auto_now=True, verbose_name='Zuletzt geändert')),
('value', models.DecimalField(decimal_places=2, max_digits=16, verbose_name='Wert')),

View File

@ -1,6 +1,6 @@
import datetime
import django.db.models.deletion
import solawi_apps.db.dbid
import solawi_apps.db_lib.dbid
from django.conf import settings
from django.db import migrations, models
@ -16,7 +16,7 @@ class Migration(migrations.Migration):
migrations.CreateModel(
name='TeamMember',
fields=[
('id', models.CharField(default=solawi_apps.db.dbid.generate_id, editable=False, max_length=32, primary_key=True, serialize=False, validators=[solawi_apps.db.dbid.validate_nanoid], verbose_name='ID')),
('id', models.CharField(default=solawi_apps.db_lib.dbid.generate_id, editable=False, max_length=32, primary_key=True, serialize=False, validators=[solawi_apps.db_lib.dbid.validate_nanoid], verbose_name='ID')),
('created', models.DateTimeField(auto_now_add=True, verbose_name='Erstellt')),
('last_modified', models.DateTimeField(auto_now=True, verbose_name='Zuletzt geändert')),
('enabled', models.BooleanField(default=False, verbose_name='Aktiviert')),

View File

@ -4,7 +4,7 @@ from django.db import models
from django.utils.translation import gettext_lazy as _
from solawi_apps.bidding.pin import make_event_pin, make_share_pin
from solawi_apps.db.models import NanoIdPkMixin, CreatedMixin, EnabledMixin, ActiveFromMixin, ActiveToMixin, \
from solawi_apps.db_lib.models import NanoIdPkMixin, CreatedMixin, EnabledMixin, ActiveFromMixin, ActiveToMixin, \
ArchivedMixin, OwnerMixin, LastModifiedMixin

View File

@ -16,7 +16,7 @@ from solawi_apps.bidding.views.team import TeamBiddingEventsView, TeamBiddingEve
TeamEndRoundView, TeamRoundResultView, TeamRoundResultPdfView, TeamRoundResultsPdfPageView, TeamPreBidView, \
TeamAddBidView, TeamEditPreBidView, TeamDeletePreBidView, TeamAddPreBidView
from solawi_apps.bidding.views.user import AccessByPinView, AccessByLinkView, EventView, LeaveEventView
from solawi_apps.db.dbid import NanoIdConverter
from solawi_apps.db_lib.dbid import NanoIdConverter
register_converter(NanoIdConverter, "nanoid")

View File

@ -1,5 +1,5 @@
import django.db.models.deletion
import solawi_apps.db.dbid
import solawi_apps.db_lib.dbid
from django.db import migrations, models
@ -14,7 +14,7 @@ class Migration(migrations.Migration):
migrations.CreateModel(
name='Version',
fields=[
('id', models.CharField(default=solawi_apps.db.dbid.generate_id, editable=False, max_length=32, primary_key=True, serialize=False, validators=[solawi_apps.db.dbid.validate_nanoid], verbose_name='ID')),
('id', models.CharField(default=solawi_apps.db_lib.dbid.generate_id, editable=False, max_length=32, primary_key=True, serialize=False, validators=[solawi_apps.db_lib.dbid.validate_nanoid], verbose_name='ID')),
('created', models.DateTimeField(auto_now_add=True, verbose_name='Erstellt')),
('enabled', models.BooleanField(default=False, verbose_name='Aktiviert')),
('version_identifier', models.CharField(max_length=255, unique=True, verbose_name='Versionsbezeichnung')),
@ -30,7 +30,7 @@ class Migration(migrations.Migration):
migrations.CreateModel(
name='Item',
fields=[
('id', models.CharField(default=solawi_apps.db.dbid.generate_id, editable=False, max_length=32, primary_key=True, serialize=False, validators=[solawi_apps.db.dbid.validate_nanoid], verbose_name='ID')),
('id', models.CharField(default=solawi_apps.db_lib.dbid.generate_id, editable=False, max_length=32, primary_key=True, serialize=False, validators=[solawi_apps.db_lib.dbid.validate_nanoid], verbose_name='ID')),
('created', models.DateTimeField(auto_now_add=True, verbose_name='Erstellt')),
('enabled', models.BooleanField(default=False, verbose_name='Aktiviert')),
('enhancement_type', models.CharField(choices=[('bugfix', 'Fehlerbehebung'), ('feature', 'Neue Funktionalität'), ('enhancement', 'Verbesserung')], max_length=128, verbose_name='Typ')),
@ -49,7 +49,7 @@ class Migration(migrations.Migration):
migrations.CreateModel(
name='Ticket',
fields=[
('id', models.CharField(default=solawi_apps.db.dbid.generate_id, editable=False, max_length=32, primary_key=True, serialize=False, validators=[solawi_apps.db.dbid.validate_nanoid], verbose_name='ID')),
('id', models.CharField(default=solawi_apps.db_lib.dbid.generate_id, editable=False, max_length=32, primary_key=True, serialize=False, validators=[solawi_apps.db_lib.dbid.validate_nanoid], verbose_name='ID')),
('created', models.DateTimeField(auto_now_add=True, verbose_name='Erstellt')),
('enabled', models.BooleanField(default=False, verbose_name='Aktiviert')),
('ticket', models.CharField(max_length=128, verbose_name='Ticket')),

View File

@ -0,0 +1 @@
{"versions":[{"version_identifier":"0.5.3-dev","release_date":"2024-04-14T00:00:00+02:00","note":"Zur besseren Überwachung der Anwendung braucht es Schnittstellen für das Monitoring, die über ein \"Ping\" hinausgehen.","version_name":"Monitoring","items":[{"is_breaking_change":false,"requires_db_update":false,"title":"Health Check API","note":"Eine API ermöglicht das Monitoring in einem externen System. Für den Zugang ist ein Token erforderlich. Dieses kann mit\ndem Management-Kommando `create_health_api_access_token` erzeugt werden.","item_type":"feature","tickets":[{"ticket_id":"50","ticket_url":"https://git.codebau.dev/solawi-suite/platform/issues/50"}]}]}]}

View File

@ -0,0 +1,54 @@
from os.path import dirname, join
from json import loads
from django.db import migrations
from solawi_apps.changelog.object_model import ChangelogDataMigration
DATA_FILE = join(dirname(__file__), '0020_changelog_append.json')
def append_changelog(apps, schema_editor):
Version = apps.get_model("changelog", "Version")
Item = apps.get_model("changelog", "Item")
Ticket = apps.get_model("changelog", "Ticket")
with open(DATA_FILE, "rt", encoding="utf-8") as fd:
json_data = fd.read()
todo = ChangelogDataMigration(**loads(json_data))
for v in todo.versions:
version = Version.objects.create(
version_identifier=v.version_identifier,
release_datetime=v.release_date,
note_md=v.note,
version_name=v.version_name,
enabled=True,
)
for i in v.items:
item = Item.objects.create(
version_fk=version,
enhancement_type=i.item_type,
requires_db_update=i.requires_db_update,
is_breaking_change=i.is_breaking_change,
title=i.title,
note_md=i.note,
enabled=True,
)
for t in i.tickets:
Ticket.objects.create(
item_fk=item,
ticket=t.ticket_id,
ticket_url=t.ticket_url,
enabled=True,
)
class Migration(migrations.Migration):
dependencies = [
('changelog', '0019_changelog_append'),
]
operations = [
migrations.RunPython(append_changelog),
]

View File

@ -2,7 +2,7 @@ from django.db import models
from django.utils.translation import gettext_lazy as _
from solawi_apps.changelog.enhancement import ENHANCEMENT_TYPE
from solawi_apps.db.models import NanoIdPkMixin, CreatedMixin, EnabledMixin
from solawi_apps.db_lib.models import NanoIdPkMixin, CreatedMixin, EnabledMixin
class Version(NanoIdPkMixin, CreatedMixin, EnabledMixin, models.Model):

View File

@ -4,5 +4,5 @@ from django.utils.translation import gettext_lazy as _
class DbConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField'
name = 'solawi_apps.db'
name = 'solawi_apps.db_lib'
verbose_name = _('[solawi-suite] Datenbank-Tools')

View File

@ -7,8 +7,8 @@ from django.db.models import ForeignKey
from django.utils.timezone import now
from django.utils.translation import gettext_lazy as _
from solawi_apps.db.dbid import generate_id, ID_LENGTH, validate_nanoid
from solawi_apps.db.dt import MIN_DATE, MAX_DATE
from solawi_apps.db_lib.dbid import generate_id, ID_LENGTH, validate_nanoid
from solawi_apps.db_lib.dt import MIN_DATE, MAX_DATE
from solawi_apps.encrypted_settings.crypt import encrypt_item, decrypt_item

View File

View File

@ -0,0 +1,8 @@
from django.apps import AppConfig
from django.utils.translation import gettext_lazy as _
class HealthConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField'
name = 'solawi_apps.health'
verbose_name = _('[solawi-suite] Health Check')

View File

@ -0,0 +1,25 @@
from django.contrib.auth.hashers import make_password
from django.core.management import BaseCommand
from solawi_apps.health.models import AccessToken
from solawi_apps.health.verification import get_verification_code
class Command(BaseCommand):
help = 'Creates health API access token'
@staticmethod
def get_new_token() -> str:
new_token = get_verification_code()
hashed = make_password(new_token)
token_object = AccessToken.objects.create(
verification_code=hashed,
enabled=True,
)
return f"X-Solawi-Suite-Access-Token: {token_object.id}:{new_token}"
def handle(self, *args, **options):
self.stdout.write(
self.style.SUCCESS('Erfolg: ') + Command.get_new_token()
)

View File

@ -0,0 +1,60 @@
from typing import Optional
from django.conf import settings
from django.contrib.auth.hashers import verify_password
from django.http import HttpRequest, HttpResponse, JsonResponse
from django.utils.timezone import now
from solawi_apps.db_lib.dbid import NANO_ID_RE
from solawi_apps.health.models import AccessToken
class HealthCheckApiAuthMiddleware:
def __init__(self, get_response):
self.get_response = get_response
self.__disable_access_allow_origin_request_key = "__request__disable_access_allow_origin"
self.access_token_header = settings.HEALTH_CHECK_ACCESS_TOKEN_HEADER
self.access_denied_response = JsonResponse({
"status": "access denied",
}, status=403)
def __call__(self, request: HttpRequest) -> HttpResponse:
response = self.get_response(request)
if getattr(request, self.__disable_access_allow_origin_request_key, False):
response["Access-Control-Allow-Origin"] = "*"
return response
@staticmethod
def verify_token(token: str) -> bool:
t_split = token.split(":", 1)
if len(t_split) != 2:
return False
token_id, token_passwd = t_split
if NANO_ID_RE.match(token_id) is None:
return False
t_now = now()
try:
token_obj = AccessToken.objects.filter(
enabled=True,
active_from__lte=t_now,
active_to__gte=t_now,
).get(id=token_id)
except (AccessToken.DoesNotExist, AccessToken.MultipleObjectsReturned):
return False
verified, _ = verify_password(token_passwd, token_obj.verification_code)
return verified
def handle_health_api_auth(self, request: HttpRequest) -> Optional[HttpResponse]:
if (token := request.headers.get(self.access_token_header, None)) is None:
return self.access_denied_response
if not HealthCheckApiAuthMiddleware.verify_token(token):
return self.access_denied_response
return None
def process_view(self, request: HttpRequest, *args, **kwargs) -> Optional[HttpResponse]:
if (request.resolver_match is not None
and request.resolver_match.namespaces is not None and 'health' in request.resolver_match.namespaces):
setattr(request, self.__disable_access_allow_origin_request_key, True)
return self.handle_health_api_auth(request)
return None

View File

@ -0,0 +1,30 @@
import datetime
import solawi_apps.db_lib.dbid
from django.db import migrations, models
class Migration(migrations.Migration):
initial = True
dependencies = [
]
operations = [
migrations.CreateModel(
name='AccessToken',
fields=[
('id', models.CharField(default=solawi_apps.db_lib.dbid.generate_id, editable=False, max_length=32, primary_key=True, serialize=False, validators=[solawi_apps.db_lib.dbid.validate_nanoid], verbose_name='ID')),
('created', models.DateTimeField(auto_now_add=True, verbose_name='Erstellt')),
('last_modified', models.DateTimeField(auto_now=True, verbose_name='Zuletzt geändert')),
('enabled', models.BooleanField(default=False, verbose_name='Aktiviert')),
('active_from', models.DateTimeField(default=datetime.datetime(2000, 1, 1, 0, 0, tzinfo=datetime.timezone.utc), verbose_name='Aktiv ab')),
('active_to', models.DateTimeField(default=datetime.datetime(2999, 12, 31, 23, 59, 59, 999999, tzinfo=datetime.timezone.utc), verbose_name='Aktiv bis')),
('verification_code', models.CharField(editable=False, max_length=255, verbose_name='Verifizierungscode')),
],
options={
'verbose_name': 'Zugangstoken',
'verbose_name_plural': 'Zugangstokens',
},
),
]

View File

@ -0,0 +1,21 @@
from django.db import models
from django.utils.translation import gettext_lazy as _
from solawi_apps.db_lib.models import (NanoIdPkMixin, CreatedMixin, LastModifiedMixin, EnabledMixin, ActiveFromMixin,
ActiveToMixin)
class AccessToken(
NanoIdPkMixin, CreatedMixin, LastModifiedMixin, EnabledMixin, ActiveFromMixin, ActiveToMixin, models.Model
):
class Meta:
verbose_name = _('Zugangstoken')
verbose_name_plural = _('Zugangstokens')
verification_code = models.CharField(
verbose_name=_("Verifizierungscode"), editable=False, null=False, blank=False, max_length=255
)
def __str__(self):
return _("Verifizierungscode %s") % self.id

View File

@ -0,0 +1,73 @@
from json import loads
from django.db import connection
from django.test import TestCase
from django.urls import reverse
from solawi_apps.health.management.commands.create_health_api_access_token import Command
class HealthCheckApiTestCase(TestCase):
failed_response = {
"status": "access denied",
}
good_response = {
"Cache backend: default": "working",
"DatabaseBackend": "working",
"DefaultFileStorageHealthCheck": "working",
"MigrationsHealthCheck": "working"
}
@classmethod
def setUpClass(cls):
super().setUpClass()
token = Command.get_new_token()
cls.header_name, cls.header_value = [t.strip() for t in token.split(':', 1)]
cls.endpoint = reverse("health:health_check:health_check_home")
def test_without_header(self):
response = self.client.get(self.endpoint)
self.assertEqual(response.status_code, 403)
self.assertDictEqual(self.failed_response, loads(response.content))
def test_with_bad_headers(self):
values = (
'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa',
'0' * 128,
':1:2:3:4:5:6:7:8:9:10',
'',
)
for value in values:
with self.subTest(value=value):
response = self.client.get(self.endpoint, headers={
self.header_name: value,
})
self.assertEqual(response.status_code, 403)
self.assertDictEqual(self.failed_response, loads(response.content))
def test_good_header(self):
if '?mode=memory' in str(connection.settings_dict.get('NAME')):
self.skipTest("in-memory db not supported")
response = self.client.get(self.endpoint, headers={
self.header_name: self.header_value,
})
self.assertEqual(response.status_code, 200)
self.assertDictEqual(self.good_response, loads(response.content))
def test_bad_mutations(self):
values = (
self.header_value[1:],
self.header_value[:-1],
self.header_value + "-a",
"a-" + self.header_value,
self.header_value.replace(":", "-"),
)
for value in values:
with self.subTest(value=value):
response = self.client.get(self.endpoint, headers={
self.header_name: value,
})
self.assertEqual(response.status_code, 403)
self.assertDictEqual(self.failed_response, loads(response.content))

View File

@ -0,0 +1,7 @@
from django.urls import path, include
urlpatterns = [
path(r'', include('health_check.urls')),
]
app_name = 'solawi_apps.health'

View File

@ -0,0 +1,7 @@
import string
from nanoid import generate
def get_verification_code(length: int = 128, chars: str = string.ascii_letters + string.digits + ".-$_!/") -> str:
return generate(size=length, alphabet=chars)

View File

@ -1,6 +1,6 @@
import datetime
import django.db.models.deletion
import solawi_apps.db.dbid
import solawi_apps.db_lib.dbid
from django.conf import settings
from django.db import migrations, models
@ -17,7 +17,7 @@ class Migration(migrations.Migration):
migrations.CreateModel(
name='Passkey',
fields=[
('id', models.CharField(default=solawi_apps.db.dbid.generate_id, editable=False, max_length=32, primary_key=True, serialize=False, validators=[solawi_apps.db.dbid.validate_nanoid], verbose_name='ID')),
('id', models.CharField(default=solawi_apps.db_lib.dbid.generate_id, editable=False, max_length=32, primary_key=True, serialize=False, validators=[solawi_apps.db_lib.dbid.validate_nanoid], verbose_name='ID')),
('created', models.DateTimeField(auto_now_add=True, verbose_name='Erstellt')),
('last_modified', models.DateTimeField(auto_now=True, verbose_name='Zuletzt geändert')),
('enabled', models.BooleanField(default=False, verbose_name='Aktiviert')),

View File

@ -2,7 +2,7 @@ from django.conf import settings
from django.db import models
from django.utils.translation import gettext_lazy as _
from solawi_apps.db.models import NanoIdPkMixin, CreatedMixin, LastModifiedMixin, EnabledMixin, ActiveToMixin, \
from solawi_apps.db_lib.models import NanoIdPkMixin, CreatedMixin, LastModifiedMixin, EnabledMixin, ActiveToMixin, \
ActiveFromMixin

View File

@ -1,6 +1,6 @@
from django.urls import path, register_converter
from solawi_apps.db.dbid import NanoIdConverter
from solawi_apps.db_lib.dbid import NanoIdConverter
from solawi_apps.passkeys.views.auth import PasskeyLoginView, PasskeyAuthOptionsView, PasskeyAuthView
from solawi_apps.passkeys.views.user import ListPasskeysView, AddPasskeyView, DeletePasskeyView, ManagePasskeyView, \
InitCreatePasskeyView, VerifyCreatePasskeyView

View File

@ -0,0 +1,34 @@
from django.contrib import admin
from django.utils.translation import gettext_lazy as _
from solawi_apps.health.models import AccessToken
class AccessTokenAdmin(admin.ModelAdmin):
ordering = ("id", "created")
list_display = ("id", "get_currently_active", "enabled", "active_from", "active_to")
readonly_fields = ("id", "verification_code", "created", "last_modified", "get_currently_active")
fieldsets = (
(_("Zugangstoken"), {
"fields": ("id", "verification_code"),
}),
(_("Gültigkeit"), {
"fields": ("enabled", "active_from", "active_to"),
}),
(_("Status"), {
"fields": ("get_currently_active",),
}),
(_("Meta"), {
"fields": ("created", "last_modified"),
}),
)
def has_add_permission(self, request):
return False
@admin.display(description=_("Ist aktuell aktiv"), boolean=True)
def get_currently_active(self, obj: AccessToken) -> bool:
return obj.is_active()

View File

@ -9,6 +9,7 @@ class PasskeyAdmin(admin.ModelAdmin):
ordering = ("owner_fk__email", "name", "created")
list_display = ("name", "get_owner", "get_currently_active", "created")
list_filter = ("owner_fk__email",)
search_fields = ("owner_fk__email", "name")

View File

@ -8,9 +8,11 @@ from django.utils.translation import gettext_lazy as _
from solawi_apps.announce.models import PublicAnnouncement, MemberAnnouncement
from solawi_apps.app_permission.models import AccessPermission, DefaultPermission
from solawi_apps.health.models import AccessToken
from solawi_apps.passkeys.models import Passkey
from solawi_apps.s_admin.admin.announce import AnnouncementAdmin
from solawi_apps.s_admin.admin.app_permission import AccessPermissionAdmin, DefaultPermissionAdmin
from solawi_apps.s_admin.admin.health import AccessTokenAdmin
from solawi_apps.s_admin.admin.passkeys import PasskeyAdmin
from solawi_apps.s_admin.admin.register import AllowedEmailDomainAdmin, RegistrationAdmin, DomainBasedAppAccessAdmin
from solawi_apps.s_admin.admin.session import SessionAdmin
@ -56,3 +58,5 @@ admin_site.register(PublicAnnouncement, AnnouncementAdmin)
admin_site.register(MemberAnnouncement, AnnouncementAdmin)
admin_site.register(Passkey, PasskeyAdmin)
admin_site.register(AccessToken, AccessTokenAdmin)

View File

@ -1,4 +1,4 @@
import solawi_apps.db.dbid
import solawi_apps.db_lib.dbid
from django.db import migrations, models
@ -13,7 +13,7 @@ class Migration(migrations.Migration):
migrations.CreateModel(
name='TxtFile',
fields=[
('id', models.CharField(default=solawi_apps.db.dbid.generate_id, editable=False, max_length=32, primary_key=True, serialize=False, validators=[solawi_apps.db.dbid.validate_nanoid], verbose_name='ID')),
('id', models.CharField(default=solawi_apps.db_lib.dbid.generate_id, editable=False, max_length=32, primary_key=True, serialize=False, validators=[solawi_apps.db_lib.dbid.validate_nanoid], verbose_name='ID')),
('created', models.DateTimeField(auto_now_add=True, verbose_name='Erstellt')),
('last_modified', models.DateTimeField(auto_now=True, verbose_name='Zuletzt geändert')),
('enabled', models.BooleanField(default=False, verbose_name='Aktiviert')),

View File

@ -1,7 +1,7 @@
from django.db import models
from django.utils.translation import gettext_lazy as _
from solawi_apps.db.models import NanoIdPkMixin, CreatedMixin, LastModifiedMixin, EnabledMixin
from solawi_apps.db_lib.models import NanoIdPkMixin, CreatedMixin, LastModifiedMixin, EnabledMixin
class TxtFile(NanoIdPkMixin, CreatedMixin, LastModifiedMixin, EnabledMixin, models.Model):

View File

@ -1,7 +1,7 @@
import datetime
import django.db.models.deletion
import solawi_apps.db.dbid
import solawi_apps.db.models
import solawi_apps.db_lib.dbid
import solawi_apps.db_lib.models
from django.conf import settings
from django.db import migrations, models
@ -18,7 +18,7 @@ class Migration(migrations.Migration):
migrations.CreateModel(
name='AllowedEmailDomain',
fields=[
('id', models.CharField(default=solawi_apps.db.dbid.generate_id, editable=False, max_length=32, primary_key=True, serialize=False, validators=[solawi_apps.db.dbid.validate_nanoid], verbose_name='ID')),
('id', models.CharField(default=solawi_apps.db_lib.dbid.generate_id, editable=False, max_length=32, primary_key=True, serialize=False, validators=[solawi_apps.db_lib.dbid.validate_nanoid], verbose_name='ID')),
('created', models.DateTimeField(auto_now_add=True, verbose_name='Erstellt')),
('last_modified', models.DateTimeField(auto_now=True, verbose_name='Zuletzt geändert')),
('enabled', models.BooleanField(default=False, verbose_name='Aktiviert')),
@ -34,7 +34,7 @@ class Migration(migrations.Migration):
migrations.CreateModel(
name='OtpAttempt',
fields=[
('id', models.CharField(default=solawi_apps.db.dbid.generate_id, editable=False, max_length=32, primary_key=True, serialize=False, validators=[solawi_apps.db.dbid.validate_nanoid], verbose_name='ID')),
('id', models.CharField(default=solawi_apps.db_lib.dbid.generate_id, editable=False, max_length=32, primary_key=True, serialize=False, validators=[solawi_apps.db_lib.dbid.validate_nanoid], verbose_name='ID')),
('created', models.DateTimeField(auto_now_add=True, verbose_name='Erstellt')),
('success', models.BooleanField(default=False, editable=False, verbose_name='Erfolgreicher Versuch')),
('user_fk', models.ForeignKey(editable=False, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL, verbose_name='User')),
@ -48,15 +48,15 @@ class Migration(migrations.Migration):
migrations.CreateModel(
name='Registration',
fields=[
('id', models.CharField(default=solawi_apps.db.dbid.generate_id, editable=False, max_length=32, primary_key=True, serialize=False, validators=[solawi_apps.db.dbid.validate_nanoid], verbose_name='ID')),
('id', models.CharField(default=solawi_apps.db_lib.dbid.generate_id, editable=False, max_length=32, primary_key=True, serialize=False, validators=[solawi_apps.db_lib.dbid.validate_nanoid], verbose_name='ID')),
('created', models.DateTimeField(auto_now_add=True, verbose_name='Erstellt')),
('last_modified', models.DateTimeField(auto_now=True, verbose_name='Zuletzt geändert')),
('enabled', models.BooleanField(default=False, verbose_name='Aktiviert')),
('active_from', models.DateTimeField(default=datetime.datetime(2000, 1, 1, 0, 0, tzinfo=datetime.timezone.utc), verbose_name='Aktiv ab')),
('active_to', models.DateTimeField(default=datetime.datetime(2999, 12, 31, 23, 59, 59, 999999, tzinfo=datetime.timezone.utc), verbose_name='Aktiv bis')),
('as_superuser', models.BooleanField(default=False, editable=False, verbose_name='Registrierung als Superuser')),
('registration_id', models.CharField(default=solawi_apps.db.dbid.generate_id, editable=False, max_length=32, unique=True, verbose_name='Registrierungs-ID')),
('registration_check', solawi_apps.db.models.EncryptedTextField(default=solawi_apps.db.dbid.generate_id, editable=False, verbose_name='Registrierungs-Check')),
('registration_id', models.CharField(default=solawi_apps.db_lib.dbid.generate_id, editable=False, max_length=32, unique=True, verbose_name='Registrierungs-ID')),
('registration_check', solawi_apps.db_lib.models.EncryptedTextField(default=solawi_apps.db_lib.dbid.generate_id, editable=False, verbose_name='Registrierungs-Check')),
('registration_finished', models.BooleanField(default=False, verbose_name='Registrierung abgeschlossen')),
('user_fk', models.OneToOneField(editable=False, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL, verbose_name='User')),
],
@ -69,10 +69,10 @@ class Migration(migrations.Migration):
migrations.CreateModel(
name='TotpSetup',
fields=[
('id', models.CharField(default=solawi_apps.db.dbid.generate_id, editable=False, max_length=32, primary_key=True, serialize=False, validators=[solawi_apps.db.dbid.validate_nanoid], verbose_name='ID')),
('id', models.CharField(default=solawi_apps.db_lib.dbid.generate_id, editable=False, max_length=32, primary_key=True, serialize=False, validators=[solawi_apps.db_lib.dbid.validate_nanoid], verbose_name='ID')),
('created', models.DateTimeField(auto_now_add=True, verbose_name='Erstellt')),
('last_modified', models.DateTimeField(auto_now=True, verbose_name='Zuletzt geändert')),
('totp_provisioning', solawi_apps.db.models.EncryptedTextField(editable=False, max_length=1024, verbose_name='TOTP Provisionierung')),
('totp_provisioning', solawi_apps.db_lib.models.EncryptedTextField(editable=False, max_length=1024, verbose_name='TOTP Provisionierung')),
('user_fk', models.OneToOneField(editable=False, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL, verbose_name='User')),
],
options={
@ -84,7 +84,7 @@ class Migration(migrations.Migration):
migrations.CreateModel(
name='BurnedOtp',
fields=[
('id', models.CharField(default=solawi_apps.db.dbid.generate_id, editable=False, max_length=32, primary_key=True, serialize=False, validators=[solawi_apps.db.dbid.validate_nanoid], verbose_name='ID')),
('id', models.CharField(default=solawi_apps.db_lib.dbid.generate_id, editable=False, max_length=32, primary_key=True, serialize=False, validators=[solawi_apps.db_lib.dbid.validate_nanoid], verbose_name='ID')),
('created', models.DateTimeField(auto_now_add=True, verbose_name='Erstellt')),
('otp', models.CharField(editable=False, max_length=6, verbose_name='Einmalpasswort')),
('totp_setup_fk', models.ForeignKey(editable=False, on_delete=django.db.models.deletion.CASCADE, to='user.totpsetup', verbose_name='TOTP Setup')),

View File

@ -1,5 +1,5 @@
import django.db.models.deletion
import solawi_apps.db.dbid
import solawi_apps.db_lib.dbid
from django.conf import settings
from django.db import migrations, models
@ -15,7 +15,7 @@ class Migration(migrations.Migration):
migrations.CreateModel(
name='LoginAttempt',
fields=[
('id', models.CharField(default=solawi_apps.db.dbid.generate_id, editable=False, max_length=32, primary_key=True, serialize=False, validators=[solawi_apps.db.dbid.validate_nanoid], verbose_name='ID')),
('id', models.CharField(default=solawi_apps.db_lib.dbid.generate_id, editable=False, max_length=32, primary_key=True, serialize=False, validators=[solawi_apps.db_lib.dbid.validate_nanoid], verbose_name='ID')),
('created', models.DateTimeField(auto_now_add=True, verbose_name='Erstellt')),
('success', models.BooleanField(default=False, editable=False, verbose_name='Erfolgreicher Versuch')),
('user_fk', models.ForeignKey(editable=False, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL, verbose_name='User')),

View File

@ -1,4 +1,4 @@
import solawi_apps.db.dbid
import solawi_apps.db_lib.dbid
from django.db import migrations, models
@ -12,7 +12,7 @@ class Migration(migrations.Migration):
migrations.CreateModel(
name='AuthenticationAttempt',
fields=[
('id', models.CharField(default=solawi_apps.db.dbid.generate_id, editable=False, max_length=32, primary_key=True, serialize=False, validators=[solawi_apps.db.dbid.validate_nanoid], verbose_name='ID')),
('id', models.CharField(default=solawi_apps.db_lib.dbid.generate_id, editable=False, max_length=32, primary_key=True, serialize=False, validators=[solawi_apps.db_lib.dbid.validate_nanoid], verbose_name='ID')),
('created', models.DateTimeField(auto_now_add=True, verbose_name='Erstellt')),
('ip', models.GenericIPAddressField(verbose_name='IP Adresse')),
],

View File

@ -1,5 +1,5 @@
import django.db.models.deletion
import solawi_apps.db.dbid
import solawi_apps.db_lib.dbid
from django.conf import settings
from django.db import migrations, models
@ -16,7 +16,7 @@ class Migration(migrations.Migration):
migrations.CreateModel(
name='UserSession',
fields=[
('id', models.CharField(default=solawi_apps.db.dbid.generate_id, editable=False, max_length=32, primary_key=True, serialize=False, validators=[solawi_apps.db.dbid.validate_nanoid], verbose_name='ID')),
('id', models.CharField(default=solawi_apps.db_lib.dbid.generate_id, editable=False, max_length=32, primary_key=True, serialize=False, validators=[solawi_apps.db_lib.dbid.validate_nanoid], verbose_name='ID')),
('created', models.DateTimeField(auto_now_add=True, verbose_name='Erstellt')),
('last_modified', models.DateTimeField(auto_now=True, verbose_name='Zuletzt geändert')),
('session_fk', models.OneToOneField(editable=False, on_delete=django.db.models.deletion.CASCADE, to='sessions.session', verbose_name='Sitzung')),

View File

@ -1,5 +1,5 @@
import django.db.models.deletion
import solawi_apps.db.dbid
import solawi_apps.db_lib.dbid
from django.conf import settings
from django.db import migrations, models
@ -15,7 +15,7 @@ class Migration(migrations.Migration):
migrations.CreateModel(
name='ForgotPassword',
fields=[
('id', models.CharField(default=solawi_apps.db.dbid.generate_id, editable=False, max_length=32, primary_key=True, serialize=False, validators=[solawi_apps.db.dbid.validate_nanoid], verbose_name='ID')),
('id', models.CharField(default=solawi_apps.db_lib.dbid.generate_id, editable=False, max_length=32, primary_key=True, serialize=False, validators=[solawi_apps.db_lib.dbid.validate_nanoid], verbose_name='ID')),
('created', models.DateTimeField(auto_now_add=True, verbose_name='Erstellt')),
('ip', models.GenericIPAddressField(verbose_name='IP Adresse')),
('user_fk', models.ForeignKey(editable=False, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL, verbose_name='User')),

View File

@ -1,5 +1,5 @@
import django.db.models.deletion
import solawi_apps.db.dbid
import solawi_apps.db_lib.dbid
from django.conf import settings
from django.db import migrations, models
@ -15,7 +15,7 @@ class Migration(migrations.Migration):
migrations.CreateModel(
name='EmergencyCode',
fields=[
('id', models.CharField(default=solawi_apps.db.dbid.generate_id, editable=False, max_length=32, primary_key=True, serialize=False, validators=[solawi_apps.db.dbid.validate_nanoid], verbose_name='ID')),
('id', models.CharField(default=solawi_apps.db_lib.dbid.generate_id, editable=False, max_length=32, primary_key=True, serialize=False, validators=[solawi_apps.db_lib.dbid.validate_nanoid], verbose_name='ID')),
('created', models.DateTimeField(auto_now_add=True, verbose_name='Erstellt')),
('code_hash', models.CharField(editable=False, max_length=255, verbose_name='Code Hash')),
('owner_fk', models.ForeignKey(editable=False, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL, verbose_name='Besitzer')),
@ -29,7 +29,7 @@ class Migration(migrations.Migration):
migrations.CreateModel(
name='EmergencyCodeAttempt',
fields=[
('id', models.CharField(default=solawi_apps.db.dbid.generate_id, editable=False, max_length=32, primary_key=True, serialize=False, validators=[solawi_apps.db.dbid.validate_nanoid], verbose_name='ID')),
('id', models.CharField(default=solawi_apps.db_lib.dbid.generate_id, editable=False, max_length=32, primary_key=True, serialize=False, validators=[solawi_apps.db_lib.dbid.validate_nanoid], verbose_name='ID')),
('created', models.DateTimeField(auto_now_add=True, verbose_name='Erstellt')),
('success', models.BooleanField(default=False, editable=False, verbose_name='Erfolgreicher Versuch')),
('ip', models.GenericIPAddressField(default=None, null=True, verbose_name='IP Adresse')),

View File

@ -1,7 +1,7 @@
import datetime
import django.db.models.deletion
import solawi_apps.app_permission.integration
import solawi_apps.db.dbid
import solawi_apps.db_lib.dbid
from django.db import migrations, models
@ -15,7 +15,7 @@ class Migration(migrations.Migration):
migrations.CreateModel(
name='DomainBasedAppAccess',
fields=[
('id', models.CharField(default=solawi_apps.db.dbid.generate_id, editable=False, max_length=32, primary_key=True, serialize=False, validators=[solawi_apps.db.dbid.validate_nanoid], verbose_name='ID')),
('id', models.CharField(default=solawi_apps.db_lib.dbid.generate_id, editable=False, max_length=32, primary_key=True, serialize=False, validators=[solawi_apps.db_lib.dbid.validate_nanoid], verbose_name='ID')),
('created', models.DateTimeField(auto_now_add=True, verbose_name='Erstellt')),
('last_modified', models.DateTimeField(auto_now=True, verbose_name='Zuletzt geändert')),
('enabled', models.BooleanField(default=False, verbose_name='Aktiviert')),

View File

@ -4,8 +4,8 @@ from django.db.models import OneToOneField
from django.utils.translation import gettext_lazy as _
from solawi_apps.app_permission.integration import get_all_installed_app_labels
from solawi_apps.db.dbid import generate_id, ID_LENGTH
from solawi_apps.db.models import CreatedMixin, LastModifiedMixin, ActiveFromMixin, ActiveToMixin, EnabledMixin, \
from solawi_apps.db_lib.dbid import generate_id, ID_LENGTH
from solawi_apps.db_lib.models import CreatedMixin, LastModifiedMixin, ActiveFromMixin, ActiveToMixin, EnabledMixin, \
EncryptedTextField, NanoIdPkMixin

View File

@ -4,7 +4,7 @@ from django.db import models
from django.db.models import OneToOneField, ForeignKey, CharField, BooleanField, GenericIPAddressField
from django.utils.translation import gettext_lazy as _
from solawi_apps.db.models import NanoIdPkMixin, CreatedMixin, LastModifiedMixin, EncryptedTextField
from solawi_apps.db_lib.models import NanoIdPkMixin, CreatedMixin, LastModifiedMixin, EncryptedTextField
class TotpSetup(NanoIdPkMixin, CreatedMixin, LastModifiedMixin, models.Model):

View File

@ -1,6 +1,6 @@
from django.urls import path, register_converter
from solawi_apps.db.dbid import NanoIdConverter
from solawi_apps.db_lib.dbid import NanoIdConverter
from solawi_apps.user.views.auth import LoginView, LogoutView, OtpView, LogoutAllSessionsView, EmergencyCodeView
from solawi_apps.user.views.manage import AccountManageView, ChangePasswordView, ChangeOtpView, ChangeOtpQrCodeView, \
ForgotPasswordView, ResetPasswordView, EmergencyCodeRegenView

View File

@ -76,7 +76,7 @@ INSTALLED_APPS = [
'solawi_apps.ui',
'solawi_apps.encrypted_settings',
'solawi_apps.url',
'solawi_apps.db',
'solawi_apps.db_lib',
'solawi_apps.user',
'solawi_apps.handbook',
'solawi_apps.bidding',
@ -99,11 +99,18 @@ INSTALLED_APPS = [
'solawi_apps.txt',
'solawi_apps.passkeys',
'solawi_apps.announce',
'solawi_apps.health',
'health_check', # required
'health_check.db', # stock Django health checkers
'health_check.cache',
'health_check.storage',
'health_check.contrib.migrations',
]
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'solawi_apps.ui.middleware.request_id.RequestIdMiddleware',
'solawi_apps.health.middleware.HealthCheckApiAuthMiddleware',
'corsheaders.middleware.CorsMiddleware',
'csp.middleware.CSPMiddleware',
'debug_toolbar.middleware.DebugToolbarMiddleware',
@ -345,6 +352,7 @@ CORS_ALLOW_ALL_ORIGINS = False
CORS_ALLOW_HEADERS = (
*default_headers,
"x-solawi-suite-csrf-token",
"x-solawi-suite-access-token",
)
CORS_ALLOW_CREDENTIALS = False
ACCESS_CONTROL_ALLOW_ORIGIN = None
@ -366,3 +374,9 @@ PASSKEYS_EXPECTED_ORIGINS = [
]
if TEST_ORIGIN is not None:
PASSKEYS_EXPECTED_ORIGINS.append(TEST_ORIGIN)
HEALTH_CHECK = {
'DISK_USAGE_MAX': 80, # percent
}
HEALTH_CHECK_ACCESS_TOKEN_HEADER = "x-solawi-suite-access-token"

View File

@ -37,6 +37,7 @@ urlpatterns = [
path('ahg/', include("solawi_apps.ahg.urls", namespace="ahg")),
path('access/', include("solawi_apps.app_permission.urls", namespace="access")),
path('changelog/', include("solawi_apps.changelog.urls", namespace="changelog")),
path('health/', include("solawi_apps.health.urls", namespace="health")),
path(
'favicon.png',
RedirectView.as_view(