core: make is_superuser a group property, remove from user
This commit is contained in:
parent
0325847c22
commit
0a5e14a352
|
@ -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,
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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)
|
||||||
|
|
||||||
|
|
|
@ -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):
|
||||||
|
|
|
@ -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):
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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)
|
||||||
|
),
|
||||||
]
|
]
|
||||||
|
|
|
@ -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),
|
||||||
|
]
|
|
@ -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)
|
||||||
|
|
|
@ -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(
|
||||||
|
|
|
@ -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(
|
||||||
|
|
|
@ -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:
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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()
|
||||||
|
|
12
swagger.yaml
12
swagger.yaml
|
@ -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
|
||||||
|
|
Reference in New Issue