diff --git a/passbook/admin/forms/users.py b/passbook/admin/forms/users.py
index c3a640396..2e9f51eb2 100644
--- a/passbook/admin/forms/users.py
+++ b/passbook/admin/forms/users.py
@@ -12,7 +12,7 @@ class UserForm(forms.ModelForm):
class Meta:
model = User
- fields = ["username", "name", "email", "is_staff", "is_active", "attributes"]
+ fields = ["username", "name", "email", "is_active", "attributes"]
widgets = {
"name": forms.TextInput,
"attributes": CodeMirrorWidget,
diff --git a/passbook/admin/templates/administration/group/list.html b/passbook/admin/templates/administration/group/list.html
index c9ddbc929..44a498c74 100644
--- a/passbook/admin/templates/administration/group/list.html
+++ b/passbook/admin/templates/administration/group/list.html
@@ -50,7 +50,7 @@
- {{ group.user_set.all|length }}
+ {{ group.users.all|length }}
|
diff --git a/passbook/admin/tests.py b/passbook/admin/tests.py
index a7017775a..13ced0c9c 100644
--- a/passbook/admin/tests.py
+++ b/passbook/admin/tests.py
@@ -8,7 +8,7 @@ from django.test import Client, TestCase
from django.urls.exceptions import NoReverseMatch
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
@@ -16,7 +16,9 @@ class TestAdmin(TestCase):
"""Generic admin tests"""
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.force_login(self.user)
diff --git a/passbook/core/api/groups.py b/passbook/core/api/groups.py
index fbd9fc7e0..1af8ab425 100644
--- a/passbook/core/api/groups.py
+++ b/passbook/core/api/groups.py
@@ -11,7 +11,7 @@ class GroupSerializer(ModelSerializer):
class Meta:
model = Group
- fields = ["pk", "name", "parent", "user_set", "attributes"]
+ fields = ["pk", "name", "is_superuser", "parent", "users", "attributes"]
class GroupViewSet(ModelViewSet):
diff --git a/passbook/core/api/users.py b/passbook/core/api/users.py
index d583fbf5e..dc04e6deb 100644
--- a/passbook/core/api/users.py
+++ b/passbook/core/api/users.py
@@ -1,5 +1,5 @@
"""User API Views"""
-from rest_framework.serializers import ModelSerializer
+from rest_framework.serializers import BooleanField, ModelSerializer
from rest_framework.viewsets import ModelViewSet
from passbook.core.models import User
@@ -8,10 +8,12 @@ from passbook.core.models import User
class UserSerializer(ModelSerializer):
"""User Serializer"""
+ is_superuser = BooleanField(read_only=True)
+
class Meta:
model = User
- fields = ["pk", "username", "name", "email"]
+ fields = ["pk", "username", "name", "is_superuser", "email"]
class UserViewSet(ModelViewSet):
diff --git a/passbook/core/forms/groups.py b/passbook/core/forms/groups.py
index 3c8dae507..ffb85dab2 100644
--- a/passbook/core/forms/groups.py
+++ b/passbook/core/forms/groups.py
@@ -18,21 +18,19 @@ class GroupForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
if self.instance.pk:
- self.initial["members"] = self.instance.user_set.values_list(
- "pk", flat=True
- )
+ self.initial["members"] = self.instance.users.values_list("pk", flat=True)
def save(self, *args, **kwargs):
instance = super().save(*args, **kwargs)
if instance.pk:
- instance.user_set.clear()
- instance.user_set.add(*self.cleaned_data["members"])
+ instance.users.clear()
+ instance.users.add(*self.cleaned_data["members"])
return instance
class Meta:
model = Group
- fields = ["name", "parent", "members", "attributes"]
+ fields = ["name", "is_superuser", "parent", "members", "attributes"]
widgets = {
"name": forms.TextInput(),
"attributes": CodeMirrorWidget,
diff --git a/passbook/core/migrations/0003_default_user.py b/passbook/core/migrations/0003_default_user.py
index 63af2c780..56f9edd9c 100644
--- a/passbook/core/migrations/0003_default_user.py
+++ b/passbook/core/migrations/0003_default_user.py
@@ -1,7 +1,7 @@
# Generated by Django 3.0.6 on 2020-05-23 16:40
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
@@ -15,8 +15,6 @@ def create_default_user(apps: Apps, schema_editor: BaseDatabaseSchemaEditor):
username="pbadmin", email="root@localhost", name="passbook Default Admin"
)
pbadmin.set_password("pbadmin") # noqa # nosec
- pbadmin.is_superuser = True
- pbadmin.is_staff = True
pbadmin.save()
@@ -27,5 +25,15 @@ class Migration(migrations.Migration):
]
operations = [
+ migrations.RemoveField(model_name="user", name="is_superuser",),
+ migrations.RemoveField(model_name="user", name="is_staff",),
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)
+ ),
]
diff --git a/passbook/core/migrations/0009_group_is_superuser.py b/passbook/core/migrations/0009_group_is_superuser.py
new file mode 100644
index 000000000..95fbe9f9a
--- /dev/null
+++ b/passbook/core/migrations/0009_group_is_superuser.py
@@ -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),
+ ]
diff --git a/passbook/core/models.py b/passbook/core/models.py
index 43cdbe0ce..d58a53856 100644
--- a/passbook/core/models.py
+++ b/passbook/core/models.py
@@ -4,6 +4,7 @@ from typing import Any, Optional, Type
from uuid import uuid4
from django.contrib.auth.models import AbstractUser
+from django.contrib.auth.models import UserManager as DjangoUserManager
from django.db import models
from django.db.models import Q, QuerySet
from django.forms import ModelForm
@@ -34,7 +35,12 @@ class Group(models.Model):
"""Custom Group model which supports a basic hierarchy"""
group_uuid = models.UUIDField(primary_key=True, editable=False, default=uuid4)
+
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(
"Group",
blank=True,
@@ -52,6 +58,14 @@ class Group(models.Model):
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):
"""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."))
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)
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):
if self.pk:
password_changed.send(sender=self, user=self, password=password)
diff --git a/passbook/core/tests/test_views_overview.py b/passbook/core/tests/test_views_overview.py
index 4a857e2d4..0edae05db 100644
--- a/passbook/core/tests/test_views_overview.py
+++ b/passbook/core/tests/test_views_overview.py
@@ -13,7 +13,7 @@ class TestOverviewViews(TestCase):
def setUp(self):
super().setUp()
- self.user = User.objects.create_superuser(
+ self.user = User.objects.create_user(
username="unittest user",
email="unittest@example.com",
password="".join(
diff --git a/passbook/core/tests/test_views_user.py b/passbook/core/tests/test_views_user.py
index 88e984f85..c38a601f3 100644
--- a/passbook/core/tests/test_views_user.py
+++ b/passbook/core/tests/test_views_user.py
@@ -13,7 +13,7 @@ class TestUserViews(TestCase):
def setUp(self):
super().setUp()
- self.user = User.objects.create_superuser(
+ self.user = User.objects.create_user(
username="unittest user",
email="unittest@example.com",
password="".join(
diff --git a/passbook/policies/group_membership/models.py b/passbook/policies/group_membership/models.py
index 3c8dbdafd..55726be1a 100644
--- a/passbook/policies/group_membership/models.py
+++ b/passbook/policies/group_membership/models.py
@@ -30,7 +30,7 @@ class GroupMembershipPolicy(Policy):
return GroupMembershipPolicyForm
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:
diff --git a/passbook/policies/group_membership/tests.py b/passbook/policies/group_membership/tests.py
index d0e7c43ea..1948942bc 100644
--- a/passbook/policies/group_membership/tests.py
+++ b/passbook/policies/group_membership/tests.py
@@ -24,7 +24,7 @@ class TestGroupMembershipPolicy(TestCase):
def test_valid(self):
"""user in group"""
group = Group.objects.create(name="test")
- group.user_set.add(get_anonymous_user())
+ group.users.add(get_anonymous_user())
group.save()
policy: GroupMembershipPolicy = GroupMembershipPolicy.objects.create(
group=group
diff --git a/passbook/sources/ldap/connector.py b/passbook/sources/ldap/connector.py
index 646ba8e49..54c6f14e6 100644
--- a/passbook/sources/ldap/connector.py
+++ b/passbook/sources/ldap/connector.py
@@ -151,7 +151,7 @@ class Connector:
group_cache[group_dn] = groups.first()
group = group_cache[group_dn]
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
for _, group in group_cache.items():
group.save()
diff --git a/swagger.yaml b/swagger.yaml
index 241018062..ac57ced14 100755
--- a/swagger.yaml
+++ b/swagger.yaml
@@ -5922,7 +5922,7 @@ definitions:
required:
- name
- parent
- - user_set
+ - users
type: object
properties:
pk:
@@ -5935,11 +5935,15 @@ definitions:
type: string
maxLength: 80
minLength: 1
+ is_superuser:
+ title: Is superuser
+ description: Users added to this group will be superusers.
+ type: boolean
parent:
title: Parent
type: string
format: uuid
- user_set:
+ users:
type: array
items:
type: integer
@@ -5970,6 +5974,10 @@ definitions:
description: User's display name.
type: string
minLength: 1
+ is_superuser:
+ title: Is superuser
+ type: boolean
+ readOnly: true
email:
title: Email address
type: string
|