core: make is_superuser a group property, remove from user

This commit is contained in:
Jens Langhammer 2020-09-15 22:37:31 +02:00
parent 0325847c22
commit 0a5e14a352
15 changed files with 112 additions and 24 deletions

View File

@ -12,7 +12,7 @@ class UserForm(forms.ModelForm):
class Meta: class Meta:
model = User model = User
fields = ["username", "name", "email", "is_staff", "is_active", "attributes"] fields = ["username", "name", "email", "is_active", "attributes"]
widgets = { widgets = {
"name": forms.TextInput, "name": forms.TextInput,
"attributes": CodeMirrorWidget, "attributes": CodeMirrorWidget,

View File

@ -50,7 +50,7 @@
</td> </td>
<td role="cell"> <td role="cell">
<span> <span>
{{ group.user_set.all|length }} {{ group.users.all|length }}
</span> </span>
</td> </td>
<td> <td>

View File

@ -8,7 +8,7 @@ from django.test import Client, TestCase
from django.urls.exceptions import NoReverseMatch from django.urls.exceptions import NoReverseMatch
from passbook.admin.urls import urlpatterns from passbook.admin.urls import urlpatterns
from passbook.core.models import User from passbook.core.models import Group, User
from passbook.lib.utils.reflection import get_apps from passbook.lib.utils.reflection import get_apps
@ -16,7 +16,9 @@ class TestAdmin(TestCase):
"""Generic admin tests""" """Generic admin tests"""
def setUp(self): def setUp(self):
self.user = User.objects.create_superuser(username="test") self.user = User.objects.create_user(username="test")
self.user.pb_groups.add(Group.objects.filter(is_superuser=True).first())
self.user.save()
self.client = Client() self.client = Client()
self.client.force_login(self.user) self.client.force_login(self.user)

View File

@ -11,7 +11,7 @@ class GroupSerializer(ModelSerializer):
class Meta: class Meta:
model = Group model = Group
fields = ["pk", "name", "parent", "user_set", "attributes"] fields = ["pk", "name", "is_superuser", "parent", "users", "attributes"]
class GroupViewSet(ModelViewSet): class GroupViewSet(ModelViewSet):

View File

@ -1,5 +1,5 @@
"""User API Views""" """User API Views"""
from rest_framework.serializers import ModelSerializer from rest_framework.serializers import BooleanField, ModelSerializer
from rest_framework.viewsets import ModelViewSet from rest_framework.viewsets import ModelViewSet
from passbook.core.models import User from passbook.core.models import User
@ -8,10 +8,12 @@ from passbook.core.models import User
class UserSerializer(ModelSerializer): class UserSerializer(ModelSerializer):
"""User Serializer""" """User Serializer"""
is_superuser = BooleanField(read_only=True)
class Meta: class Meta:
model = User model = User
fields = ["pk", "username", "name", "email"] fields = ["pk", "username", "name", "is_superuser", "email"]
class UserViewSet(ModelViewSet): class UserViewSet(ModelViewSet):

View File

@ -18,21 +18,19 @@ class GroupForm(forms.ModelForm):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
if self.instance.pk: if self.instance.pk:
self.initial["members"] = self.instance.user_set.values_list( self.initial["members"] = self.instance.users.values_list("pk", flat=True)
"pk", flat=True
)
def save(self, *args, **kwargs): def save(self, *args, **kwargs):
instance = super().save(*args, **kwargs) instance = super().save(*args, **kwargs)
if instance.pk: if instance.pk:
instance.user_set.clear() instance.users.clear()
instance.user_set.add(*self.cleaned_data["members"]) instance.users.add(*self.cleaned_data["members"])
return instance return instance
class Meta: class Meta:
model = Group model = Group
fields = ["name", "parent", "members", "attributes"] fields = ["name", "is_superuser", "parent", "members", "attributes"]
widgets = { widgets = {
"name": forms.TextInput(), "name": forms.TextInput(),
"attributes": CodeMirrorWidget, "attributes": CodeMirrorWidget,

View File

@ -1,7 +1,7 @@
# Generated by Django 3.0.6 on 2020-05-23 16:40 # Generated by Django 3.0.6 on 2020-05-23 16:40
from django.apps.registry import Apps from django.apps.registry import Apps
from django.db import migrations from django.db import migrations, models
from django.db.backends.base.schema import BaseDatabaseSchemaEditor from django.db.backends.base.schema import BaseDatabaseSchemaEditor
@ -15,8 +15,6 @@ def create_default_user(apps: Apps, schema_editor: BaseDatabaseSchemaEditor):
username="pbadmin", email="root@localhost", name="passbook Default Admin" username="pbadmin", email="root@localhost", name="passbook Default Admin"
) )
pbadmin.set_password("pbadmin") # noqa # nosec pbadmin.set_password("pbadmin") # noqa # nosec
pbadmin.is_superuser = True
pbadmin.is_staff = True
pbadmin.save() pbadmin.save()
@ -27,5 +25,15 @@ class Migration(migrations.Migration):
] ]
operations = [ operations = [
migrations.RemoveField(model_name="user", name="is_superuser",),
migrations.RemoveField(model_name="user", name="is_staff",),
migrations.RunPython(create_default_user), migrations.RunPython(create_default_user),
migrations.AddField(
model_name="user",
name="is_superuser",
field=models.BooleanField(default=False),
),
migrations.AddField(
model_name="user", name="is_staff", field=models.BooleanField(default=False)
),
] ]

View File

@ -0,0 +1,44 @@
# Generated by Django 3.1.1 on 2020-09-15 19:53
from django.apps.registry import Apps
from django.db import migrations, models
from django.db.backends.base.schema import BaseDatabaseSchemaEditor
def create_default_admin_group(apps: Apps, schema_editor: BaseDatabaseSchemaEditor):
db_alias = schema_editor.connection.alias
Group = apps.get_model("passbook_core", "Group")
User = apps.get_model("passbook_core", "User")
# Creates a default admin group
group, _ = Group.objects.using(db_alias).get_or_create(
is_superuser=True, defaults={"name": "passbook Admins",}
)
group.users.add(User.objects.get(username="pbadmin"))
group.save()
class Migration(migrations.Migration):
dependencies = [
("passbook_core", "0008_auto_20200824_1532"),
]
operations = [
migrations.RemoveField(model_name="user", name="is_superuser",),
migrations.RemoveField(model_name="user", name="is_staff",),
migrations.AlterField(
model_name="user",
name="pb_groups",
field=models.ManyToManyField(
related_name="users", to="passbook_core.Group"
),
),
migrations.AddField(
model_name="group",
name="is_superuser",
field=models.BooleanField(
default=False, help_text="Users added to this group will be superusers."
),
),
migrations.RunPython(create_default_admin_group),
]

View File

@ -4,6 +4,7 @@ from typing import Any, Optional, Type
from uuid import uuid4 from uuid import uuid4
from django.contrib.auth.models import AbstractUser from django.contrib.auth.models import AbstractUser
from django.contrib.auth.models import UserManager as DjangoUserManager
from django.db import models from django.db import models
from django.db.models import Q, QuerySet from django.db.models import Q, QuerySet
from django.forms import ModelForm from django.forms import ModelForm
@ -34,7 +35,12 @@ class Group(models.Model):
"""Custom Group model which supports a basic hierarchy""" """Custom Group model which supports a basic hierarchy"""
group_uuid = models.UUIDField(primary_key=True, editable=False, default=uuid4) group_uuid = models.UUIDField(primary_key=True, editable=False, default=uuid4)
name = models.CharField(_("name"), max_length=80) name = models.CharField(_("name"), max_length=80)
is_superuser = models.BooleanField(
default=False, help_text=_("Users added to this group will be superusers.")
)
parent = models.ForeignKey( parent = models.ForeignKey(
"Group", "Group",
blank=True, blank=True,
@ -52,6 +58,14 @@ class Group(models.Model):
unique_together = (("name", "parent",),) unique_together = (("name", "parent",),)
class UserManager(DjangoUserManager):
"""Custom user manager that doesn't assign is_superuser and is_staff"""
def create_user(self, username, email=None, password=None, **extra_fields):
"""Custom user manager that doesn't assign is_superuser and is_staff"""
return self._create_user(username, email, password, **extra_fields)
class User(GuardianUserMixin, AbstractUser): class User(GuardianUserMixin, AbstractUser):
"""Custom User model to allow easier adding o f user-based settings""" """Custom User model to allow easier adding o f user-based settings"""
@ -59,11 +73,23 @@ class User(GuardianUserMixin, AbstractUser):
name = models.TextField(help_text=_("User's display name.")) name = models.TextField(help_text=_("User's display name."))
sources = models.ManyToManyField("Source", through="UserSourceConnection") sources = models.ManyToManyField("Source", through="UserSourceConnection")
pb_groups = models.ManyToManyField("Group") pb_groups = models.ManyToManyField("Group", related_name="users")
password_change_date = models.DateTimeField(auto_now_add=True) password_change_date = models.DateTimeField(auto_now_add=True)
attributes = models.JSONField(default=dict, blank=True) attributes = models.JSONField(default=dict, blank=True)
objects = UserManager()
@property
def is_superuser(self) -> bool:
"""Get supseruser status based on membership in a group with superuser status"""
return self.pb_groups.filter(is_superuser=True).exists()
@property
def is_staff(self) -> bool:
"""superuser == staff user"""
return self.is_superuser
def set_password(self, password): def set_password(self, password):
if self.pk: if self.pk:
password_changed.send(sender=self, user=self, password=password) password_changed.send(sender=self, user=self, password=password)

View File

@ -13,7 +13,7 @@ class TestOverviewViews(TestCase):
def setUp(self): def setUp(self):
super().setUp() super().setUp()
self.user = User.objects.create_superuser( self.user = User.objects.create_user(
username="unittest user", username="unittest user",
email="unittest@example.com", email="unittest@example.com",
password="".join( password="".join(

View File

@ -13,7 +13,7 @@ class TestUserViews(TestCase):
def setUp(self): def setUp(self):
super().setUp() super().setUp()
self.user = User.objects.create_superuser( self.user = User.objects.create_user(
username="unittest user", username="unittest user",
email="unittest@example.com", email="unittest@example.com",
password="".join( password="".join(

View File

@ -30,7 +30,7 @@ class GroupMembershipPolicy(Policy):
return GroupMembershipPolicyForm return GroupMembershipPolicyForm
def passes(self, request: PolicyRequest) -> PolicyResult: def passes(self, request: PolicyRequest) -> PolicyResult:
return PolicyResult(self.group.user_set.filter(pk=request.user.pk).exists()) return PolicyResult(self.group.users.filter(pk=request.user.pk).exists())
class Meta: class Meta:

View File

@ -24,7 +24,7 @@ class TestGroupMembershipPolicy(TestCase):
def test_valid(self): def test_valid(self):
"""user in group""" """user in group"""
group = Group.objects.create(name="test") group = Group.objects.create(name="test")
group.user_set.add(get_anonymous_user()) group.users.add(get_anonymous_user())
group.save() group.save()
policy: GroupMembershipPolicy = GroupMembershipPolicy.objects.create( policy: GroupMembershipPolicy = GroupMembershipPolicy.objects.create(
group=group group=group

View File

@ -151,7 +151,7 @@ class Connector:
group_cache[group_dn] = groups.first() group_cache[group_dn] = groups.first()
group = group_cache[group_dn] group = group_cache[group_dn]
users = User.objects.filter(attributes__ldap_uniq=uniq) users = User.objects.filter(attributes__ldap_uniq=uniq)
group.user_set.add(*list(users)) group.users.add(*list(users))
# Now that all users are added, lets write everything # Now that all users are added, lets write everything
for _, group in group_cache.items(): for _, group in group_cache.items():
group.save() group.save()

View File

@ -5922,7 +5922,7 @@ definitions:
required: required:
- name - name
- parent - parent
- user_set - users
type: object type: object
properties: properties:
pk: pk:
@ -5935,11 +5935,15 @@ definitions:
type: string type: string
maxLength: 80 maxLength: 80
minLength: 1 minLength: 1
is_superuser:
title: Is superuser
description: Users added to this group will be superusers.
type: boolean
parent: parent:
title: Parent title: Parent
type: string type: string
format: uuid format: uuid
user_set: users:
type: array type: array
items: items:
type: integer type: integer
@ -5970,6 +5974,10 @@ definitions:
description: User's display name. description: User's display name.
type: string type: string
minLength: 1 minLength: 1
is_superuser:
title: Is superuser
type: boolean
readOnly: true
email: email:
title: Email address title: Email address
type: string type: string