diff --git a/server/.gitignore b/server/.gitignore index 2c3f6a9..590be07 100644 --- a/server/.gitignore +++ b/server/.gitignore @@ -14,3 +14,7 @@ __pycache__ /etebase_server_settings.py /secret.txt + +/build +/dist +/*.egg-info diff --git a/server/ChangeLog.md b/server/ChangeLog.md index 18bf06d..d1c203d 100644 --- a/server/ChangeLog.md +++ b/server/ChangeLog.md @@ -1,5 +1,21 @@ # Changelog +## Version 0.11.0 +- Update deps for Python 3.11 + +## Version 0.10.0 +- Replace the deprecated aioredis with redis-py +- Optimize how we fetch the current (latest) revision of items + +## Version 0.9.1 +- Update pinned Django version (only matters if using `requirements.txt`). + +## Version 0.9.0 +- Add LDAP support for checking the validity of a username +- Allow specifying engine-specific database options +- Fix crash on shutdown when redis isn't used +- Reorganize the code to be a valid Python package + ## Version 0.8.3 - Fix compatibility with latest fastapi diff --git a/server/README.md b/server/README.md index 06b9efc..2b30011 100644 --- a/server/README.md +++ b/server/README.md @@ -63,6 +63,12 @@ Now you can initialise our django app. ./manage.py migrate ``` +Create static files: + +``` +./manage.py collectstatic +``` + And you are done! You can now run the debug server just to see everything works as expected by running: ``` @@ -151,7 +157,7 @@ Instead of having to create Django users manually when signup up Etebase users, For example, this makes sense when putting an Etebase server in production. However, this does come with the added risk that everybody with access to your server will be able to sign up. -In order to set it up, comment out the line `ETEBASE_CREATE_USER_FUNC = "django_etebase.utils.create_user_blocked"` in `server/settings.py` and restart your Etebase server. +In order to set it up, comment out the line `ETEBASE_CREATE_USER_FUNC = "etebase_server.django.utils.create_user_blocked"` in `server/settings.py` and restart your Etebase server. # License @@ -177,3 +183,5 @@ Become a financial contributor and help us sustain our community! [![ilovept](https://github.com/ilovept.png?size=40)](https://github.com/ilovept) [![ryanleesipes](https://github.com/ryanleesipes.png?size=40)](https://github.com/ryanleesipes) +[![DanielG](https://github.com/DanielG.png?size=40)](https://github.com/DanielG) +[![Kanaye](https://github.com/Kanaye.png?size=40)](https://github.com/Kanaye) diff --git a/server/django_etebase/signals.py b/server/django_etebase/signals.py deleted file mode 100644 index 0fc3e80..0000000 --- a/server/django_etebase/signals.py +++ /dev/null @@ -1,3 +0,0 @@ -from django.dispatch import Signal - -user_signed_up = Signal(providing_args=["request", "user"]) diff --git a/server/docker/test-server/Dockerfile b/server/docker/test-server/Dockerfile index 19ddb91..27c4e38 100644 --- a/server/docker/test-server/Dockerfile +++ b/server/docker/test-server/Dockerfile @@ -1,4 +1,4 @@ -FROM python:3.9.0-alpine +FROM python:3.11.0-alpine ARG ETESYNC_VERSION diff --git a/server/docker/test-server/etebase-server.ini b/server/docker/test-server/etebase-server.ini index a9b65ff..e36868f 100644 --- a/server/docker/test-server/etebase-server.ini +++ b/server/docker/test-server/etebase-server.ini @@ -9,4 +9,4 @@ allowed_host1 = * [database] engine = django.db.backends.sqlite3 -name = /db.sqlite3 +name = /data/db.sqlite3 diff --git a/server/etebase-server.ini.example b/server/etebase-server.ini.example index 4f4b456..436efe1 100644 --- a/server/etebase-server.ini.example +++ b/server/etebase-server.ini.example @@ -18,3 +18,18 @@ allowed_host1 = example.com [database] engine = django.db.backends.sqlite3 name = db.sqlite3 + +[database-options] +; Add engine-specific options here, such as postgresql parameter key words + +;[ldap] +;server = +;search_base = +;filter = +; In case a cache TTL of 1 hour is too short for you, set `cache_ttl` to the preferred +; amount of hours a cache entry should be viewed as valid: +;cache_ttl = 5 +;bind_dn = +; Either specify the password directly, or provide a password file +;bind_pw = +;bind_pw_file = /path/to/the/file.txt diff --git a/server/etebase_server/asgi.py b/server/etebase_server/asgi.py index 25dbf77..5a7ffa7 100644 --- a/server/etebase_server/asgi.py +++ b/server/etebase_server/asgi.py @@ -7,7 +7,7 @@ django_application = get_asgi_application() def create_application(): - from etebase_fastapi.main import create_application + from etebase_server.fastapi.main import create_application app = create_application() diff --git a/server/django_etebase/__init__.py b/server/etebase_server/django/__init__.py similarity index 100% rename from server/django_etebase/__init__.py rename to server/etebase_server/django/__init__.py diff --git a/server/django_etebase/app_settings_inner.py b/server/etebase_server/django/app_settings_inner.py similarity index 100% rename from server/django_etebase/app_settings_inner.py rename to server/etebase_server/django/app_settings_inner.py diff --git a/server/django_etebase/apps.py b/server/etebase_server/django/apps.py similarity index 53% rename from server/django_etebase/apps.py rename to server/etebase_server/django/apps.py index 84e4b6e..8f95e10 100644 --- a/server/django_etebase/apps.py +++ b/server/etebase_server/django/apps.py @@ -2,4 +2,5 @@ from django.apps import AppConfig class DjangoEtebaseConfig(AppConfig): - name = "django_etebase" + name = "etebase_server.django" + label = "django_etebase" diff --git a/server/django_etebase/migrations/0001_initial.py b/server/etebase_server/django/migrations/0001_initial.py similarity index 98% rename from server/django_etebase/migrations/0001_initial.py rename to server/etebase_server/django/migrations/0001_initial.py index 42ee022..87783b3 100644 --- a/server/django_etebase/migrations/0001_initial.py +++ b/server/etebase_server/django/migrations/0001_initial.py @@ -4,7 +4,7 @@ from django.conf import settings import django.core.validators from django.db import migrations, models import django.db.models.deletion -import django_etebase.models +from etebase_server.django.models import chunk_directory_path class Migration(migrations.Migration): @@ -85,7 +85,7 @@ class Migration(migrations.Migration): ), ( "chunkFile", - models.FileField(max_length=150, unique=True, upload_to=django_etebase.models.chunk_directory_path), + models.FileField(max_length=150, unique=True, upload_to=chunk_directory_path), ), ( "item", diff --git a/server/django_etebase/migrations/0002_userinfo.py b/server/etebase_server/django/migrations/0002_userinfo.py similarity index 100% rename from server/django_etebase/migrations/0002_userinfo.py rename to server/etebase_server/django/migrations/0002_userinfo.py diff --git a/server/django_etebase/migrations/0003_collectioninvitation.py b/server/etebase_server/django/migrations/0003_collectioninvitation.py similarity index 100% rename from server/django_etebase/migrations/0003_collectioninvitation.py rename to server/etebase_server/django/migrations/0003_collectioninvitation.py diff --git a/server/django_etebase/migrations/0004_collectioninvitation_version.py b/server/etebase_server/django/migrations/0004_collectioninvitation_version.py similarity index 100% rename from server/django_etebase/migrations/0004_collectioninvitation_version.py rename to server/etebase_server/django/migrations/0004_collectioninvitation_version.py diff --git a/server/django_etebase/migrations/0005_auto_20200526_1021.py b/server/etebase_server/django/migrations/0005_auto_20200526_1021.py similarity index 100% rename from server/django_etebase/migrations/0005_auto_20200526_1021.py rename to server/etebase_server/django/migrations/0005_auto_20200526_1021.py diff --git a/server/django_etebase/migrations/0006_auto_20200526_1040.py b/server/etebase_server/django/migrations/0006_auto_20200526_1040.py similarity index 100% rename from server/django_etebase/migrations/0006_auto_20200526_1040.py rename to server/etebase_server/django/migrations/0006_auto_20200526_1040.py diff --git a/server/django_etebase/migrations/0007_auto_20200526_1336.py b/server/etebase_server/django/migrations/0007_auto_20200526_1336.py similarity index 100% rename from server/django_etebase/migrations/0007_auto_20200526_1336.py rename to server/etebase_server/django/migrations/0007_auto_20200526_1336.py diff --git a/server/django_etebase/migrations/0008_auto_20200526_1535.py b/server/etebase_server/django/migrations/0008_auto_20200526_1535.py similarity index 91% rename from server/django_etebase/migrations/0008_auto_20200526_1535.py rename to server/etebase_server/django/migrations/0008_auto_20200526_1535.py index 7bb83d5..f4b8a85 100644 --- a/server/django_etebase/migrations/0008_auto_20200526_1535.py +++ b/server/etebase_server/django/migrations/0008_auto_20200526_1535.py @@ -3,7 +3,7 @@ import django.core.validators from django.db import migrations, models import django.db.models.deletion -import django_etebase.models +from etebase_server.django.models import generate_stoken_uid class Migration(migrations.Migration): @@ -21,7 +21,7 @@ class Migration(migrations.Migration): "uid", models.CharField( db_index=True, - default=django_etebase.models.generate_stoken_uid, + default=generate_stoken_uid, max_length=43, unique=True, validators=[ diff --git a/server/django_etebase/migrations/0009_auto_20200526_1535.py b/server/etebase_server/django/migrations/0009_auto_20200526_1535.py similarity index 100% rename from server/django_etebase/migrations/0009_auto_20200526_1535.py rename to server/etebase_server/django/migrations/0009_auto_20200526_1535.py diff --git a/server/django_etebase/migrations/0010_auto_20200526_1539.py b/server/etebase_server/django/migrations/0010_auto_20200526_1539.py similarity index 100% rename from server/django_etebase/migrations/0010_auto_20200526_1539.py rename to server/etebase_server/django/migrations/0010_auto_20200526_1539.py diff --git a/server/django_etebase/migrations/0011_collectionmember_stoken.py b/server/etebase_server/django/migrations/0011_collectionmember_stoken.py similarity index 100% rename from server/django_etebase/migrations/0011_collectionmember_stoken.py rename to server/etebase_server/django/migrations/0011_collectionmember_stoken.py diff --git a/server/django_etebase/migrations/0012_auto_20200527_0743.py b/server/etebase_server/django/migrations/0012_auto_20200527_0743.py similarity index 100% rename from server/django_etebase/migrations/0012_auto_20200527_0743.py rename to server/etebase_server/django/migrations/0012_auto_20200527_0743.py diff --git a/server/django_etebase/migrations/0013_collectionmemberremoved.py b/server/etebase_server/django/migrations/0013_collectionmemberremoved.py similarity index 100% rename from server/django_etebase/migrations/0013_collectionmemberremoved.py rename to server/etebase_server/django/migrations/0013_collectionmemberremoved.py diff --git a/server/django_etebase/migrations/0014_auto_20200602_1558.py b/server/etebase_server/django/migrations/0014_auto_20200602_1558.py similarity index 100% rename from server/django_etebase/migrations/0014_auto_20200602_1558.py rename to server/etebase_server/django/migrations/0014_auto_20200602_1558.py diff --git a/server/django_etebase/migrations/0015_collectionitemrevision_salt.py b/server/etebase_server/django/migrations/0015_collectionitemrevision_salt.py similarity index 100% rename from server/django_etebase/migrations/0015_collectionitemrevision_salt.py rename to server/etebase_server/django/migrations/0015_collectionitemrevision_salt.py diff --git a/server/django_etebase/migrations/0016_auto_20200623_0820.py b/server/etebase_server/django/migrations/0016_auto_20200623_0820.py similarity index 100% rename from server/django_etebase/migrations/0016_auto_20200623_0820.py rename to server/etebase_server/django/migrations/0016_auto_20200623_0820.py diff --git a/server/django_etebase/migrations/0017_auto_20200623_0958.py b/server/etebase_server/django/migrations/0017_auto_20200623_0958.py similarity index 100% rename from server/django_etebase/migrations/0017_auto_20200623_0958.py rename to server/etebase_server/django/migrations/0017_auto_20200623_0958.py diff --git a/server/django_etebase/migrations/0018_auto_20200624_0748.py b/server/etebase_server/django/migrations/0018_auto_20200624_0748.py similarity index 100% rename from server/django_etebase/migrations/0018_auto_20200624_0748.py rename to server/etebase_server/django/migrations/0018_auto_20200624_0748.py diff --git a/server/django_etebase/migrations/0019_auto_20200626_0748.py b/server/etebase_server/django/migrations/0019_auto_20200626_0748.py similarity index 100% rename from server/django_etebase/migrations/0019_auto_20200626_0748.py rename to server/etebase_server/django/migrations/0019_auto_20200626_0748.py diff --git a/server/django_etebase/migrations/0020_remove_collectionitemrevision_salt.py b/server/etebase_server/django/migrations/0020_remove_collectionitemrevision_salt.py similarity index 100% rename from server/django_etebase/migrations/0020_remove_collectionitemrevision_salt.py rename to server/etebase_server/django/migrations/0020_remove_collectionitemrevision_salt.py diff --git a/server/django_etebase/migrations/0021_auto_20200626_0913.py b/server/etebase_server/django/migrations/0021_auto_20200626_0913.py similarity index 95% rename from server/django_etebase/migrations/0021_auto_20200626_0913.py rename to server/etebase_server/django/migrations/0021_auto_20200626_0913.py index 3bb6e21..356d5fd 100644 --- a/server/django_etebase/migrations/0021_auto_20200626_0913.py +++ b/server/etebase_server/django/migrations/0021_auto_20200626_0913.py @@ -2,7 +2,7 @@ import django.core.validators from django.db import migrations, models -import django_etebase.models +from etebase_server.django.models import generate_stoken_uid class Migration(migrations.Migration): @@ -62,7 +62,7 @@ class Migration(migrations.Migration): name="uid", field=models.CharField( db_index=True, - default=django_etebase.models.generate_stoken_uid, + default=generate_stoken_uid, max_length=43, unique=True, validators=[ diff --git a/server/django_etebase/migrations/0022_auto_20200804_1059.py b/server/etebase_server/django/migrations/0022_auto_20200804_1059.py similarity index 100% rename from server/django_etebase/migrations/0022_auto_20200804_1059.py rename to server/etebase_server/django/migrations/0022_auto_20200804_1059.py diff --git a/server/django_etebase/migrations/0023_collectionitemchunk_collection.py b/server/etebase_server/django/migrations/0023_collectionitemchunk_collection.py similarity index 100% rename from server/django_etebase/migrations/0023_collectionitemchunk_collection.py rename to server/etebase_server/django/migrations/0023_collectionitemchunk_collection.py diff --git a/server/django_etebase/migrations/0024_auto_20200804_1209.py b/server/etebase_server/django/migrations/0024_auto_20200804_1209.py similarity index 100% rename from server/django_etebase/migrations/0024_auto_20200804_1209.py rename to server/etebase_server/django/migrations/0024_auto_20200804_1209.py diff --git a/server/django_etebase/migrations/0025_auto_20200804_1216.py b/server/etebase_server/django/migrations/0025_auto_20200804_1216.py similarity index 100% rename from server/django_etebase/migrations/0025_auto_20200804_1216.py rename to server/etebase_server/django/migrations/0025_auto_20200804_1216.py diff --git a/server/django_etebase/migrations/0026_auto_20200907_0752.py b/server/etebase_server/django/migrations/0026_auto_20200907_0752.py similarity index 100% rename from server/django_etebase/migrations/0026_auto_20200907_0752.py rename to server/etebase_server/django/migrations/0026_auto_20200907_0752.py diff --git a/server/django_etebase/migrations/0027_auto_20200907_0752.py b/server/etebase_server/django/migrations/0027_auto_20200907_0752.py similarity index 100% rename from server/django_etebase/migrations/0027_auto_20200907_0752.py rename to server/etebase_server/django/migrations/0027_auto_20200907_0752.py diff --git a/server/django_etebase/migrations/0028_auto_20200907_0754.py b/server/etebase_server/django/migrations/0028_auto_20200907_0754.py similarity index 95% rename from server/django_etebase/migrations/0028_auto_20200907_0754.py rename to server/etebase_server/django/migrations/0028_auto_20200907_0754.py index 24c6246..9a0f1ae 100644 --- a/server/django_etebase/migrations/0028_auto_20200907_0754.py +++ b/server/etebase_server/django/migrations/0028_auto_20200907_0754.py @@ -2,7 +2,7 @@ from django.db import migrations -from django_etebase.models import AccessLevels +from etebase_server.django.models import AccessLevels def change_access_level_to_int(apps, schema_editor): diff --git a/server/django_etebase/migrations/0029_auto_20200907_0801.py b/server/etebase_server/django/migrations/0029_auto_20200907_0801.py similarity index 100% rename from server/django_etebase/migrations/0029_auto_20200907_0801.py rename to server/etebase_server/django/migrations/0029_auto_20200907_0801.py diff --git a/server/django_etebase/migrations/0030_auto_20200922_0832.py b/server/etebase_server/django/migrations/0030_auto_20200922_0832.py similarity index 100% rename from server/django_etebase/migrations/0030_auto_20200922_0832.py rename to server/etebase_server/django/migrations/0030_auto_20200922_0832.py diff --git a/server/django_etebase/migrations/0031_auto_20201013_1336.py b/server/etebase_server/django/migrations/0031_auto_20201013_1336.py similarity index 100% rename from server/django_etebase/migrations/0031_auto_20201013_1336.py rename to server/etebase_server/django/migrations/0031_auto_20201013_1336.py diff --git a/server/django_etebase/migrations/0032_auto_20201013_1409.py b/server/etebase_server/django/migrations/0032_auto_20201013_1409.py similarity index 100% rename from server/django_etebase/migrations/0032_auto_20201013_1409.py rename to server/etebase_server/django/migrations/0032_auto_20201013_1409.py diff --git a/server/django_etebase/migrations/0033_collection_uid.py b/server/etebase_server/django/migrations/0033_collection_uid.py similarity index 100% rename from server/django_etebase/migrations/0033_collection_uid.py rename to server/etebase_server/django/migrations/0033_collection_uid.py diff --git a/server/django_etebase/migrations/0034_auto_20201214_1124.py b/server/etebase_server/django/migrations/0034_auto_20201214_1124.py similarity index 100% rename from server/django_etebase/migrations/0034_auto_20201214_1124.py rename to server/etebase_server/django/migrations/0034_auto_20201214_1124.py diff --git a/server/django_etebase/migrations/0035_auto_20201214_1126.py b/server/etebase_server/django/migrations/0035_auto_20201214_1126.py similarity index 100% rename from server/django_etebase/migrations/0035_auto_20201214_1126.py rename to server/etebase_server/django/migrations/0035_auto_20201214_1126.py diff --git a/server/django_etebase/migrations/0036_auto_20201214_1128.py b/server/etebase_server/django/migrations/0036_auto_20201214_1128.py similarity index 100% rename from server/django_etebase/migrations/0036_auto_20201214_1128.py rename to server/etebase_server/django/migrations/0036_auto_20201214_1128.py diff --git a/server/django_etebase/migrations/0037_auto_20210127_1237.py b/server/etebase_server/django/migrations/0037_auto_20210127_1237.py similarity index 100% rename from server/django_etebase/migrations/0037_auto_20210127_1237.py rename to server/etebase_server/django/migrations/0037_auto_20210127_1237.py diff --git a/server/django_etebase/migrations/__init__.py b/server/etebase_server/django/migrations/__init__.py similarity index 100% rename from server/django_etebase/migrations/__init__.py rename to server/etebase_server/django/migrations/__init__.py diff --git a/server/django_etebase/models.py b/server/etebase_server/django/models.py similarity index 99% rename from server/django_etebase/models.py rename to server/etebase_server/django/models.py index 4640e6d..e3a8e7f 100644 --- a/server/django_etebase/models.py +++ b/server/etebase_server/django/models.py @@ -96,7 +96,7 @@ class CollectionItem(models.Model): @cached_property def content(self) -> "CollectionItemRevision": - return self.revisions.get(current=True) + return self.revisions.filter(current=True)[0] @property def etag(self) -> str: diff --git a/server/etebase_server/django/signals.py b/server/etebase_server/django/signals.py new file mode 100644 index 0000000..53eb43d --- /dev/null +++ b/server/etebase_server/django/signals.py @@ -0,0 +1,4 @@ +from django.dispatch import Signal + +# Provides arguments "request" and "user" +user_signed_up = Signal() diff --git a/server/django_etebase/token_auth/__init__.py b/server/etebase_server/django/token_auth/__init__.py similarity index 100% rename from server/django_etebase/token_auth/__init__.py rename to server/etebase_server/django/token_auth/__init__.py diff --git a/server/django_etebase/token_auth/apps.py b/server/etebase_server/django/token_auth/apps.py similarity index 60% rename from server/django_etebase/token_auth/apps.py rename to server/etebase_server/django/token_auth/apps.py index a0e98be..43ebb74 100644 --- a/server/django_etebase/token_auth/apps.py +++ b/server/etebase_server/django/token_auth/apps.py @@ -2,4 +2,4 @@ from django.apps import AppConfig class TokenAuthConfig(AppConfig): - name = "django_etebase.token_auth" + name = "etebase_server.django.token_auth" diff --git a/server/django_etebase/token_auth/migrations/0001_initial.py b/server/etebase_server/django/token_auth/migrations/0001_initial.py similarity index 94% rename from server/django_etebase/token_auth/migrations/0001_initial.py rename to server/etebase_server/django/token_auth/migrations/0001_initial.py index 660b38c..906005c 100644 --- a/server/django_etebase/token_auth/migrations/0001_initial.py +++ b/server/etebase_server/django/token_auth/migrations/0001_initial.py @@ -3,7 +3,7 @@ from django.conf import settings from django.db import migrations, models import django.db.models.deletion -from django_etebase.token_auth import models as token_auth_models +from etebase_server.django.token_auth import models as token_auth_models class Migration(migrations.Migration): diff --git a/server/django_etebase/token_auth/migrations/__init__.py b/server/etebase_server/django/token_auth/migrations/__init__.py similarity index 100% rename from server/django_etebase/token_auth/migrations/__init__.py rename to server/etebase_server/django/token_auth/migrations/__init__.py diff --git a/server/django_etebase/token_auth/models.py b/server/etebase_server/django/token_auth/models.py similarity index 92% rename from server/django_etebase/token_auth/models.py rename to server/etebase_server/django/token_auth/models.py index dd5ae87..de2ffc1 100644 --- a/server/django_etebase/token_auth/models.py +++ b/server/etebase_server/django/token_auth/models.py @@ -1,7 +1,7 @@ from django.db import models from django.utils import timezone from django.utils.crypto import get_random_string -from myauth.models import get_typed_user_model +from etebase_server.myauth.models import get_typed_user_model User = get_typed_user_model() diff --git a/server/django_etebase/utils.py b/server/etebase_server/django/utils.py similarity index 93% rename from server/django_etebase/utils.py rename to server/etebase_server/django/utils.py index 3a05fd4..d4aca72 100644 --- a/server/django_etebase/utils.py +++ b/server/etebase_server/django/utils.py @@ -3,7 +3,7 @@ from dataclasses import dataclass from django.db.models import QuerySet from django.core.exceptions import PermissionDenied -from myauth.models import UserType, get_typed_user_model +from etebase_server.myauth.models import UserType, get_typed_user_model from . import app_settings diff --git a/server/etebase_fastapi/__init__.py b/server/etebase_server/fastapi/__init__.py similarity index 100% rename from server/etebase_fastapi/__init__.py rename to server/etebase_server/fastapi/__init__.py diff --git a/server/etebase_fastapi/db_hack.py b/server/etebase_server/fastapi/db_hack.py similarity index 100% rename from server/etebase_fastapi/db_hack.py rename to server/etebase_server/fastapi/db_hack.py diff --git a/server/etebase_fastapi/dependencies.py b/server/etebase_server/fastapi/dependencies.py similarity index 93% rename from server/etebase_fastapi/dependencies.py rename to server/etebase_server/fastapi/dependencies.py index 520d499..b4d5cf4 100644 --- a/server/etebase_fastapi/dependencies.py +++ b/server/etebase_server/fastapi/dependencies.py @@ -6,9 +6,9 @@ from fastapi.security import APIKeyHeader from django.utils import timezone from django.db.models import QuerySet -from django_etebase import models -from django_etebase.token_auth.models import AuthToken, get_default_expiry -from myauth.models import UserType, get_typed_user_model +from etebase_server.django import models +from etebase_server.django.token_auth.models import AuthToken, get_default_expiry +from etebase_server.myauth.models import UserType, get_typed_user_model from .exceptions import AuthenticationFailed from .utils import get_object_or_404 from .db_hack import django_db_cleanup_decorator diff --git a/server/etebase_fastapi/exceptions.py b/server/etebase_server/fastapi/exceptions.py similarity index 100% rename from server/etebase_fastapi/exceptions.py rename to server/etebase_server/fastapi/exceptions.py diff --git a/server/etebase_fastapi/main.py b/server/etebase_server/fastapi/main.py similarity index 96% rename from server/etebase_fastapi/main.py rename to server/etebase_server/fastapi/main.py index 3e0c1e9..e4abd6c 100644 --- a/server/etebase_fastapi/main.py +++ b/server/etebase_server/fastapi/main.py @@ -6,7 +6,7 @@ from fastapi.middleware.cors import CORSMiddleware from fastapi.middleware.trustedhost import TrustedHostMiddleware from fastapi.staticfiles import StaticFiles -from django_etebase import app_settings +from etebase_server.django import app_settings from .exceptions import CustomHttpException from .msgpack import MsgpackResponse @@ -43,7 +43,7 @@ def create_application(prefix="", middlewares=[]): app.include_router(websocket_router, prefix=f"{BASE_PATH}/ws", tags=["websocket"]) if settings.DEBUG: - from etebase_fastapi.routers.test_reset_view import test_reset_view_router + from .routers.test_reset_view import test_reset_view_router app.include_router(test_reset_view_router, prefix=f"{BASE_PATH}/test/authentication") diff --git a/server/etebase_fastapi/msgpack.py b/server/etebase_server/fastapi/msgpack.py similarity index 100% rename from server/etebase_fastapi/msgpack.py rename to server/etebase_server/fastapi/msgpack.py diff --git a/server/etebase_fastapi/redis.py b/server/etebase_server/fastapi/redis.py similarity index 61% rename from server/etebase_fastapi/redis.py rename to server/etebase_server/fastapi/redis.py index 3735e36..b4f7a04 100644 --- a/server/etebase_fastapi/redis.py +++ b/server/etebase_server/fastapi/redis.py @@ -1,7 +1,7 @@ import typing as t -import aioredis +from redis import asyncio as aioredis -from django_etebase import app_settings +from etebase_server.django import app_settings class RedisWrapper: @@ -12,12 +12,11 @@ class RedisWrapper: async def setup(self): if self.redis_uri is not None: - self.redis = await aioredis.create_redis_pool(self.redis_uri) + self.redis = await aioredis.from_url(self.redis_uri) async def close(self): - if self.redis is not None: - self.redis.close() - await self.redis.wait_closed() + if hasattr(self, "redis"): + await self.redis.close() @property def is_active(self): diff --git a/server/etebase_fastapi/routers/__init__.py b/server/etebase_server/fastapi/routers/__init__.py similarity index 100% rename from server/etebase_fastapi/routers/__init__.py rename to server/etebase_server/fastapi/routers/__init__.py diff --git a/server/etebase_fastapi/routers/authentication.py b/server/etebase_server/fastapi/routers/authentication.py similarity index 95% rename from server/etebase_fastapi/routers/authentication.py rename to server/etebase_server/fastapi/routers/authentication.py index fd21d21..d771a5c 100644 --- a/server/etebase_fastapi/routers/authentication.py +++ b/server/etebase_server/fastapi/routers/authentication.py @@ -14,12 +14,12 @@ from django.db import transaction from django.utils.functional import cached_property from fastapi import APIRouter, Depends, status, Request -from django_etebase import app_settings, models -from django_etebase.token_auth.models import AuthToken -from django_etebase.models import UserInfo -from django_etebase.signals import user_signed_up -from django_etebase.utils import create_user, get_user_queryset, CallbackContext -from myauth.models import UserType, get_typed_user_model +from etebase_server.django import app_settings, models +from etebase_server.django.token_auth.models import AuthToken +from etebase_server.django.models import UserInfo +from etebase_server.django.signals import user_signed_up +from etebase_server.django.utils import create_user, get_user_queryset, CallbackContext +from etebase_server.myauth.models import UserType, get_typed_user_model from ..exceptions import AuthenticationFailed, transform_validation_error, HttpError from ..msgpack import MsgpackRoute from ..utils import BaseModel, permission_responses, msgpack_encode, msgpack_decode, get_user_username_email_kwargs diff --git a/server/etebase_fastapi/routers/collection.py b/server/etebase_server/fastapi/routers/collection.py similarity index 99% rename from server/etebase_fastapi/routers/collection.py rename to server/etebase_server/fastapi/routers/collection.py index c8146f2..c847743 100644 --- a/server/etebase_fastapi/routers/collection.py +++ b/server/etebase_server/fastapi/routers/collection.py @@ -7,8 +7,8 @@ from django.db import transaction, IntegrityError from django.db.models import Q, QuerySet from fastapi import APIRouter, Depends, status, Request, BackgroundTasks -from django_etebase import models -from myauth.models import UserType +from etebase_server.django import models +from etebase_server.myauth.models import UserType from .authentication import get_authenticated_user from .websocket import get_ticket, TicketRequest, TicketOut from ..exceptions import HttpError, transform_validation_error, PermissionDenied, ValidationError @@ -396,7 +396,7 @@ def item_create(item_model: CollectionItemIn, collection: models.Collection, val if not created: # We don't have to use select_for_update here because the unique constraint on current guards against # the race condition. But it's a good idea because it'll lock and wait rather than fail. - current_revision = instance.revisions.filter(current=True).select_for_update().first() + current_revision = instance.revisions.filter(current=True).select_for_update()[0] assert current_revision is not None current_revision.current = None current_revision.save() diff --git a/server/etebase_fastapi/routers/invitation.py b/server/etebase_server/fastapi/routers/invitation.py similarity index 97% rename from server/etebase_fastapi/routers/invitation.py rename to server/etebase_server/fastapi/routers/invitation.py index 7e52978..adb51c6 100644 --- a/server/etebase_fastapi/routers/invitation.py +++ b/server/etebase_server/fastapi/routers/invitation.py @@ -4,9 +4,9 @@ from django.db import transaction, IntegrityError from django.db.models import QuerySet from fastapi import APIRouter, Depends, status, Request -from django_etebase import models -from django_etebase.utils import get_user_queryset, CallbackContext -from myauth.models import UserType, get_typed_user_model +from etebase_server.django import models +from etebase_server.django.utils import get_user_queryset, CallbackContext +from etebase_server.myauth.models import UserType, get_typed_user_model from .authentication import get_authenticated_user from ..exceptions import HttpError, PermissionDenied from ..msgpack import MsgpackRoute diff --git a/server/etebase_fastapi/routers/member.py b/server/etebase_server/fastapi/routers/member.py similarity index 96% rename from server/etebase_fastapi/routers/member.py rename to server/etebase_server/fastapi/routers/member.py index 38beb79..123357b 100644 --- a/server/etebase_fastapi/routers/member.py +++ b/server/etebase_server/fastapi/routers/member.py @@ -4,8 +4,8 @@ from django.db import transaction from django.db.models import QuerySet from fastapi import APIRouter, Depends, status -from django_etebase import models -from myauth.models import UserType, get_typed_user_model +from etebase_server.django import models +from etebase_server.myauth.models import UserType, get_typed_user_model from .authentication import get_authenticated_user from ..msgpack import MsgpackRoute from ..utils import get_object_or_404, BaseModel, permission_responses, PERMISSIONS_READ, PERMISSIONS_READWRITE diff --git a/server/etebase_fastapi/routers/test_reset_view.py b/server/etebase_server/fastapi/routers/test_reset_view.py similarity index 91% rename from server/etebase_fastapi/routers/test_reset_view.py rename to server/etebase_server/fastapi/routers/test_reset_view.py index 09638e4..7895697 100644 --- a/server/etebase_fastapi/routers/test_reset_view.py +++ b/server/etebase_server/fastapi/routers/test_reset_view.py @@ -3,11 +3,11 @@ from django.db import transaction from django.shortcuts import get_object_or_404 from fastapi import APIRouter, Request, status -from django_etebase.utils import get_user_queryset, CallbackContext +from etebase_server.django.utils import get_user_queryset, CallbackContext from .authentication import SignupIn, signup_save from ..msgpack import MsgpackRoute from ..exceptions import HttpError -from myauth.models import get_typed_user_model +from etebase_server.myauth.models import get_typed_user_model test_reset_view_router = APIRouter(route_class=MsgpackRoute, tags=["test helpers"]) User = get_typed_user_model() diff --git a/server/etebase_fastapi/routers/websocket.py b/server/etebase_server/fastapi/routers/websocket.py similarity index 83% rename from server/etebase_fastapi/routers/websocket.py rename to server/etebase_server/fastapi/routers/websocket.py index 3fc535f..443ee06 100644 --- a/server/etebase_fastapi/routers/websocket.py +++ b/server/etebase_server/fastapi/routers/websocket.py @@ -1,16 +1,17 @@ import asyncio import typing as t -import aioredis +from redis import asyncio as aioredis +from redis.exceptions import ConnectionError from asgiref.sync import sync_to_async from django.db.models import QuerySet from fastapi import APIRouter, Depends, WebSocket, WebSocketDisconnect, status import nacl.encoding import nacl.utils -from django_etebase import models -from django_etebase.utils import CallbackContext, get_user_queryset -from myauth.models import UserType, get_typed_user_model +from etebase_server.django import models +from etebase_server.django.utils import CallbackContext, get_user_queryset +from etebase_server.myauth.models import UserType, get_typed_user_model from ..dependencies import get_collection_queryset, get_item_queryset from ..exceptions import NotSupported @@ -51,7 +52,7 @@ async def get_ticket( uid = nacl.encoding.URLSafeBase64Encoder.encode(nacl.utils.random(32)) ticket_model = TicketInner(user=user.id, req=ticket_request) ticket_raw = msgpack_encode(ticket_model.dict()) - await redisw.redis.set(uid, ticket_raw, expire=TICKET_VALIDITY_SECONDS * 1000) + await redisw.redis.set(uid, ticket_raw, ex=TICKET_VALIDITY_SECONDS * 1000) return TicketOut(ticket=uid) @@ -103,9 +104,9 @@ async def send_item_updates( async def redis_connector(websocket: WebSocket, ticket_model: TicketInner, user: UserType, stoken: t.Optional[str]): async def producer_handler(r: aioredis.Redis, ws: WebSocket): + pubsub = r.pubsub() channel_name = f"col.{ticket_model.req.collection}" - (channel,) = await r.psubscribe(channel_name) - assert isinstance(channel, aioredis.Channel) + await pubsub.subscribe(channel_name) # Send missing items if we are not up to date queryset: QuerySet[models.Collection] = get_collection_queryset(user) @@ -117,12 +118,20 @@ async def redis_connector(websocket: WebSocket, ticket_model: TicketInner, user: return await send_item_updates(websocket, collection, user, stoken) + async def handle_message(): + msg = await pubsub.get_message(ignore_subscribe_messages=True, timeout=20) + message_raw = t.cast(t.Optional[t.Tuple[str, bytes]], msg) + if message_raw: + _, message = message_raw + await ws.send_bytes(message) + try: while True: # We wait on the websocket so we fail if web sockets fail or get data receive = asyncio.create_task(websocket.receive()) done, pending = await asyncio.wait( - {receive, channel.wait_message()}, return_when=asyncio.FIRST_COMPLETED + {receive, handle_message()}, + return_when=asyncio.FIRST_COMPLETED, ) for task in pending: task.cancel() @@ -131,12 +140,7 @@ async def redis_connector(websocket: WebSocket, ticket_model: TicketInner, user: await websocket.close(code=status.WS_1008_POLICY_VIOLATION) return - message_raw = t.cast(t.Optional[t.Tuple[str, bytes]], await channel.get()) - if message_raw: - _, message = message_raw - await ws.send_bytes(message) - - except aioredis.errors.ConnectionClosedError: + except ConnectionError: await websocket.close(code=status.WS_1012_SERVICE_RESTART) except WebSocketDisconnect: pass diff --git a/server/etebase_fastapi/sendfile/LICENSE b/server/etebase_server/fastapi/sendfile/LICENSE similarity index 100% rename from server/etebase_fastapi/sendfile/LICENSE rename to server/etebase_server/fastapi/sendfile/LICENSE diff --git a/server/etebase_fastapi/sendfile/README.md b/server/etebase_server/fastapi/sendfile/README.md similarity index 100% rename from server/etebase_fastapi/sendfile/README.md rename to server/etebase_server/fastapi/sendfile/README.md diff --git a/server/etebase_fastapi/sendfile/__init__.py b/server/etebase_server/fastapi/sendfile/__init__.py similarity index 100% rename from server/etebase_fastapi/sendfile/__init__.py rename to server/etebase_server/fastapi/sendfile/__init__.py diff --git a/server/etebase_fastapi/sendfile/backends/__init__.py b/server/etebase_server/fastapi/sendfile/backends/__init__.py similarity index 100% rename from server/etebase_fastapi/sendfile/backends/__init__.py rename to server/etebase_server/fastapi/sendfile/backends/__init__.py diff --git a/server/etebase_fastapi/sendfile/backends/mod_wsgi.py b/server/etebase_server/fastapi/sendfile/backends/mod_wsgi.py similarity index 100% rename from server/etebase_fastapi/sendfile/backends/mod_wsgi.py rename to server/etebase_server/fastapi/sendfile/backends/mod_wsgi.py diff --git a/server/etebase_fastapi/sendfile/backends/nginx.py b/server/etebase_server/fastapi/sendfile/backends/nginx.py similarity index 100% rename from server/etebase_fastapi/sendfile/backends/nginx.py rename to server/etebase_server/fastapi/sendfile/backends/nginx.py diff --git a/server/etebase_fastapi/sendfile/backends/simple.py b/server/etebase_server/fastapi/sendfile/backends/simple.py similarity index 100% rename from server/etebase_fastapi/sendfile/backends/simple.py rename to server/etebase_server/fastapi/sendfile/backends/simple.py diff --git a/server/etebase_fastapi/sendfile/backends/xsendfile.py b/server/etebase_server/fastapi/sendfile/backends/xsendfile.py similarity index 100% rename from server/etebase_fastapi/sendfile/backends/xsendfile.py rename to server/etebase_server/fastapi/sendfile/backends/xsendfile.py diff --git a/server/etebase_fastapi/sendfile/utils.py b/server/etebase_server/fastapi/sendfile/utils.py similarity index 100% rename from server/etebase_fastapi/sendfile/utils.py rename to server/etebase_server/fastapi/sendfile/utils.py diff --git a/server/etebase_fastapi/stoken_handler.py b/server/etebase_server/fastapi/stoken_handler.py similarity index 97% rename from server/etebase_fastapi/stoken_handler.py rename to server/etebase_server/fastapi/stoken_handler.py index b6f2999..b4c7eab 100644 --- a/server/etebase_fastapi/stoken_handler.py +++ b/server/etebase_server/fastapi/stoken_handler.py @@ -3,7 +3,7 @@ import typing as t from django.db.models import QuerySet from fastapi import status -from django_etebase.models import Stoken +from etebase_server.django.models import Stoken from .exceptions import HttpError diff --git a/server/etebase_fastapi/utils.py b/server/etebase_server/fastapi/utils.py similarity index 92% rename from server/etebase_fastapi/utils.py rename to server/etebase_server/fastapi/utils.py index 09c223e..334633c 100644 --- a/server/etebase_fastapi/utils.py +++ b/server/etebase_server/fastapi/utils.py @@ -10,9 +10,9 @@ from pydantic import BaseModel as PyBaseModel from django.db.models import Model, QuerySet from django.core.exceptions import ObjectDoesNotExist -from django_etebase import app_settings -from django_etebase.models import AccessLevels -from myauth.models import UserType, get_typed_user_model +from etebase_server.django import app_settings +from etebase_server.django.models import AccessLevels +from etebase_server.myauth.models import UserType, get_typed_user_model from .exceptions import HttpError, HttpErrorOut diff --git a/server/myauth/__init__.py b/server/etebase_server/myauth/__init__.py similarity index 100% rename from server/myauth/__init__.py rename to server/etebase_server/myauth/__init__.py diff --git a/server/myauth/admin.py b/server/etebase_server/myauth/admin.py similarity index 100% rename from server/myauth/admin.py rename to server/etebase_server/myauth/admin.py diff --git a/server/myauth/apps.py b/server/etebase_server/myauth/apps.py similarity index 54% rename from server/myauth/apps.py rename to server/etebase_server/myauth/apps.py index 96cb29b..65e21b1 100644 --- a/server/myauth/apps.py +++ b/server/etebase_server/myauth/apps.py @@ -2,4 +2,5 @@ from django.apps import AppConfig class MyauthConfig(AppConfig): - name = "myauth" + name = "etebase_server.myauth" + label = "myauth" diff --git a/server/myauth/forms.py b/server/etebase_server/myauth/forms.py similarity index 92% rename from server/myauth/forms.py rename to server/etebase_server/myauth/forms.py index fc2be74..7681835 100644 --- a/server/myauth/forms.py +++ b/server/etebase_server/myauth/forms.py @@ -1,6 +1,6 @@ from django import forms from django.contrib.auth.forms import UsernameField -from myauth.models import get_typed_user_model +from etebase_server.myauth.models import get_typed_user_model User = get_typed_user_model() diff --git a/server/etebase_server/myauth/ldap.py b/server/etebase_server/myauth/ldap.py new file mode 100644 index 0000000..a0912d5 --- /dev/null +++ b/server/etebase_server/myauth/ldap.py @@ -0,0 +1,109 @@ +import logging + +from django.utils import timezone +from django.conf import settings +from django.core.exceptions import PermissionDenied as DjangoPermissionDenied +from etebase_server.django.utils import CallbackContext +from etebase_server.myauth.models import get_typed_user_model, UserType +from etebase_server.fastapi.dependencies import get_authenticated_user +from etebase_server.fastapi.exceptions import PermissionDenied as FastAPIPermissionDenied +from fastapi import Depends + +import ldap + +User = get_typed_user_model() + + +def ldap_setting(name, default): + """Wrapper around django.conf.settings""" + return getattr(settings, f"LDAP_{name}", default) + + +class LDAPConnection: + __instance__ = None + __user_cache = {} # Username -> Valid until + + @staticmethod + def get_instance(): + """To get a Singleton""" + if not LDAPConnection.__instance__: + return LDAPConnection() + else: + return LDAPConnection.__instance__ + + def __init__(self): + # Cache some settings + self.__LDAP_FILTER = ldap_setting("FILTER", "") + self.__LDAP_SEARCH_BASE = ldap_setting("SEARCH_BASE", "") + + # The time a cache entry is valid (in hours) + try: + self.__LDAP_CACHE_TTL = int(ldap_setting("CACHE_TTL", "")) + except ValueError: + logging.error("Invalid value for cache_ttl. Defaulting to 1 hour") + self.__LDAP_CACHE_TTL = 1 + + password = ldap_setting("BIND_PW", "") + if not password: + pw_file = ldap_setting("BIND_PW_FILE", "") + if pw_file: + with open(pw_file, "r") as f: + password = f.read().replace("\n", "") + + self.__ldap_connection = ldap.initialize(ldap_setting("SERVER", "")) + try: + self.__ldap_connection.simple_bind_s(ldap_setting("BIND_DN", ""), password) + except ldap.LDAPError as err: + logging.error(f"LDAP Error occuring during bind: {err.desc}") + + def __is_cache_valid(self, username): + """Returns True if the cache entry is still valid. Returns False otherwise.""" + if username in self.__user_cache: + if timezone.now() <= self.__user_cache[username]: + # Cache entry is still valid + return True + return False + + def __remove_cache(self, username): + del self.__user_cache[username] + + def has_user(self, username): + """ + Since we don't care about the password and so authentication + another way, all we care about is whether the user exists. + """ + if self.__is_cache_valid(username): + return True + if username in self.__user_cache: + self.__remove_cache(username) + + filterstr = self.__LDAP_FILTER.replace("%s", username) + try: + result = self.__ldap_connection.search_s(self.__LDAP_SEARCH_BASE, ldap.SCOPE_SUBTREE, filterstr=filterstr) + except ldap.NO_RESULTS_RETURNED: + # We handle the specific error first and the the generic error, as + # we may expect ldap.NO_RESULTS_RETURNED, but not any other error + return False + except ldap.LDAPError as err: + logging.error(f"Error occured while performing an LDAP query: {err.desc}") + return False + + if len(result) == 1: + self.__user_cache[username] = timezone.now() + timezone.timedelta(hours=self.__LDAP_CACHE_TTL) + return True + return False + + +def is_user_in_ldap(user: UserType = Depends(get_authenticated_user)): + if not LDAPConnection.get_instance().has_user(user.username): + raise FastAPIPermissionDenied(detail="User not in LDAP directory.") + + +def create_user(context: CallbackContext, *args, **kwargs): + """ + A create_user function which first checks if the user already exists in the + configured LDAP directory. + """ + if not LDAPConnection.get_instance().has_user(kwargs["username"]): + raise DjangoPermissionDenied("User not in the LDAP directory.") + return User.objects.create_user(*args, **kwargs) diff --git a/server/myauth/migrations/0001_initial.py b/server/etebase_server/myauth/migrations/0001_initial.py similarity index 100% rename from server/myauth/migrations/0001_initial.py rename to server/etebase_server/myauth/migrations/0001_initial.py diff --git a/server/myauth/migrations/0002_auto_20200515_0801.py b/server/etebase_server/myauth/migrations/0002_auto_20200515_0801.py similarity index 85% rename from server/myauth/migrations/0002_auto_20200515_0801.py rename to server/etebase_server/myauth/migrations/0002_auto_20200515_0801.py index 068c9ae..15e35d5 100644 --- a/server/myauth/migrations/0002_auto_20200515_0801.py +++ b/server/etebase_server/myauth/migrations/0002_auto_20200515_0801.py @@ -1,7 +1,7 @@ # Generated by Django 3.0.3 on 2020-05-15 08:01 from django.db import migrations, models -import myauth.models +import etebase_server.myauth.models as myauth_models class Migration(migrations.Migration): @@ -19,7 +19,7 @@ class Migration(migrations.Migration): help_text="Required. 150 characters or fewer. Letters, digits and ./+/-/_ only.", max_length=150, unique=True, - validators=[myauth.models.UnicodeUsernameValidator()], + validators=[myauth_models.UnicodeUsernameValidator()], verbose_name="username", ), ), diff --git a/server/myauth/migrations/0003_auto_20201119_0810.py b/server/etebase_server/myauth/migrations/0003_auto_20201119_0810.py similarity index 84% rename from server/myauth/migrations/0003_auto_20201119_0810.py rename to server/etebase_server/myauth/migrations/0003_auto_20201119_0810.py index cfd2ec3..2fbc236 100644 --- a/server/myauth/migrations/0003_auto_20201119_0810.py +++ b/server/etebase_server/myauth/migrations/0003_auto_20201119_0810.py @@ -1,7 +1,7 @@ # Generated by Django 3.1.1 on 2020-11-19 08:10 from django.db import migrations, models -import myauth.models +import etebase_server.myauth.models as myauth_models class Migration(migrations.Migration): @@ -14,7 +14,7 @@ class Migration(migrations.Migration): migrations.AlterModelManagers( name="user", managers=[ - ("objects", myauth.models.UserManager()), + ("objects", myauth_models.UserManager()), ], ), migrations.AlterField( @@ -30,7 +30,7 @@ class Migration(migrations.Migration): help_text="Required. 150 characters or fewer. Letters, digits and ./-/_ only.", max_length=150, unique=True, - validators=[myauth.models.UnicodeUsernameValidator()], + validators=[myauth_models.UnicodeUsernameValidator()], verbose_name="username", ), ), diff --git a/server/myauth/migrations/__init__.py b/server/etebase_server/myauth/migrations/__init__.py similarity index 100% rename from server/myauth/migrations/__init__.py rename to server/etebase_server/myauth/migrations/__init__.py diff --git a/server/myauth/models.py b/server/etebase_server/myauth/models.py similarity index 100% rename from server/myauth/models.py rename to server/etebase_server/myauth/models.py diff --git a/server/myauth/tests.py b/server/etebase_server/myauth/tests.py similarity index 100% rename from server/myauth/tests.py rename to server/etebase_server/myauth/tests.py diff --git a/server/myauth/views.py b/server/etebase_server/myauth/views.py similarity index 100% rename from server/myauth/views.py rename to server/etebase_server/myauth/views.py diff --git a/server/etebase_server/settings.py b/server/etebase_server/settings.py index 42ded0b..97ec7e9 100644 --- a/server/etebase_server/settings.py +++ b/server/etebase_server/settings.py @@ -15,7 +15,8 @@ import configparser from .utils import get_secret_from_file # Build paths inside the project like this: os.path.join(BASE_DIR, ...) -BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) +SOURCE_DIR = os.path.dirname(os.path.abspath(__file__)) +BASE_DIR = os.path.dirname(SOURCE_DIR) AUTH_USER_MODEL = "myauth.User" @@ -54,9 +55,9 @@ INSTALLED_APPS = [ "django.contrib.sessions", "django.contrib.messages", "django.contrib.staticfiles", - "myauth.apps.MyauthConfig", - "django_etebase.apps.DjangoEtebaseConfig", - "django_etebase.token_auth.apps.TokenAuthConfig", + "etebase_server.myauth.apps.MyauthConfig", + "etebase_server.django.apps.DjangoEtebaseConfig", + "etebase_server.django.token_auth.apps.TokenAuthConfig", ] MIDDLEWARE = [ @@ -74,7 +75,7 @@ ROOT_URLCONF = "etebase_server.urls" TEMPLATES = [ { "BACKEND": "django.template.backends.django.DjangoTemplates", - "DIRS": [os.path.join(BASE_DIR, "templates")], + "DIRS": [os.path.join(SOURCE_DIR, "templates")], "APP_DIRS": True, "OPTIONS": { "context_processors": [ @@ -139,6 +140,8 @@ config_locations = [ "/etc/etebase-server/etebase-server.ini", ] +ETEBASE_CREATE_USER_FUNC = "etebase_server.django.utils.create_user_blocked" + # Use config file if present if any(os.path.isfile(x) for x in config_locations): config = configparser.ConfigParser() @@ -164,10 +167,30 @@ if any(os.path.isfile(x) for x in config_locations): if "database" in config: DATABASES = {"default": {x.upper(): y for x, y in config.items("database")}} -ETEBASE_CREATE_USER_FUNC = "django_etebase.utils.create_user_blocked" + if "database-options" in config: + DATABASES["default"]["OPTIONS"] = config["database-options"] + + if "ldap" in config: + ldap = config["ldap"] + LDAP_SERVER = ldap.get("server", "") + LDAP_SEARCH_BASE = ldap.get("search_base", "") + LDAP_FILTER = ldap.get("filter", "") + LDAP_BIND_DN = ldap.get("bind_dn", "") + LDAP_BIND_PW = ldap.get("bind_pw", "") + LDAP_BIND_PW_FILE = ldap.get("bind_pw_file", "") + LDAP_CACHE_TTL = ldap.get("cache_ttl", "") + + if not LDAP_BIND_DN: + raise Exception("LDAP enabled but bind_dn is not set!") + if not LDAP_BIND_PW and not LDAP_BIND_PW_FILE: + raise Exception("LDAP enabled but both bind_pw and bind_pw_file are not set!") + + # Configure EteBase to use LDAP + ETEBASE_CREATE_USER_FUNC = "etebase_server.myauth.ldap.create_user" + ETEBASE_API_PERMISSIONS_READ = ["etebase_server.myauth.ldap.is_user_in_ldap"] # Efficient file streaming (for large files) -SENDFILE_BACKEND = "etebase_fastapi.sendfile.backends.simple" +SENDFILE_BACKEND = "etebase_server.fastapi.sendfile.backends.simple" SENDFILE_ROOT = MEDIA_ROOT # Make an `etebase_server_settings` module available to override settings. diff --git a/server/templates/success.html b/server/etebase_server/templates/success.html similarity index 100% rename from server/templates/success.html rename to server/etebase_server/templates/success.html diff --git a/server/etebase_server/urls.py b/server/etebase_server/urls.py index 7cf5a60..574ddf7 100644 --- a/server/etebase_server/urls.py +++ b/server/etebase_server/urls.py @@ -1,25 +1,13 @@ import os from django.conf import settings -from django.conf.urls import url from django.contrib import admin -from django.urls import path, re_path +from django.urls import path from django.views.generic import TemplateView from django.views.static import serve from django.contrib.staticfiles import finders urlpatterns = [ - url(r"^admin/", admin.site.urls), + path("admin/", admin.site.urls), path("", TemplateView.as_view(template_name="success.html")), ] - -if settings.DEBUG: - - def serve_static(request, path): - filename = finders.find(path) - dirname = os.path.dirname(filename) - basename = os.path.basename(filename) - - return serve(request, basename, dirname) - - urlpatterns += [re_path(r"^static/(?P.*)$", serve_static)] diff --git a/server/etebase_server/utils.py b/server/etebase_server/utils.py index 64ed657..9f56457 100644 --- a/server/etebase_server/utils.py +++ b/server/etebase_server/utils.py @@ -13,6 +13,8 @@ # along with this program. If not, see . from django.core.management import utils +import os +import stat def get_secret_from_file(path): @@ -21,6 +23,7 @@ def get_secret_from_file(path): return f.read().strip() except EnvironmentError: with open(path, "w") as f: + os.chmod(path, stat.S_IRUSR | stat.S_IWUSR) secret_key = utils.get_random_secret_key() f.write(secret_key) return secret_key diff --git a/server/example-configs/nginx-uwsgi/README.md b/server/example-configs/nginx-uwsgi/README.md deleted file mode 100644 index 55b5fa5..0000000 --- a/server/example-configs/nginx-uwsgi/README.md +++ /dev/null @@ -1,22 +0,0 @@ -# Running `etebase` under `nginx` and `uwsgi` - -This configuration assumes that etebase server has been installed in the home folder of a non privileged user -called `EtebaseUser` following the instructions in . Also that static -files have been collected at `/srv/http/etebase_server` by running the following commands: - -```shell -sudo mkdir -p /srv/http/etebase_server/static -sudo chown -R EtebaseUser /srv/http/etebase_server -sudo su EtebaseUser -cd /path/to/etebase -ln -s /srv/http/etebase_server/static static -./manage.py collectstatic -``` - -It is also assumed that `nginx` and `uwsgi` have been installed system wide by `root`, and that `nginx` is running as user/group `www-data`. - -In this setup, `uwsgi` running as a `systemd` service as `root` creates a unix socket with read-write access -to both `EtebaseUser` and `nginx`. It then drops its `root` privilege and runs `etebase` as `EtebaseUser`. - -`nginx` listens on the `https` port (or a non standard port `https` port if desired), delivers static pages directly -and for everything else, communicates with `etebase` over the unix socket. diff --git a/server/example-configs/nginx-uwsgi/etebase.ini b/server/example-configs/nginx-uwsgi/etebase.ini deleted file mode 100644 index a2ebe97..0000000 --- a/server/example-configs/nginx-uwsgi/etebase.ini +++ /dev/null @@ -1,15 +0,0 @@ -# uwsgi configuration file -# typical location of this file would be /etc/uwsgi/sites/etebase.ini - -[uwsgi] -socket = /path/to/etebase_server.sock -chown-socket = EtebaseUser:www-data -chmod-socket = 660 -vacuum = true - - -uid = EtebaseUser -chdir = /path/to/etebase -home = %(chdir)/.venv -module = etebase_server.wsgi -master = true diff --git a/server/example-configs/nginx-uwsgi/etesync.ini b/server/example-configs/nginx-uwsgi/etesync.ini deleted file mode 100644 index e79eeee..0000000 --- a/server/example-configs/nginx-uwsgi/etesync.ini +++ /dev/null @@ -1,15 +0,0 @@ -# uwsgi configuration file -# typical location of this file would be /etc/uwsgi/sites/etesync.ini - -[uwsgi] -socket = /path/to/etesync_server.sock -chown-socket = EtesyncUser:www-data -chmod-socket = 660 -vacuum = true - - -uid = EtesyncUser -chdir = /path/to/etesync -home = %(chdir)/.venv -module = etesync_server.wsgi -master = true diff --git a/server/example-configs/nginx-uwsgi/my.server.name.conf b/server/example-configs/nginx-uwsgi/my.server.name.conf deleted file mode 100644 index 6b5de6e..0000000 --- a/server/example-configs/nginx-uwsgi/my.server.name.conf +++ /dev/null @@ -1,36 +0,0 @@ -# nginx configuration for etebase server running on https://my.server.name -# typical location of this file would be /etc/nginx/sites-available/my.server.name.conf - -server { - server_name my.server.name; - - root /srv/http/etebase_server; - - client_max_body_size 20M; - - location /static { - expires 1y; - try_files $uri $uri/ =404; - } - - location /media { - expires 1y; - try_files $uri $uri/ =404; - } - - location / { - uwsgi_pass unix:/path/to/etebase_server.sock; - include uwsgi_params; - } - - # change 443 to say 9443 to run on a non standard port - listen 443 ssl; - listen [::]:443 ssl; - # Enable these two instead of the two above if your nginx supports http2 - # listen 443 ssl http2; - # listen [::]:443 ssl http2; - - ssl_certificate /path/to/certificate-file - ssl_certificate_key /path/to/certificate-key-file - # other ssl directives as needed -} diff --git a/server/example-configs/nginx-uwsgi/uwsgi.service b/server/example-configs/nginx-uwsgi/uwsgi.service deleted file mode 100644 index 9941ec3..0000000 --- a/server/example-configs/nginx-uwsgi/uwsgi.service +++ /dev/null @@ -1,15 +0,0 @@ -# systemd unit for running uwsgi in emperor mode -# typical location of this file would be /etc/systemd/system/uwsgi.service - -[Unit] -Description=uWSGI Emperor service - -[Service] -ExecStart=/usr/local/bin/uwsgi --emperor /etc/uwsgi/sites -Restart=always -KillSignal=SIGQUIT -Type=notify -NotifyAccess=all - -[Install] -WantedBy=multi-user.target diff --git a/server/manage.py b/server/manage.py index 91277fb..a3a8741 100755 --- a/server/manage.py +++ b/server/manage.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 """Django's command-line utility for administrative tasks.""" import os import sys diff --git a/server/pyproject.toml b/server/pyproject.toml index e34796e..b622800 100644 --- a/server/pyproject.toml +++ b/server/pyproject.toml @@ -1,2 +1,6 @@ [tool.black] -line-length = 120 \ No newline at end of file +line-length = 120 + +[build-system] +requires = ["setuptools>=42"] +build-backend = "setuptools.build_meta" diff --git a/server/requirements-dev.txt b/server/requirements-dev.txt index 2be0ac0..ae24fb4 100644 --- a/server/requirements-dev.txt +++ b/server/requirements-dev.txt @@ -1,65 +1,66 @@ # -# This file is autogenerated by pip-compile with python 3.10 -# To update, run: +# This file is autogenerated by pip-compile with Python 3.11 +# by the following command: # # pip-compile --output-file=requirements-dev.txt requirements.in/development.txt # -asgiref==3.5.0 +asgiref==3.5.2 # via django -black==22.1.0 +black==22.10.0 # via -r requirements.in/development.txt -click==8.0.4 +build==0.9.0 + # via pip-tools +click==8.1.3 # via # black # pip-tools -coverage==6.3.2 +coverage==6.5.0 # via -r requirements.in/development.txt -django==3.2.12 +django==3.2.16 # via # -r requirements.in/development.txt # django-stubs # django-stubs-ext -django-stubs==1.9.0 +django-stubs==1.13.0 # via -r requirements.in/development.txt -django-stubs-ext==0.3.1 +django-stubs-ext==0.7.0 # via django-stubs -mypy==0.941 +mypy==0.991 # via django-stubs mypy-extensions==0.4.3 # via # black # mypy -pathspec==0.9.0 +packaging==21.3 + # via build +pathspec==0.10.2 # via black -pep517==0.12.0 - # via pip-tools -pip-tools==6.5.1 +pep517==0.13.0 + # via build +pip-tools==6.11.0 # via -r requirements.in/development.txt -platformdirs==2.5.1 +platformdirs==2.6.0 # via black -pytz==2022.1 +pyparsing==3.0.9 + # via packaging +pytz==2022.6 # via django pywatchman==1.4.1 # via -r requirements.in/development.txt -sqlparse==0.4.2 +sqlparse==0.4.3 # via django -toml==0.10.2 - # via django-stubs tomli==2.0.1 - # via - # black - # mypy - # pep517 -types-pytz==2021.3.6 # via django-stubs -types-pyyaml==6.0.5 +types-pytz==2022.6.0.1 + # via django-stubs +types-pyyaml==6.0.12.2 # via django-stubs -typing-extensions==4.1.1 +typing-extensions==4.4.0 # via # django-stubs # django-stubs-ext # mypy -wheel==0.37.1 +wheel==0.38.4 # via pip-tools # The following packages are considered to be unsafe in a requirements file: diff --git a/server/requirements.in/base.txt b/server/requirements.in/base.txt index e253c5e..6410748 100644 --- a/server/requirements.in/base.txt +++ b/server/requirements.in/base.txt @@ -5,4 +5,4 @@ fastapi typing_extensions uvicorn[standard] aiofiles -aioredis +redis>=4.2.0rc1 diff --git a/server/requirements.txt b/server/requirements.txt index 45c96d4..51fa812 100644 --- a/server/requirements.txt +++ b/server/requirements.txt @@ -1,67 +1,64 @@ # -# This file is autogenerated by pip-compile with python 3.10 -# To update, run: +# This file is autogenerated by pip-compile with Python 3.11 +# by the following command: # # pip-compile --output-file=requirements.txt requirements.in/base.txt # -aiofiles==0.8.0 +aiofiles==22.1.0 # via -r requirements.in/base.txt -aioredis==2.0.1 - # via -r requirements.in/base.txt -anyio==3.5.0 +anyio==3.6.2 # via # starlette - # watchgod -asgiref==3.5.0 - # via - # django - # uvicorn + # watchfiles +asgiref==3.5.2 + # via django async-timeout==4.0.2 - # via aioredis -cffi==1.15.0 + # via redis +cffi==1.15.1 # via pynacl -click==8.0.4 +click==8.1.3 # via uvicorn -django==3.2.12 +django==3.2.16 # via -r requirements.in/base.txt -fastapi==0.75.0 +fastapi==0.88.0 # via -r requirements.in/base.txt -h11==0.13.0 +h11==0.14.0 # via uvicorn -httptools==0.4.0 +httptools==0.5.0 # via uvicorn -idna==3.3 +idna==3.4 # via anyio -msgpack==1.0.3 +msgpack==1.0.4 # via -r requirements.in/base.txt pycparser==2.21 # via cffi -pydantic==1.9.0 +pydantic==1.10.2 # via fastapi pynacl==1.5.0 # via -r requirements.in/base.txt -python-dotenv==0.19.2 +python-dotenv==0.21.0 # via uvicorn -pytz==2022.1 +pytz==2022.6 # via django pyyaml==6.0 # via uvicorn -sniffio==1.2.0 +redis==4.4.0 + # via -r requirements.in/base.txt +sniffio==1.3.0 # via anyio -sqlparse==0.4.2 +sqlparse==0.4.3 # via django -starlette==0.17.1 +starlette==0.22.0 # via fastapi -typing-extensions==4.1.1 +typing-extensions==4.4.0 # via # -r requirements.in/base.txt - # aioredis # pydantic -uvicorn[standard]==0.17.6 +uvicorn[standard]==0.20.0 # via -r requirements.in/base.txt -uvloop==0.16.0 +uvloop==0.17.0 # via uvicorn -watchgod==0.8.1 +watchfiles==0.18.1 # via uvicorn -websockets==10.2 +websockets==10.4 # via uvicorn diff --git a/server/setup.py b/server/setup.py new file mode 100644 index 0000000..964c8b3 --- /dev/null +++ b/server/setup.py @@ -0,0 +1,20 @@ +from setuptools import find_packages, setup + +setup( + name="etebase_server", + version="0.11.0", + description="An Etebase (EteSync 2.0) server", + url="https://www.etebase.com/", + classifiers=[ + "Development Status :: 5 - Production/Stable", + "Framework :: Django", + "Framework :: FastAPI", + "Intended Audience :: System Administrators", + "License :: OSI Approved :: GNU Affero General Public License v3", + ], + packages=find_packages(include=["etebase_server", "etebase_server.*"]), + install_requires=list(open("requirements.in/base.txt")), + package_data={ + "etebase_server": ["templates/*"], + }, +)