Skip to content

Commit

Permalink
Merge pull request #667 from kobotoolbox/assign-object-level-delete-p…
Browse files Browse the repository at this point in the history
…ermissions

Assign object-level delete_data_xform permission…
  • Loading branch information
noliveleger authored Dec 9, 2020
2 parents f85b08c + d0b6fb4 commit d91834c
Showing 1 changed file with 126 additions and 26 deletions.
152 changes: 126 additions & 26 deletions onadata/apps/logger/migrations/0015_add_delete_data_permission.py
Original file line number Diff line number Diff line change
@@ -1,52 +1,140 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals

import sys

from django.db import migrations
from django.contrib.auth.management import create_permissions
from django.contrib.auth.models import (
Permission,
User,
AnonymousUser,
)
from django.contrib.auth.models import AnonymousUser


def forwards_func(apps, schema_editor):
"""
All users need to receive the new permission at the model level.
def create_new_perms(apps):
"""
# Permission does not exist when running this migration for the first time.
# Django is running migrations in a transaction and permissions are created
# after the transaction is completed.
The new `delete_data_xform` permission does not exist when running this
migration for the first time. Django runs migrations in a transaction and
new permissions are not created until after the transaction is completed.
See https://stackoverflow.com/a/40092780/1141214
"""
# ToDo update this code when upgrading to Django 2.x
# see https://stackoverflow.com/a/40092780/1141214
apps.models_module = True
create_permissions(apps, verbosity=0)
apps.models_module = None

permission = Permission.objects.get(content_type__app_label='logger',
codename='delete_data_xform')
user_ids = (
User.objects.values_list('pk', flat=True).exclude(pk=AnonymousUser().pk)
)

def grant_model_level_perms(apps):
"""
Grant `delete_submissions` permission to everyone at the model level
"""
User = apps.get_model('auth', 'User') # noqa
Permission = apps.get_model('auth', 'Permission') # noqa
ThroughModel = User.user_permissions.through # noqa

permission = Permission.objects.get(
content_type__app_label='logger', codename='delete_data_xform'
)
user_ids = User.objects.values_list('pk', flat=True).exclude(
pk=AnonymousUser().pk
)

through_models = []
for user_id in user_ids:
through_models.append(ThroughModel(user_id=user_id,
permission_id=permission.pk))
through_models.append(
ThroughModel(user_id=user_id, permission_id=permission.pk)
)

sys.stderr.write(
'Creating {} model-level permission assignments...\n'.format(
len(through_models)
)
)
sys.stderr.flush()
# Django 1.8 does not support `ignore_conflicts=True`
ThroughModel.objects.bulk_create(through_models)


def reverse_func(apps, schema_editor):
def remove_model_level_perms(apps):
"""
Revert 'delete_data_xform' permission. It can take a while on big databases
Remove all model-level 'delete_submissions' permission assignments
"""
users = User.objects.exclude(pk=AnonymousUser().pk)
permission = Permission.objects.get(content_type__app_label='logger',
codename='delete_data_xform')
for user_ in users.all():
user_.user_permissions.remove(permission)
User = apps.get_model('auth', 'User') # noqa
Permission = apps.get_model('auth', 'Permission') # noqa
ThroughModel = User.user_permissions.through # noqa

permission = Permission.objects.get(
content_type__app_label='logger', codename='delete_data_xform'
)
ThroughModel.objects.filter(permission=permission).exclude(
user_id=AnonymousUser().pk
).delete()


def grant_object_level_perms(apps):
"""
At the object level, grant `delete_submissions` to anyone who already has
`change_submissions`
"""
User = apps.get_model('auth', 'User') # noqa
Permission = apps.get_model('auth', 'Permission') # noqa
UserObjectPermission = apps.get_model(
'guardian', 'UserObjectPermission'
) # noqa

new_perm = Permission.objects.get(
content_type__app_label='logger', codename='delete_data_xform'
)
old_perm = Permission.objects.get(
content_type__app_label='logger', codename='change_xform'
)
new_perm_objects = []
for old_assign in UserObjectPermission.objects.filter(
permission=old_perm
).iterator():
old_assign.pk = None
old_assign.permission = new_perm
new_perm_objects.append(old_assign)
sys.stderr.write(
'Creating {} object-level permission assignments...\n'.format(
len(new_perm_objects)
)
)
sys.stderr.flush()
# Django 1.8 does not support `ignore_conflicts=True`
UserObjectPermission.objects.bulk_create(new_perm_objects)


def remove_object_level_perms(apps):
"""
Remove all object-level 'delete_submissions' permission assignments
"""
Permission = apps.get_model('auth', 'Permission') # noqa
UserObjectPermission = apps.get_model(
'guardian', 'UserObjectPermission'
) # noqa
perm = Permission.objects.get(
content_type__app_label='logger', codename='delete_data_xform'
)
UserObjectPermission.objects.filter(permission=perm).delete()


def forwards_func(apps, schema_editor):
sys.stderr.write(
'Expanding `change_xform` into `change_xform` and '
'`delete_data_xform`. This may take several minutes on large '
'databases...\n'
)
sys.stderr.flush()
create_new_perms(apps)
grant_model_level_perms(apps)
grant_object_level_perms(apps)


def reverse_func(apps, schema_editor):
# In testing, removal took only a small fraction of the time that it took
# to create the assignments
remove_object_level_perms(apps)
remove_model_level_perms(apps)


class Migration(migrations.Migration):
Expand All @@ -58,7 +146,19 @@ class Migration(migrations.Migration):
operations = [
migrations.AlterModelOptions(
name='xform',
options={'ordering': ('id_string',), 'verbose_name': 'XForm', 'verbose_name_plural': 'XForms', 'permissions': (('view_xform', 'Can view associated data'), ('report_xform', 'Can make submissions to the form'), ('move_xform', 'Can move form between projects'), ('transfer_xform', 'Can transfer form ownership'), ('validate_xform', 'Can validate submissions'), ('delete_data_xform', 'Can delete submissions'))},
options={
'ordering': ('id_string',),
'verbose_name': 'XForm',
'verbose_name_plural': 'XForms',
'permissions': (
('view_xform', 'Can view associated data'),
('report_xform', 'Can make submissions to the form'),
('move_xform', 'Can move form between projects'),
('transfer_xform', 'Can transfer form ownership'),
('validate_xform', 'Can validate submissions'),
('delete_data_xform', 'Can delete submissions'),
),
},
),
migrations.RunPython(forwards_func, reverse_func),
]

0 comments on commit d91834c

Please sign in to comment.