Skip to content
Snippets Groups Projects
Commit 31db16df authored by Tomáš Valenta's avatar Tomáš Valenta
Browse files

implement first permission level - secret readers, group based admin

parent 99a8ec88
No related branches found
No related tags found
No related merge requests found
Pipeline #12005 passed
...@@ -173,6 +173,17 @@ class ContractAdmin(MarkdownxGuardedModelAdmin, FieldsetsInlineMixin, NestedMode ...@@ -173,6 +173,17 @@ class ContractAdmin(MarkdownxGuardedModelAdmin, FieldsetsInlineMixin, NestedMode
for index, fieldset in enumerate(fieldsets_with_inlines) for index, fieldset in enumerate(fieldsets_with_inlines)
] ]
def get_queryset(self, request):
queryset = super().get_queryset(request)
if not request.user.has_perm("contracts.view_confidential"):
queryset = queryset.filter(is_public=True)
if not request.user.has_perm("contracts.approve"):
queryset = queryset.filter(is_approved=True)
return queryset
def save_model(self, request, obj, form, change) -> None: def save_model(self, request, obj, form, change) -> None:
if obj.created_by is None: if obj.created_by is None:
obj.created_by = request.user obj.created_by = request.user
...@@ -262,25 +273,50 @@ class SigneeAdmin(MarkdownxGuardedModelAdmin): ...@@ -262,25 +273,50 @@ class SigneeAdmin(MarkdownxGuardedModelAdmin):
form = SigneeAdminForm form = SigneeAdminForm
fields = (
"name",
"entity_type",
"address_street_with_number",
"address_district",
"address_zip",
"address_country",
"ico_number",
"load_ares_data_button",
"date_of_birth",
"department",
"role",
)
readonly_fields = ("load_ares_data_button",) readonly_fields = ("load_ares_data_button",)
list_filter = ("entity_type",) list_filter = ("entity_type",)
list_display = ("name", "entity_type") list_display = ("name", "entity_type")
def get_fields(self, request, obj=None):
fields = [
"name",
"entity_type",
"ico_number",
"department",
"role",
]
if (
obj is None # Creating
or obj.entity_has_public_address
or request.user.has_perm("contracts.view_confidential", obj)
):
entity_type_index = fields.index("entity_type")
fields[entity_type_index:entity_type_index] = [
"address_street_with_number",
"address_district",
"address_zip",
"address_country",
]
fields.insert(
fields.index("department") - 1,
"date_of_birth",
)
if (
obj is None # Allowed to create
or request.user.has_perm("contracts.edit_signee", obj)
):
fields.insert(
fields.index("ico_number"),
"load_ares_data_button"
)
return fields
def load_ares_data_button(self, obj): def load_ares_data_button(self, obj):
return format_html( return format_html(
'<button type="button" id="load_ares_data">Načíst data</button>' '<button type="button" id="load_ares_data">Načíst data</button>'
......
# Generated by Django 4.1.4 on 2023-03-24 10:05
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('contracts', '0012_alter_contractee_address_country_and_more'),
]
operations = [
migrations.AlterField(
model_name='contractee',
name='address_country',
field=models.CharField(default='CZ', max_length=256, verbose_name='Země'),
),
migrations.AlterField(
model_name='signee',
name='address_country',
field=models.CharField(default='CZ', max_length=256, verbose_name='Země'),
),
]
import typing
import logging import logging
import jwt import jwt
from django.conf import settings
from django.contrib.auth.models import Group from django.contrib.auth.models import Group
from django.conf import settings
from pirates.auth import PiratesOIDCAuthenticationBackend from pirates.auth import PiratesOIDCAuthenticationBackend
logging.basicConfig(level=logging.DEBUG) logging.basicConfig(level=logging.DEBUG)
class RegistryOIDCAuthenticationBackend(PiratesOIDCAuthenticationBackend): class RegistryOIDCAuthenticationBackend(PiratesOIDCAuthenticationBackend):
def _assign_new_user_groups(self, user, access_token, user_groups=None) -> None: def _assign_new_user_groups(
self,
user,
access_token: dict,
user_groups: typing.Union[None, list] = None
) -> None:
if user_groups is None: if user_groups is None:
user_groups = user.groups.all() user_groups = user.groups.all()
for group in access_token["groups"]: for group in access_token["groups"]:
if group.startswith("_"):
continue
group_name = f"sso_{group}" group_name = f"sso_{group}"
group = Group.objects.filter(name=group_name) group = Group.objects.filter(name=group_name)
...@@ -27,9 +36,12 @@ class RegistryOIDCAuthenticationBackend(PiratesOIDCAuthenticationBackend): ...@@ -27,9 +36,12 @@ class RegistryOIDCAuthenticationBackend(PiratesOIDCAuthenticationBackend):
if group not in user_groups: if group not in user_groups:
user.groups.add(group) user.groups.add(group)
user.save() def _remove_old_user_groups(
self,
def _remove_old_user_groups(self, user, access_token, user_groups=None) -> None: user,
access_token: dict,
user_groups: typing.Union[None, list] = None
) -> None:
if user_groups is None: if user_groups is None:
user_groups = user.groups.all() user_groups = user.groups.all()
...@@ -50,10 +62,17 @@ class RegistryOIDCAuthenticationBackend(PiratesOIDCAuthenticationBackend): ...@@ -50,10 +62,17 @@ class RegistryOIDCAuthenticationBackend(PiratesOIDCAuthenticationBackend):
user_groups = user.groups.all() user_groups = user.groups.all()
self._remove_old_user_groups( self._remove_old_user_groups(
user, decoded_access_token, user_groups=user_groups user,
decoded_access_token,
user_groups=user_groups
) )
self._assign_new_user_groups( self._assign_new_user_groups(
user, decoded_access_token, user_groups=user_groups user,
decoded_access_token,
user_groups=user_groups
) )
user.update_group_based_admin()
user.save(saved_by_auth=True)
return user return user
...@@ -3,8 +3,9 @@ ...@@ -3,8 +3,9 @@
# exit on error # exit on error
set -e set -e
# migrate database # Migrate database
python manage.py makemigrations # Custom Group model
python manage.py migrate python manage.py migrate
# start webserver # Start webserver
exec gunicorn -c gunicorn.conf.py registry.wsgi exec gunicorn -c gunicorn.conf.py registry.wsgi
# Generated by Django 4.1.4 on 2023-03-24 10:54
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('users', '0001_initial'),
]
operations = [
migrations.AddField(
model_name='user',
name='is_staff_based_on_group',
field=models.BooleanField(default=True, verbose_name='Admin přístup dle členství ve skupině'),
),
]
# Generated by Django 4.1.4 on 2023-03-24 10:59
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('users', '0002_user_is_staff_based_on_group'),
]
operations = [
migrations.AlterField(
model_name='user',
name='is_staff_based_on_group',
field=models.BooleanField(default=True, help_text='Určuje, zda bude "Administrační přístup" uživatele definován dle členství ve skupinách, nebo podle speciálního nastavení zde.', verbose_name='Administrační přístup dle členství ve skupině'),
),
]
from django.db import models
from django.contrib.auth.models import Group
from pirates import models as pirates_models from pirates import models as pirates_models
class User(pirates_models.AbstractUser): class User(pirates_models.AbstractUser):
is_staff_based_on_group = models.BooleanField(
default=True,
verbose_name="Administrační přístup dle členství ve skupině",
help_text=(
"Určuje, zda bude \"Administrační přístup\" uživatele "
"definován dle členství ve skupinách, nebo podle "
"speciálního nastavení zde."
)
)
def set_unusable_password(self) -> None: def set_unusable_password(self) -> None:
# Purely for compatibility with Guardian # Purely for compatibility with Guardian
pass pass
...@@ -14,19 +26,65 @@ class User(pirates_models.AbstractUser): ...@@ -14,19 +26,65 @@ class User(pirates_models.AbstractUser):
return f"{first_name}{self.last_name}" return f"{first_name}{self.last_name}"
# https://docs.djangoproject.com/en/4.1/ref/models/instances/#customizing-model-loading
@classmethod
def from_db(cls, db, field_names, values):
# Default implementation of from_db() (subject to change and could
# be replaced with super()).
if len(values) != len(cls._meta.concrete_fields):
values = list(values)
values.reverse()
values = [
values.pop() if f.attname in field_names else models.DEFERRED
for f in cls._meta.concrete_fields
]
instance = cls(*values)
instance._state.adding = False
instance._state.db = db
# customization to store the original field values on the instance
instance._loaded_values = dict(
zip(
field_names,
(
value
for value in values
if value is not models.DEFERRED
)
)
)
return instance
def save(self, *args, saved_by_auth: bool = False, **kwargs):
if (
not self._state.adding
and not saved_by_auth
and self._loaded_values["is_staff"] != self.is_staff
):
self.is_staff_based_on_group = False
return super().save(*args, **kwargs)
def update_group_based_admin(self) -> None:
if not self.is_staff_based_on_group:
return
self.is_staff_based_on_group = True
self.is_staff = self.groups.filter(is_staff=True).exists()
@property @property
def can_approve_contracts(self) -> bool: def can_approve_contracts(self) -> bool:
# TODO: Do we need the superuser check? return self.has_perm("contracts.approve")
return self.is_superuser or self.has_perm("contracts.approve")
@property @property
def can_create_contracts(self) -> bool: def can_create_contracts(self) -> bool:
# TODO: Do we need the superuser check? return self.has_perm("contracts.add")
return self.is_superuser or self.has_perm("contracts.add")
@property @property
def can_view_confidential(self) -> bool: def can_view_confidential(self) -> bool:
return self.is_superuser or self.has_perm("contracts.view_confidential") return self.has_perm("contracts.view_confidential")
@property @property
def contracts_to_approve_count(self) -> int: def contracts_to_approve_count(self) -> int:
...@@ -41,3 +99,12 @@ class User(pirates_models.AbstractUser): ...@@ -41,3 +99,12 @@ class User(pirates_models.AbstractUser):
app_label = "users" app_label = "users"
verbose_name = "Uživatel" verbose_name = "Uživatel"
verbose_name_plural = "Uživatelé" verbose_name_plural = "Uživatelé"
if not hasattr(Group, "is_staff"):
is_staff = models.BooleanField(
default=False,
verbose_name="Administrační přístup",
help_text="Určuje, zda se skupina může přihlásit do správy tohoto webu.",
)
is_staff.contribute_to_class(Group, "is_staff")
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment