Skip to content

Commit

Permalink
add removeroles management command (wip) (#1391)
Browse files Browse the repository at this point in the history
  • Loading branch information
mikkonie committed Jan 9, 2025
1 parent 09a87f0 commit 814b000
Show file tree
Hide file tree
Showing 5 changed files with 156 additions and 24 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ Added
- Django check for unique app setting names within each plugin (#1456)
- App setting ``user_modifiable`` validation (#1536)
- ``AppSettingAPI.get_all_by_scope()`` helper (#1534)
- ``removeroles`` management command (#1391)

Changed
-------
Expand Down
117 changes: 117 additions & 0 deletions projectroles/management/commands/removeroles.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
"""
Removeroles management command for removing all roles from a user.
"""

import sys

from django.contrib.auth import get_user_model
from django.core.management.base import BaseCommand
from django.db import transaction

from projectroles.management.logging import ManagementCommandLogger
from projectroles.models import RoleAssignment, SODAR_CONSTANTS
from projectroles.views import RoleAssignmentDeleteMixin

logger = ManagementCommandLogger(__name__)
User = get_user_model()


# SODAR constants
PROJECT_ROLE_OWNER = SODAR_CONSTANTS['PROJECT_ROLE_OWNER']

# Local constants
USER_NOT_FOUND_MSG = 'User not found with username: {}'


class Command(RoleAssignmentDeleteMixin, BaseCommand):
help = (
'Remove all roles from a user. Replace owner roles with given user or '
'parent owner.'
)

def add_arguments(self, parser):
parser.add_argument(
'-u',
'--user',
dest='user',
required=True,
help='User name of user whose roles will be removed',
)
parser.add_argument(
'-o',
'--owner',
dest='owner',
required=False,
help='Set owner role for user by given user name if set, otherwise '
'set to parent owner',
)

def handle(self, *args, **options):
if options['user'] == options.get('owner'):
logger.error(
'Same username given for both user and new owner: {}'.format(
options['user']
)
)
sys.exit(1)
try:
user = User.objects.get(username=options['user'])
except User.DoesNotExist:
logger.error(USER_NOT_FOUND_MSG.format(options['user']))
sys.exit(1)
owner_name = options.get('owner')
owner = None
if owner_name:
try:
owner = User.objects.get(username=owner_name)
except User.DoesNotExist:
logger.error(USER_NOT_FOUND_MSG.format(owner_name))
sys.exit(1)

logger.info('Removing roles from user "{}"..'.format(user.username))
if owner:
logger.info(
'New owner for replacing owner roles: {}'.format(owner.username)
)
role_count = 0
fail_count = 0
roles = RoleAssignment.objects.filter(user=user).order_by(
'project__full_title'
)
if roles.count() == 0:
logger.info('No roles found')
return

for role_as in roles:
r_name = role_as.role.name
project = role_as.project
p_title = project.get_log_title()
if project.is_remote(): # Skip remote projects
logger.debug('Skipping remote project: {}'.format(p_title))
continue
# Owner role reassignment
if role_as.role.name == PROJECT_ROLE_OWNER:
# TODO: If no set owner, get parent owner
# TODO: If parent owner is not found, fail and continue
# TODO: If parent owner has existing local role, promote that
# TODO: Else add owner role
pass
# Non-owner role removal
else:
try:
with transaction.atomic():
self.delete_assignment(role_as, None, False)
except Exception as ex:
logger.error(
'Failed to delete assignment "{}" from {}: '
'{}'.format(r_name, p_title, ex)
)
fail_count += 1
continue
logger.info('Deleted role "{}" from {}'.format(r_name, p_title))
role_count += 1
logger.info(
'Removed roles from user "{}" ({} OK, {} failed)'.format(
user.username, role_count, fail_count
)
)
3 changes: 3 additions & 0 deletions projectroles/tests/test_commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -962,6 +962,9 @@ def test_command_check(self):
self.assertEqual(AppSetting.objects.count(), 8)


# TODO: Add removeroles tests


class TestSyncGroups(TestCase):
"""Tests for syncgroups command"""

Expand Down
57 changes: 34 additions & 23 deletions projectroles/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -1894,20 +1894,28 @@ def _update_app_alerts(self, app_alerts, project, user, inh_as):
project=project,
)

def delete_assignment(self, request, instance):
def delete_assignment(self, role_as, request=None, notify=True):
"""
Delete RoleAssignment. Calls the modify API for additional actions,
raises app alerts and sends email notifications about the deletion.
:param role_as: RoleAssingment object
:param request: HttpRequest object or None
:param notify: Add app alerts and send email if True
"""
app_alerts = get_backend_api('appalerts_backend')
timeline = get_backend_api('timeline_backend')
tl_event = None
project = instance.project
user = instance.user
role = instance.role
project = role_as.project
user = role_as.user
role = role_as.role

# Init Timeline event
if timeline:
tl_event = timeline.add_event(
project=project,
app_name=APP_NAME,
user=request.user,
user=request.user if request else None,
event_name='role_delete',
description='delete role "{}" from {{{}}}'.format(
role.name, 'user'
Expand All @@ -1918,10 +1926,10 @@ def delete_assignment(self, request, instance):
# Call the project plugin modify API for additional actions
if getattr(settings, 'PROJECTROLES_ENABLE_MODIFY_API', False):
self.call_project_modify_api(
'perform_role_delete', 'revert_role_delete', [instance, request]
'perform_role_delete', 'revert_role_delete', [role_as, request]
)
# Delete object itself
instance.delete()
role_as.delete()

# Delete corresponding PROJECT_USER settings
if (
Expand All @@ -1935,23 +1943,26 @@ def delete_assignment(self, request, instance):
APP_SETTING_SCOPE_PROJECT_USER, project, user
)

inh_as = project.get_role(user, inherited_only=True)
if tl_event:
tl_event.set_status(timeline.TL_STATUS_OK)
if app_alerts:
self._update_app_alerts(app_alerts, project, user, inh_as)
if SEND_EMAIL and app_settings.get(
APP_NAME, 'notify_email_role', user=user
):
if inh_as:
email.send_role_change_mail(
'update', project, user, inh_as.role, request
)
else:
email.send_role_change_mail(
'delete', project, user, None, request
)
return instance
if notify:
inh_as = project.get_role(user, inherited_only=True)
if app_alerts:
self._update_app_alerts(app_alerts, project, user, inh_as)
if (
SEND_EMAIL
and request
and app_settings.get(APP_NAME, 'notify_email_role', user=user)
):
if inh_as:
email.send_role_change_mail(
'update', project, user, inh_as.role, request
)
else:
email.send_role_change_mail(
'delete', project, user, None, request
)
return role_as


class RoleAssignmentCreateView(
Expand Down Expand Up @@ -2104,7 +2115,7 @@ def post(self, *args, **kwargs):
else:
try:
self.object = self.delete_assignment(
request=self.request, instance=self.object
role_as=self.object, request=self.request
)
messages.success(
self.request,
Expand Down
2 changes: 1 addition & 1 deletion projectroles/views_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -597,7 +597,7 @@ def perform_destroy(self, instance):
)
):
raise PermissionDenied('User lacks permission to assign delegates')
self.delete_assignment(request=self.request, instance=instance)
self.delete_assignment(role_as=instance, request=self.request)


class RoleAssignmentOwnerTransferAPIView(
Expand Down

0 comments on commit 814b000

Please sign in to comment.