django_rbac: remove utils (#70894)

This commit is contained in:
Valentin Deniaud 2022-11-03 11:29:54 +01:00
parent 58dd0ae0be
commit f8f19e14b5
14 changed files with 68 additions and 189 deletions

View File

@ -25,23 +25,20 @@ class Authentic2RBACConfig(AppConfig):
from django.db.models.signals import post_delete, post_migrate, post_save
from authentic2.models import Service
from django_rbac import utils
from . import models, signal_handlers, signals
# update role parenting when new role parenting is created
post_save.connect(signal_handlers.role_parenting_post_save, sender=utils.get_role_parenting_model())
post_save.connect(signal_handlers.role_parenting_post_save, sender=models.RoleParenting)
# update role parenting when role parenting is deleted
post_delete.connect(
signal_handlers.role_parenting_post_delete, sender=utils.get_role_parenting_model()
)
post_delete.connect(signal_handlers.role_parenting_post_delete, sender=models.RoleParenting)
# or soft-created
signals.post_soft_create.connect(
signal_handlers.role_parenting_post_soft_delete, sender=utils.get_role_parenting_model()
signal_handlers.role_parenting_post_soft_delete, sender=models.RoleParenting
)
# or soft-deleted
signals.post_soft_delete.connect(
signal_handlers.role_parenting_post_soft_delete, sender=utils.get_role_parenting_model()
signal_handlers.role_parenting_post_soft_delete, sender=models.RoleParenting
)
# create CRUD operations and admin
post_migrate.connect(signal_handlers.create_base_operations, sender=self)

View File

@ -25,11 +25,9 @@ from django.db.models import query
from django.db.models.query import Prefetch, Q
from django.db.transaction import atomic
from django_rbac import utils
from django_rbac.utils import get_operation
from . import models as a2_models
from . import signals
from .utils import get_operation
class AbstractBaseManager(models.Manager):
@ -55,8 +53,7 @@ class OperationManager(models.Manager):
target_query = query.Q(target_ct=ContentType.objects.get_for_model(ContentType), target_id=ct.pk)
if isinstance(object_or_model, models.Model):
target_query |= query.Q(target_ct=ct, target_id=object.pk)
Permission = utils.get_permission_model()
qs = Permission.objects.for_user(user)
qs = a2_models.Permission.objects.for_user(user)
qs = qs.filter(operation__slug=operation_slug)
qs = qs.filter(ou_query & target_query)
return qs.exists()
@ -66,10 +63,9 @@ class PermissionManagerBase(models.Manager):
def get_by_natural_key(self, operation_slug, ou_nk, target_ct, target_nk):
qs = self.filter(operation__slug=operation_slug)
if ou_nk:
OrganizationalUnit = utils.get_ou_model()
try:
ou = OrganizationalUnit.objects.get_by_natural_key(*ou_nk)
except OrganizationalUnit.DoesNotExist:
ou = a2_models.OrganizationalUnit.objects.get_by_natural_key(*ou_nk)
except a2_models.OrganizationalUnit.DoesNotExist:
raise self.model.DoesNotExist
qs = qs.filter(ou=ou)
else:
@ -102,8 +98,7 @@ class PermissionQueryset(query.QuerySet):
"""Retrieve all permissions hold by an user through its role and
inherited roles.
"""
Role = utils.get_role_model()
roles = Role.objects.for_user(user=user)
roles = a2_models.Role.objects.for_user(user=user)
return self.filter(roles__in=roles)
def cleanup(self):
@ -207,14 +202,13 @@ class RoleParentingManager(models.Manager):
tls = Local()
def get_by_natural_key(self, parent_nk, child_nk, direct):
Role = utils.get_role_model()
try:
parent = Role.objects.get_by_natural_key(*parent_nk)
except Role.DoesNotExist:
parent = a2_models.Role.objects.get_by_natural_key(*parent_nk)
except a2_models.Role.DoesNotExist:
raise self.model.DoesNotExist
try:
child = Role.objects.get_by_natural_key(*child_nk)
except Role.DoesNotExist:
child = a2_models.Role.objects.get_by_natural_key(*child_nk)
except a2_models.Role.DoesNotExist:
raise self.model.DoesNotExist
return self.get(parent=parent, child=child, direct=direct)
@ -298,13 +292,11 @@ ON CONFLICT (parent_id, child_id, direct) DO UPDATE SET created = EXCLUDED.creat
@contextlib.contextmanager
def defer_update_transitive_closure():
from . import utils
RoleParentingManager.tls.DO_UPDATE_CLOSURE = False
try:
yield
if RoleParentingManager.tls.CLOSURE_UPDATED:
utils.get_role_parenting_model().objects.update_transitive_closure()
a2_models.RoleParenting.objects.update_transitive_closure()
finally:
RoleParentingManager.tls.DO_UPDATE_CLOSURE = True
RoleParentingManager.tls.CLOSURE_UPDATED = False

View File

@ -2,7 +2,7 @@
from django.db import migrations, models
import django_rbac.utils
import authentic2.a2_rbac.utils
class Migration(migrations.Migration):
@ -16,14 +16,14 @@ class Migration(migrations.Migration):
model_name='organizationalunit',
name='uuid',
field=models.CharField(
default=django_rbac.utils.get_hex_uuid, max_length=32, unique=True, verbose_name='uuid'
default=authentic2.a2_rbac.utils.get_hex_uuid, max_length=32, unique=True, verbose_name='uuid'
),
),
migrations.AlterField(
model_name='role',
name='uuid',
field=models.CharField(
default=django_rbac.utils.get_hex_uuid, max_length=32, unique=True, verbose_name='uuid'
default=authentic2.a2_rbac.utils.get_hex_uuid, max_length=32, unique=True, verbose_name='uuid'
),
),
]

View File

@ -37,9 +37,8 @@ from model_utils.managers import QueryManager
from authentic2.decorators import errorcollector
from authentic2.utils.cache import GlobalCache
from authentic2.validators import HexaColourValidator
from django_rbac import utils as rbac_utils
from . import app_settings, fields, managers
from . import app_settings, fields, managers, utils
class AbstractBase(models.Model):
@ -47,9 +46,7 @@ class AbstractBase(models.Model):
slug
"""
uuid = models.CharField(
max_length=32, verbose_name=_('uuid'), unique=True, default=rbac_utils.get_hex_uuid
)
uuid = models.CharField(max_length=32, verbose_name=_('uuid'), unique=True, default=utils.get_hex_uuid)
name = models.CharField(max_length=256, verbose_name=_('name'))
slug = models.SlugField(max_length=256, verbose_name=_('slug'))
description = models.TextField(verbose_name=_('description'), blank=True)
@ -65,11 +62,11 @@ class AbstractBase(models.Model):
def save(self, *args, **kwargs):
# truncate slug and add a hash if it's too long
if not self.slug:
self.slug = rbac_utils.generate_slug(self.name)
self.slug = utils.generate_slug(self.name)
if len(self.slug) > 256:
self.slug = self.slug[:252] + hashlib.md5(self.slug).hexdigest()[:4]
if not self.uuid:
self.uuid = rbac_utils.get_hex_uuid()
self.uuid = utils.get_hex_uuid()
return super().save(*args, **kwargs)
def natural_key(self):
@ -269,7 +266,7 @@ class Permission(models.Model):
to='a2_rbac.Operation', verbose_name=_('operation'), on_delete=models.CASCADE
)
ou = models.ForeignKey(
to=rbac_utils.get_ou_model_name(),
to=OrganizationalUnit,
verbose_name=_('organizational unit'),
related_name='scoped_permission',
null=True,
@ -370,7 +367,7 @@ Permission._meta.natural_key = [
class Role(AbstractBase):
ou = models.ForeignKey(
to=rbac_utils.get_ou_model_name(),
to=OrganizationalUnit,
verbose_name=_('organizational unit'),
swappable=True,
blank=True,
@ -380,9 +377,7 @@ class Role(AbstractBase):
members = models.ManyToManyField(
to=settings.AUTH_USER_MODEL, swappable=True, blank=True, related_name='roles'
)
permissions = models.ManyToManyField(
to=rbac_utils.get_permission_model_name(), related_name='roles', blank=True
)
permissions = models.ManyToManyField(to=Permission, related_name='roles', blank=True)
name = models.TextField(verbose_name=_('name'))
admin_scope_ct = models.ForeignKey(
to='contenttypes.ContentType',
@ -414,19 +409,15 @@ class Role(AbstractBase):
objects = managers.RoleQuerySet.as_manager()
def add_child(self, child):
RoleParenting = rbac_utils.get_role_parenting_model()
RoleParenting.objects.soft_create(self, child)
def remove_child(self, child):
RoleParenting = rbac_utils.get_role_parenting_model()
RoleParenting.objects.soft_delete(self, child)
def add_parent(self, parent):
RoleParenting = rbac_utils.get_role_parenting_model()
RoleParenting.objects.soft_create(parent, self)
def remove_parent(self, parent):
RoleParenting = rbac_utils.get_role_parenting_model()
RoleParenting.objects.soft_delete(parent, self)
def parents(self, include_self=True, annotate=False, direct=None):
@ -518,7 +509,7 @@ class Role(AbstractBase):
def has_self_administration(self, op=None):
if not op:
op = MANAGE_MEMBERS_OP
operation = rbac_utils.get_operation(op)
operation = utils.get_operation(op)
self_perm, dummy = Permission.objects.get_or_create(
operation=operation,
target_ct=ContentType.objects.get_for_model(self),
@ -531,7 +522,7 @@ class Role(AbstractBase):
'Add permission to role so that it is self-administered'
if not op:
op = MANAGE_MEMBERS_OP
operation = rbac_utils.get_operation(op)
operation = utils.get_operation(op)
self_perm, dummy = Permission.objects.get_or_create(
operation=operation, target_ct=ContentType.objects.get_for_model(self), target_id=self.pk
)
@ -553,7 +544,7 @@ class Role(AbstractBase):
if isinstance(operation_tpl, str):
operation = Operation.objects.get(slug=operation_tpl)
else:
operation = rbac_utils.get_operation(operation_tpl)
operation = utils.get_operation(operation_tpl)
permission, _ = Permission.objects.get_or_create(
operation=operation, target_ct=target_ct, target_id=target_id, ou=ou
)
@ -571,7 +562,7 @@ class Role(AbstractBase):
if isinstance(operation_tpl, str):
operation = Operation.objects.get(slug=operation_tpl)
else:
operation = rbac_utils.get_operation(operation_tpl)
operation = utils.get_operation(operation_tpl)
qs = Permission.objects.filter(target_ct=target_ct, target_id=target_id, operation=operation)
if ou:
qs = qs.filter(ou=ou)
@ -704,13 +695,13 @@ Role._meta.natural_key = [
class RoleParenting(models.Model):
parent = models.ForeignKey(
to=rbac_utils.get_role_model_name(),
to=Role,
swappable=True,
related_name='child_relation',
on_delete=models.CASCADE,
)
child = models.ForeignKey(
to=rbac_utils.get_role_model_name(),
to=Role,
swappable=True,
related_name='parent_relation',
on_delete=models.CASCADE,

View File

@ -20,11 +20,11 @@ from django.db import DEFAULT_DB_ALIAS, router, transaction
from django.utils.translation import gettext as _
from django.utils.translation import override
from authentic2.a2_rbac.models import OrganizationalUnit, Role
from authentic2.a2_rbac.models import OrganizationalUnit, Role, RoleParenting
from authentic2.utils.misc import get_fk_model
from django_rbac.utils import get_operation, get_role_parenting_model
from .managers import defer_update_transitive_closure
from .utils import get_operation
def create_default_ou(app_config, verbosity=2, interactive=True, using=DEFAULT_DB_ALIAS, **kwargs):
@ -144,6 +144,6 @@ def create_base_operations(app_config, verbosity=2, interactive=True, using=DEFA
def fix_role_parenting_closure(app_config, verbosity=2, interactive=True, using=DEFAULT_DB_ALIAS, **kwargs):
'''Close the role parenting relation after migrations'''
if not router.allow_migrate(using, get_role_parenting_model()):
if not router.allow_migrate(using, RoleParenting):
return
get_role_parenting_model().objects.update_transitive_closure()
RoleParenting.objects.update_transitive_closure()

View File

@ -14,15 +14,24 @@
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import uuid
from django.contrib.auth import get_user_model
from django.contrib.contenttypes.models import ContentType
from django.utils.text import slugify
from django_rbac import utils as rbac_utils
from . import models
def get_hex_uuid():
return uuid.uuid4().hex
def get_operation(operation_tpl):
operation, dummy = models.Operation.objects.get_or_create(slug=operation_tpl.slug)
return operation
def get_default_ou():
try:
return models.OrganizationalUnit.objects.get(default=True)
@ -37,7 +46,7 @@ def get_default_ou_pk():
def get_view_user_perm(ou=None):
User = get_user_model()
view_user_perm, dummy = models.Permission.objects.get_or_create(
operation=rbac_utils.get_operation(models.VIEW_OP),
operation=get_operation(models.VIEW_OP),
target_ct=ContentType.objects.get_for_model(ContentType),
target_id=ContentType.objects.get_for_model(User).pk,
ou__isnull=ou is None,
@ -49,14 +58,14 @@ def get_view_user_perm(ou=None):
def get_search_ou_perm(ou=None):
if ou:
view_ou_perm, dummy = models.Permission.objects.get_or_create(
operation=rbac_utils.get_operation(models.SEARCH_OP),
operation=get_operation(models.SEARCH_OP),
target_ct=ContentType.objects.get_for_model(ou),
target_id=ou.pk,
ou__isnull=True,
)
else:
view_ou_perm, dummy = models.Permission.objects.get_or_create(
operation=rbac_utils.get_operation(models.SEARCH_OP),
operation=get_operation(models.SEARCH_OP),
target_ct=ContentType.objects.get_for_model(ContentType),
target_id=ContentType.objects.get_for_model(models.OrganizationalUnit).pk,
ou__isnull=True,
@ -67,7 +76,7 @@ def get_search_ou_perm(ou=None):
def get_manage_authorizations_user_perm(ou=None):
User = get_user_model()
manage_authorizations_user_perm, dummy = models.Permission.objects.get_or_create(
operation=rbac_utils.get_operation(models.MANAGE_AUTHORIZATIONS_OP),
operation=get_operation(models.MANAGE_AUTHORIZATIONS_OP),
target_ct=ContentType.objects.get_for_model(ContentType),
target_id=ContentType.objects.get_for_model(User).pk,
ou__isnull=ou is None,

View File

@ -8,7 +8,8 @@ from django.core.exceptions import FieldDoesNotExist
from django.db import models
from django.db.models.query import Q
from django_rbac import utils
from authentic2.a2_rbac.models import OrganizationalUnit as OU
from authentic2.a2_rbac.models import Permission
def get_fk_model(model, fieldname):
@ -83,7 +84,6 @@ class DjangoRBACBackend:
"""
if not hasattr(user_obj, '_rbac_perms_cache'):
perms_cache = {}
Permission = utils.get_permission_model()
qs = Permission.objects.for_user(user_obj)
ct_ct = ContentType.objects.get_for_model(ContentType)
qs = qs.select_related('operation')
@ -236,7 +236,6 @@ class DjangoRBACBackend:
perm_or_perms = set(perm_or_perms)
cache = self.get_permission_cache(user_obj)
model = qs.model
OU = utils.get_ou_model()
has_ou_field = get_fk_model(model, 'ou') == OU
if perm_or_perms & cache.get('__all__', set()):
return True
@ -282,7 +281,6 @@ class DjangoRBACBackend:
return perm in self.get_permission_cache(user_obj).get('ou.%s' % ou.pk, ())
def ous_with_perm(self, user_obj, perm, queryset=None):
OU = utils.get_ou_model()
qs = queryset or OU.objects.all()
if user_obj.is_anonymous:

View File

@ -32,8 +32,8 @@ from authentic2 import app_settings
from authentic2.a2_rbac.models import ADMIN_OP
from authentic2.a2_rbac.models import OrganizationalUnit as OU
from authentic2.a2_rbac.models import Permission, Role
from authentic2.a2_rbac.utils import get_operation
from authentic2.custom_user.models import User
from django_rbac.utils import get_operation
try:
from authentic2.a2_rbac.models import MANAGE_MEMBERS_OP # pylint: disable=C0412

View File

@ -1,7 +1,7 @@
from django.conf import settings
from django.db import migrations, models
import django_rbac
import authentic2.a2_rbac
class Migration(migrations.Migration):
@ -27,7 +27,7 @@ class Migration(migrations.Migration):
(
'uuid',
models.CharField(
default=django_rbac.utils.get_hex_uuid,
default=authentic2.a2_rbac.utils.get_hex_uuid,
unique=True,
max_length=32,
verbose_name='uuid',
@ -92,7 +92,7 @@ class Migration(migrations.Migration):
(
'uuid',
models.CharField(
default=django_rbac.utils.get_hex_uuid,
default=authentic2.a2_rbac.utils.get_hex_uuid,
unique=True,
max_length=32,
verbose_name='uuid',

View File

@ -1,92 +0,0 @@
import uuid
from django.apps import apps
from django.conf import settings
from django.utils.text import slugify
from . import constants
DEFAULT_MODELS = {
constants.RBAC_OU_MODEL_SETTING: 'django_rbac.OrganizationalUnit',
constants.RBAC_ROLE_PARENTING_MODEL_SETTING: 'django_rbac.RoleParenting',
constants.RBAC_ROLE_MODEL_SETTING: 'django_rbac.Role',
constants.RBAC_PERMISSION_MODEL_SETTING: 'django_rbac.Permission',
}
def get_hex_uuid():
return uuid.uuid4().hex
def get_swapped_model_name(setting):
"""Return a model qualified name given a setting name containing the
qualified name of the model, useful to retrieve swappable models
name.
"""
if not hasattr(settings, setting):
setattr(settings, setting, DEFAULT_MODELS[setting])
return getattr(settings, setting)
def get_swapped_model(setting):
"""Return a model given a setting name containing the qualified name
of the model, useful to retrieve swappable models.
"""
app, model_name = get_swapped_model_name(setting).rsplit('.', 1)
return apps.get_model(app, model_name)
def get_role_model_name():
'''Returns the currently configured role model'''
return get_swapped_model_name(constants.RBAC_ROLE_MODEL_SETTING)
def get_ou_model_name():
'''Returns the currently configured organizational unit model'''
return get_swapped_model_name(constants.RBAC_OU_MODEL_SETTING)
def get_role_parenting_model_name():
'''Returns the currently configured role parenting model'''
return get_swapped_model_name(constants.RBAC_ROLE_PARENTING_MODEL_SETTING)
def get_permission_model_name():
'''Returns the currently configured permission model'''
return get_swapped_model_name(constants.RBAC_PERMISSION_MODEL_SETTING)
def get_role_model():
'''Returns the currently configured role model'''
return get_swapped_model(constants.RBAC_ROLE_MODEL_SETTING)
def get_ou_model():
'''Returns the currently configured organizational unit model'''
return get_swapped_model(constants.RBAC_OU_MODEL_SETTING)
def get_role_parenting_model():
'''Returns the currently configured role parenting model'''
return get_swapped_model(constants.RBAC_ROLE_PARENTING_MODEL_SETTING)
def get_permission_model():
'''Returns the currently configured permission model'''
return get_swapped_model(constants.RBAC_PERMISSION_MODEL_SETTING)
def get_operation(operation_tpl):
from authentic2.a2_rbac import models
operation, dummy = models.Operation.objects.get_or_create(slug=operation_tpl.slug)
return operation
def generate_slug(name, seen_slugs=None):
slug = base_slug = slugify(name).lstrip('_')
if seen_slugs:
i = 1
while slug in seen_slugs:
slug = '%s-%s' % (base_slug, i)
return slug

View File

@ -36,12 +36,11 @@ from authentic2.a2_rbac.models import (
Permission,
Role,
)
from authentic2.a2_rbac.utils import get_default_ou
from authentic2.a2_rbac.utils import get_default_ou, get_operation
from authentic2.apps.journal.models import Event
from authentic2.custom_user.models import DeletedUser
from authentic2.models import UserExternalId
from authentic2_auth_oidc.models import OIDCAccount, OIDCProvider
from django_rbac.utils import get_operation
from .utils import call_command, login

View File

@ -31,11 +31,10 @@ from webtest import Upload
from authentic2.a2_rbac.models import MANAGE_MEMBERS_OP, VIEW_OP
from authentic2.a2_rbac.models import OrganizationalUnit as OU
from authentic2.a2_rbac.models import Permission, Role
from authentic2.a2_rbac.utils import get_default_ou
from authentic2.a2_rbac.utils import get_default_ou, get_operation
from authentic2.apps.journal.models import Event
from authentic2.models import Service
from authentic2.validators import EmailValidator
from django_rbac.utils import get_operation
from .utils import assert_event, get_link_from_mail, login, request_select2, text_content

View File

@ -21,14 +21,9 @@ from django.db import connection
from django.db.models import Q
from django.test.utils import CaptureQueriesContext
from authentic2.a2_rbac import models
from authentic2.a2_rbac.models import Operation, OrganizationalUnit, Permission, Role, RoleParenting
from authentic2.custom_user import backends
from django_rbac import utils
OU = OrganizationalUnit = utils.get_ou_model()
Permission = utils.get_permission_model()
RoleParenting = utils.get_role_parenting_model()
Role = utils.get_role_model()
User = get_user_model()
@ -95,10 +90,6 @@ def test_role_parenting(db):
def test_role_parenting_soft_delete_children(db):
OrganizationalUnit = utils.get_ou_model()
Role = utils.get_role_model()
RoleParenting = utils.get_role_parenting_model()
ou = OrganizationalUnit.objects.create(name='ou')
roles = []
for i in range(10):
@ -121,10 +112,6 @@ def test_role_parenting_soft_delete_children(db):
def test_role_parenting_soft_delete_parents(db):
OrganizationalUnit = utils.get_ou_model()
Role = utils.get_role_model()
RoleParenting = utils.get_role_parenting_model()
ou = OrganizationalUnit.objects.create(name='ou')
roles = []
for i in range(10):
@ -167,7 +154,7 @@ def test_massive_role_parenting(db):
relations.append(RoleParenting(parent=roles[i], child=roles[(i - 1) // SPAN]))
RoleParenting.objects.bulk_create(relations)
RoleParenting.objects.update_transitive_closure()
operation, _ = models.Operation.objects.get_or_create(slug='admin')
operation, _ = Operation.objects.get_or_create(slug='admin')
perm, _ = Permission.objects.get_or_create(
operation=operation,
target_ct=ContentType.objects.get_for_model(ContentType),
@ -176,22 +163,22 @@ def test_massive_role_parenting(db):
roles[0].members.add(user)
Role.objects.get(pk=roles[-1].pk).permissions.add(perm)
for i in range(SIZE):
assert models.Operation.objects.has_perm(user, 'admin', User)
assert Operation.objects.has_perm(user, 'admin', User)
for i in range(SIZE):
assert list(Role.objects.for_user(user).order_by('pk')) == list(Role.objects.order_by('pk'))
def test_rbac_backend(db):
ou1 = OU.objects.create(name='ou1', slug='ou1')
ou2 = OU.objects.create(name='ou2', slug='ou2')
ou1 = OrganizationalUnit.objects.create(name='ou1', slug='ou1')
ou2 = OrganizationalUnit.objects.create(name='ou2', slug='ou2')
user1 = User.objects.create(username='john.doe')
ct_ct = ContentType.objects.get_for_model(ContentType)
role_ct = ContentType.objects.get_for_model(Role)
change_op = models.Operation.objects.get(slug='change')
view_op = models.Operation.objects.get(slug='view')
delete_op = models.Operation.objects.get(slug='delete')
add_op = models.Operation.objects.get(slug='add')
admin_op = models.Operation.objects.get(slug='admin')
change_op = Operation.objects.get(slug='change')
view_op = Operation.objects.get(slug='view')
delete_op = Operation.objects.get(slug='delete')
add_op = Operation.objects.get(slug='add')
admin_op = Operation.objects.get(slug='admin')
perm1 = Permission.objects.create(operation=change_op, target_ct=ct_ct, target_id=role_ct.pk)
perm2 = Permission.objects.create(operation=view_op, target_ct=ct_ct, target_id=role_ct.pk)
Role.objects.all().delete()

View File

@ -30,13 +30,12 @@ from webtest import Upload
from authentic2.a2_rbac.models import VIEW_OP
from authentic2.a2_rbac.models import OrganizationalUnit as OU
from authentic2.a2_rbac.models import Permission, Role
from authentic2.a2_rbac.utils import get_default_ou, get_view_user_perm
from authentic2.a2_rbac.utils import get_default_ou, get_operation, get_view_user_perm
from authentic2.apps.journal.models import Event
from authentic2.custom_user.models import User
from authentic2.manager import user_import
from authentic2.models import Attribute, AttributeValue
from authentic2_idp_oidc.models import OIDCAuthorization, OIDCClient
from django_rbac.utils import get_operation
from .utils import get_link_from_mail, login, logout