Skip to content
Commits on Source (342)
......@@ -6,6 +6,232 @@ All notable changes to this project will be documented in this file.
The format is based on `Keep a Changelog`_,
and this project adheres to `Semantic Versioning`_.
`2.1`_ - 2021-11-05
-------------------
Added
~~~~~
* Provide an ``ExtensiblePolymorphicModel`` to support the features of extensible models for polymorphic models and vice-versa.
* Implement optional Sentry integration for error and performance tracing.
* Option to limit allowed scopes per application, including mixin to enforce that limit on OAuth resource views
* Support trusted OAuth applications that leave out the authorisation screen.
* Add birthplace to Person model.
Changed
~~~~~~~
* Replace dev.sh helper script with tox environments.
* OAuth Grant Flows are now configured system-wide instead of per app.
* Refactor OAuth2 application management views.
Fixed
~~~~~
* Fix default admin contacts
Credits
~~~~~~~
* We welcome new contributor 🐧 Jonathan Krüger!
* We welcome new contributor 🐭 Lukas Weichelt!
`2.0`_ - 2021-10-29
-------------------
Changed
~~~~~~~
* Refactor views/forms for creating/editing persons.
Fixed
~~~~~
* Fix order of submit buttons in login form and restructure login template
to make 2FA work correctly.
* Fix page title bug on the impersonate page.
* Users were able to edit the linked user if self-editing was activated.
* Users weren't able to edit the allowed fields although they were configured correctly.
* Provide `style.css` and icon files without any authentication to avoid caching issues.
Removed
~~~~~~~
* Remove mass linking of persons to accounts, bevcause the view had performance issues,
but was practically unused.
`2.0rc7`_ - 2021-10-18
----------------------
Fixed
~~~~~
* Configuration mechanisms for OpenID Connect were broken.
* Set a fixed version for django-sass-processor to avoid a bug with searching ``style.css`` in the wrong storage.
* Correct the z-index of the navbar to display the main title again on mobile devices.
Removed
~~~~~~~
* Leftovers from a functionality already dropped in the development process
(custom authentication backends and alternative login views).
`2.0rc6`_ - 2021-10-11
----------------------
Added
~~~~~
* OpenID Connect scope and accompanying claim ``groups``
* Support config files in JSON format
* Allow apps to dynamically generate OAuth scopes
Changed
~~~~~~~
* Do not log or e-mail ALLOWED_HOSTS violations
* Update translations.
* Use initial superuser settings as default contact and from addresses
Fixed
~~~~~
* Show link to imprint in footer
* Fix API for adding OAuth scopes in AppConfigs
* Deleting persons is possible again.
* Removed wrong changelog section
Removed
~~~~~~~
* Dropped data anonymization (django-hattori) support for now
* ``OAUTH2_SCOPES`` setting in apps is not supported anymore. Use ``get_all_scopes`` method
on ``AppConfig`` class instead.
`2.0rc5`_ - 2021-08-25
----------------------
Fixed
~~~~~
* The view for getting the progress of celery tasks didn't respect that there can be anonymous users.
* Updated django to latest 3.2.x
`2.0rc4`_ - 2021-08-01
----------------------
Added
~~~~~
* Allow to configure port for prometheus metrics endpoint.
Fixed
~~~~~
* Correctly deliver server errors to user
* Use text HTTP response for serviceworker.js insteas of binary stream
* Use Django permission instead of rule to prevent performance issues.
`2.0rc3`_ - 2021-07-26
----------------------
Added
~~~~~
* Support PDF generation without available request object (started completely from background).
* Display a loading animation while fetching search results in the sidebar.
Fixed
~~~~~
* Make search suggestions selectable using the arrow keys.
Fixed
~~~~~
* Use correct HTML 5 elements for the search frontend and fix CSS accordingly.
`2.0rc2`_ - 2021-06-24
---------------------
Added
~~~~~
* Allow to install system and build dependencies in docker build
`2.0rc1`_ - 2021-06-23
----------------------
Added
~~~~~
* Add option to disable dashboard auto updating as a user and sitewide.
Changed
~~~~~~~
* Use semantically correct html elements for headings and alerts.
Fixed
~~~~~
* Add missing dependency python-gnupg
* Add missing AWS options to ignore invalid ssl certificates
`2.0b2`_ - 2021-06-15
--------------------
Added
~~~~~~~
* Add option to disable dashboard auto updating as a user and sitewide.
Changed
~~~~~~~
* Add verbose names for all preference sections.
* Add verbose names for all openid connect scopes and show them in grant
view.
* Include public dashboard in navigation
* Update German translations.
Fixed
~~~~~
* Fix broken backup health check
* Make error recovery in about page work
Removed
~~~~~~~
* Drop all leftovers of DataTables.
`2.0b1`_ - 2021-06-01
---------------------
Changed
~~~~~~~
* Rename every occurance of "social account" by "third-party account".
* Use own templates and views for PWA meta and manifest.
* Use term "application" for all authorized OAuth2 applications/tokens.
* Use importlib instead of pkg_resources (no functional changes)
Fixed
~~~~~
* Fix installation documentation (nginx, uWSGI).
* Use a set for data checks registry to prevent double entries.
* Progress page tried to redirect even if the URL is empty.
Removed
~~~~~~~
* Drop django-pwa completely.
`2.0b0`_ - 2021-05-21
---------------------
......@@ -15,14 +241,14 @@ Added
* Allow defining several search configs for LDAP users and groups
* Use setuptools entrypoints to find apps
* Add django-cachalot as query cache
* Add `syncable_fields` property to `ExtensibleModel to discover fields
* Add ``syncable_fields`` property to ``ExtensibleModel`` to discover fields
sync backends can write to
* Add `aleksis-admin` script to wrap django-admin with pre-configured settings
* Add ``aleksis-admin`` script to wrap django-admin with pre-configured settings
* Auto-create persons for users if matching attributes are found
* Add `django-allauth` to allow authentication using OAuth, user registration,
* Add ``django-allauth`` to allow authentication using OAuth, user registration,
password changes and password reset
* Add OAuth2 and OpenID Connect provider support
* Add `django-uwsgi` to use uWSGI and Celery in development
* Add ``django-uwsgi`` to use uWSGI and Celery in development
* Add loading page for displaying Celery task progress
* Implement generic PDF generation using Chromium
* Support Amazon S3 storage for /media files
......@@ -60,7 +286,7 @@ Changed
* Default search index backend is now Whoosh with Redis storage
* Re-style search result page
* Move notifications to separate page with indicator in menu
* Move to `BigAutoField` for all AlekSIS apps
* Move to ``BigAutoField`` for all AlekSIS apps
* Require Django 3.2 and Python 3.9
* Person and group lists can now be filtered
* Allow displaying the default widget to anonymous users
......@@ -205,3 +431,14 @@ Fixed
.. _2.0a1: https://edugit.org/AlekSIS/Official/AlekSIS/-/tags/2.0a1
.. _2.0a2: https://edugit.org/AlekSIS/Official/AlekSIS/-/tags/2.0a2
.. _2.0b0: https://edugit.org/AlekSIS/Official/AlekSIS/-/tags/2.0b0
.. _2.0b1: https://edugit.org/AlekSIS/Official/AlekSIS/-/tags/2.0b1
.. _2.0b2: https://edugit.org/AlekSIS/Official/AlekSIS/-/tags/2.0b2
.. _2.0rc1: https://edugit.org/AlekSIS/Official/AlekSIS/-/tags/2.0rc1
.. _2.0rc2: https://edugit.org/AlekSIS/Official/AlekSIS/-/tags/2.0rc2
.. _2.0rc3: https://edugit.org/AlekSIS/Official/AlekSIS/-/tags/2.0rc3
.. _2.0rc4: https://edugit.org/AlekSIS/Official/AlekSIS/-/tags/2.0rc4
.. _2.0rc5: https://edugit.org/AlekSIS/Official/AlekSIS/-/tags/2.0rc5
.. _2.0rc6: https://edugit.org/AlekSIS/Official/AlekSIS/-/tags/2.0rc6
.. _2.0rc7: https://edugit.org/AlekSIS/Official/AlekSIS/-/tags/2.0rc7
.. _2.0: https://edugit.org/AlekSIS/Official/AlekSIS/-/tags/2.0
.. _2.1: https://edugit.org/AlekSIS/Official/AlekSIS/-/tags/2.1
......@@ -95,7 +95,18 @@ USER 33:33
# Additional steps
ONBUILD ARG APPS
ONBUILD ARG BUILD_DEPS
ONBUILD ARG SYSTEM_DEPS
ONBUILD USER 0:0
ONBUILD RUN set -e; \
if [ -n "$BUILD_DEPS" ]; then \
eatmydata apt-get update; \
eatmydata apt-get install -y $BUILD_DEPS; \
fi; \
if [ -n "$SYSTEM_DEPS" ]; then \
eatmydata apt-get update; \
eatmydata apt-get install -y $SYSTEM_DEPS; \
fi;
ONBUILD RUN set -e; \
if [ -n "$APPS" ]; then \
eatmydata pip install $APPS; \
......@@ -103,7 +114,7 @@ ONBUILD RUN set -e; \
eatmydata aleksis-admin yarn install; \
eatmydata aleksis-admin collectstatic --no-input; \
rm -rf /usr/local/share/.cache; \
eatmydata apt-get remove --purge -y yarnpkg; \
eatmydata apt-get remove --purge -y yarnpkg $BUILD_DEPS; \
eatmydata apt-get autoremove --purge -y; \
apt-get clean -y; \
rm -f /var/lib/apt/lists/*_*; \
......
import pkg_resources
from importlib import metadata
try:
from .celery import app as celery_app
......@@ -7,7 +7,7 @@ except ModuleNotFoundError:
celery_app = None
try:
__version__ = pkg_resources.get_distribution("AlekSIS-Core").version
__version__ = metadata.distribution("AlekSIS-Core").version
except Exception:
__version__ = "unknown"
......
from hattori.base import BaseAnonymizer, faker
from .models import Person
class PersonAnonymizer(BaseAnonymizer):
model = Person
attributes = [
("first_name", faker.first_name),
("last_name", faker.last_name),
("additional_name", ""),
("short_name", lambda **kwargs: faker.pystr(min_chars=3, max_chars=5, **kwargs)),
("street", faker.street_name),
("housenumber", faker.building_number),
("postal_code", faker.postcode),
("place", faker.city),
("phone_number", ""),
("mobile_number", ""),
("email", faker.email),
(
"date_of_birth",
lambda **kwargs: faker.date_of_birth(minimum_age=8, maximum_age=66, **kwargs),
),
("photo", ""),
]
......@@ -2,8 +2,10 @@ from typing import Any, Optional
import django.apps
from django.apps import apps
from django.conf import settings
from django.http import HttpRequest
from django.utils.module_loading import autodiscover_modules
from django.utils.translation import gettext as _
from dynamic_preferences.registries import preference_models
from health_check.plugins import plugin_dir
......@@ -74,9 +76,9 @@ class CoreConfig(AppConfig):
"""Get all data checks from all loaded models."""
from aleksis.core.data_checks import DataCheckRegistry
data_checks = []
data_checks = set()
for model in apps.get_models():
data_checks += getattr(model, "data_checks", [])
data_checks.update(getattr(model, "data_checks", []))
DataCheckRegistry.data_checks = data_checks
def preference_updated(
......@@ -136,3 +138,19 @@ class CoreConfig(AppConfig):
if has_person(user):
# Save the associated person to pick up defaults
user.person.save()
@classmethod
def get_all_scopes(cls) -> dict[str, str]:
scopes = {
"read": "Read anything the resource owner can read",
"write": "Write anything the resource owner can write",
}
if settings.OAUTH2_PROVIDER.get("OIDC_ENABLED", False):
scopes |= {
"openid": _("OpenID Connect scope"),
"profile": _("Given name, family name, link to profile and picture if existing."),
"address": _("Full home postal address"),
"email": _("Email address"),
"phone": _("Home and mobile phone"),
}
return scopes
......@@ -225,7 +225,7 @@ class DataCheck:
class DataCheckRegistry:
"""Create central registry for all data checks in AlekSIS."""
data_checks = []
data_checks: set = set()
@classproperty
def data_checks_by_name(cls):
......
......@@ -13,7 +13,6 @@ from allauth.account.forms import SignupForm
from allauth.account.utils import get_user_model, setup_user_email
from django_select2.forms import ModelSelect2MultipleWidget, ModelSelect2Widget, Select2Widget
from dynamic_preferences.forms import PreferenceForm
from guardian.core import ObjectPermissionChecker
from material import Fieldset, Layout, Row
from .mixins import ExtensibleForm, SchoolTermRelatedExtensibleForm
......@@ -23,6 +22,7 @@ from .models import (
DashboardWidget,
Group,
GroupType,
OAuthApplication,
Person,
SchoolTerm,
)
......@@ -31,59 +31,12 @@ from .registries import (
person_preferences_registry,
site_preferences_registry,
)
from .util.auth_helpers import AppScopes
from .util.core_helpers import get_site_preferences
class PersonAccountForm(forms.ModelForm):
"""Form to assign user accounts to persons in the frontend."""
class Meta:
model = Person
fields = ["last_name", "first_name", "user"]
widgets = {"user": Select2Widget(attrs={"class": "browser-default"})}
new_user = forms.CharField(required=False)
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
# Fields displayed only for informational purposes
self.fields["first_name"].disabled = True
self.fields["last_name"].disabled = True
def clean(self) -> None:
user = get_user_model()
if self.cleaned_data.get("new_user", None):
if self.cleaned_data.get("user", None):
# The user selected both an existing user and provided a name to create a new one
self.add_error(
"new_user",
_("You cannot set a new username when also selecting an existing user."),
)
elif user.objects.filter(username=self.cleaned_data["new_user"]).exists():
# The user tried to create a new user with the name of an existing user
self.add_error("new_user", _("This username is already in use."))
else:
# Create new User object and assign to form field for existing user
new_user_obj = user.objects.create_user(
self.cleaned_data["new_user"],
self.instance.email,
first_name=self.instance.first_name,
last_name=self.instance.last_name,
)
self.cleaned_data["user"] = new_user_obj
# Formset for batch-processing of assignments of users to persons
PersonsAccountsFormSet = forms.modelformset_factory(
Person, form=PersonAccountForm, max_num=0, extra=0
)
class EditPersonForm(ExtensibleForm):
"""Form to edit an existing person object in the frontend."""
class PersonForm(ExtensibleForm):
"""Form to edit or add a person object in the frontend."""
layout = Layout(
Fieldset(
......@@ -96,7 +49,11 @@ class EditPersonForm(ExtensibleForm):
Fieldset(_("Address"), Row("street", "housenumber"), Row("postal_code", "place")),
Fieldset(_("Contact data"), "email", Row("phone_number", "mobile_number")),
Fieldset(
_("Advanced personal data"), Row("sex", "date_of_birth"), Row("photo"), "guardians",
_("Advanced personal data"),
Row("date_of_birth", "place_of_birth"),
Row("sex"),
Row("photo"),
"guardians",
),
)
......@@ -117,6 +74,7 @@ class EditPersonForm(ExtensibleForm):
"mobile_number",
"email",
"date_of_birth",
"place_of_birth",
"sex",
"photo",
"guardians",
......@@ -142,25 +100,49 @@ class EditPersonForm(ExtensibleForm):
required=False, label=_("New user"), help_text=_("Create a new account")
)
def __init__(self, request: HttpRequest, *args, **kwargs):
def __init__(self, *args, **kwargs):
request = kwargs.pop("request", None)
super().__init__(*args, **kwargs)
# Disable non-editable fields
person_fields = set([field.name for field in Person.syncable_fields()]).intersection(
set(self.fields)
)
allowed_person_fields = get_site_preferences()["account__editable_fields_person"]
if self.instance:
checker = ObjectPermissionChecker(request.user)
checker.prefetch_perms([self.instance])
if (
request
and self.instance
and not request.user.has_perm("core.change_person", self.instance)
):
# First, disable all fields
for field in self.fields:
self.fields[field].disabled = True
for field in person_fields:
if not checker.has_perm(f"core.change_person_field_{field}", self.instance):
self.fields[field].disabled = True
# Then, activate allowed fields
for field in allowed_person_fields:
self.fields[field].disabled = False
def clean(self) -> None:
# Use code implemented in dedicated form to verify user selection
return PersonAccountForm.clean(self)
user = get_user_model()
if self.cleaned_data.get("new_user", None):
if self.cleaned_data.get("user", None):
# The user selected both an existing user and provided a name to create a new one
self.add_error(
"new_user",
_("You cannot set a new username when also selecting an existing user."),
)
elif user.objects.filter(username=self.cleaned_data["new_user"]).exists():
# The user tried to create a new user with the name of an existing user
self.add_error("new_user", _("This username is already in use."))
else:
# Create new User object and assign to form field for existing user
new_user_obj = user.objects.create_user(
self.cleaned_data["new_user"],
self.instance.email,
first_name=self.instance.first_name,
last_name=self.instance.last_name,
)
self.cleaned_data["user"] = new_user_obj
class EditGroupForm(SchoolTermRelatedExtensibleForm):
......@@ -615,3 +597,23 @@ class ListActionForm(ActionForm):
self.items = items
super().__init__(request, *args, **kwargs)
self.fields["selected_objects"].choices = self._get_choices()
class OAuthApplicationForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields["allowed_scopes"].widget = forms.SelectMultiple(
choices=list(AppScopes().get_all_scopes().items())
)
class Meta:
model = OAuthApplication
fields = (
"name",
"client_id",
"client_secret",
"client_type",
"allowed_scopes",
"redirect_uris",
"skip_authorization",
)
......@@ -38,9 +38,9 @@ class BaseBackupHealthCheck(BaseHealthCheckBackend):
self.add_error(_("The backup folder doesn't exist."))
return
if backups:
last_backup = backups[-1]
last_backup_time = dbbackup_utils.filename_to_date(last_backup)
time_gone_since_backup = last_backup_time - datetime.now()
last_backup = backups[:1]
last_backup_time = dbbackup_utils.filename_to_date(last_backup[0])
time_gone_since_backup = datetime.now() - last_backup_time
# Check if backup is older than configured time
if time_gone_since_backup.seconds > self.configured_seconds:
......
......@@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2021-05-21 19:54+0200\n"
"POT-Creation-Date: 2021-10-28 16:18+0200\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
......@@ -30,6 +30,6 @@ msgstr ""
msgid "OK"
msgstr ""
#: aleksis/core/static/js/main.js:121
#: aleksis/core/static/js/main.js:127
msgid "This page may contain outdated information since there is no internet connection."
msgstr ""
......@@ -3,32 +3,36 @@
# This file is distributed under the same license as the PACKAGE package.
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2021-05-21 19:54+0200\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"Language: \n"
"POT-Creation-Date: 2021-08-28 17:53+0200\n"
"PO-Revision-Date: 2021-10-28 14:37+0000\n"
"Last-Translator: Jonathan Weth <teckids@jonathanweth.de>\n"
"Language-Team: German <https://translate.edugit.org/projects/aleksis/"
"aleksis-core-js/de/>\n"
"Language: de_DE\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=n != 1;\n"
"X-Generator: Weblate 4.8\n"
#: aleksis/core/static/js/main.js:15
msgid "Today"
msgstr ""
msgstr "Heute"
#: aleksis/core/static/js/main.js:16
msgid "Cancel"
msgstr ""
msgstr "Abbrechen"
#: aleksis/core/static/js/main.js:17
msgid "OK"
msgstr ""
msgstr "OK"
#: aleksis/core/static/js/main.js:121
#: aleksis/core/static/js/main.js:127
msgid "This page may contain outdated information since there is no internet connection."
msgstr ""
"Diese Seite enthält vielleicht veraltete Informationen, da es keine "
"Internetverbindung gibt."
......@@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2021-05-21 19:54+0200\n"
"POT-Creation-Date: 2021-10-28 16:18+0200\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
......@@ -30,6 +30,6 @@ msgstr ""
msgid "OK"
msgstr ""
#: aleksis/core/static/js/main.js:121
#: aleksis/core/static/js/main.js:127
msgid "This page may contain outdated information since there is no internet connection."
msgstr ""
......@@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2021-05-21 19:54+0200\n"
"POT-Creation-Date: 2021-10-28 16:18+0200\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
......@@ -29,6 +29,6 @@ msgstr ""
msgid "OK"
msgstr ""
#: aleksis/core/static/js/main.js:121
#: aleksis/core/static/js/main.js:127
msgid "This page may contain outdated information since there is no internet connection."
msgstr ""
......@@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2021-05-21 19:54+0200\n"
"POT-Creation-Date: 2021-10-28 16:18+0200\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
......@@ -29,6 +29,6 @@ msgstr ""
msgid "OK"
msgstr ""
#: aleksis/core/static/js/main.js:121
#: aleksis/core/static/js/main.js:127
msgid "This page may contain outdated information since there is no internet connection."
msgstr ""
......@@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2021-05-21 19:54+0200\n"
"POT-Creation-Date: 2021-10-28 16:18+0200\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
......@@ -29,6 +29,6 @@ msgstr ""
msgid "OK"
msgstr ""
#: aleksis/core/static/js/main.js:121
#: aleksis/core/static/js/main.js:127
msgid "This page may contain outdated information since there is no internet connection."
msgstr ""