From 4ae450f9632de608abd66144c9791d6987c4c35e Mon Sep 17 00:00:00 2001 From: Basraah Date: Tue, 3 Jan 2017 09:52:20 +1000 Subject: [PATCH] Group system overhaul (#588) * Add open/hidden group membership display and remove * Include requestable groups other than open * Prevent users requesting or leaving non-joinable groups I have not prevented users joining hidden groups however, as there may be some use cases where the direct link is provided for users to request access to the group. Also prevent users generating leave requests for groups they are not a member of. * Refactor Group extension models into a single OneToOne model Added group leader field * Add blankable fields * Switched to use navactive for menu highlighting * Consolidate member state checking for easier code reuse * Added support for group leaders to manage groups * Added info log when a user removes someone from a group * Add ordering to group member list --- alliance_auth/settings.py.example | 1 + alliance_auth/urls.py | 6 + authentication/context_processors.py | 8 +- authentication/decorators.py | 24 +-- authentication/managers.py | 44 +++- groupmanagement/admin.py | 14 +- groupmanagement/context_processors.py | 5 + groupmanagement/managers.py | 56 +++++ groupmanagement/migrations/0004_authgroup.py | 112 ++++++++++ groupmanagement/models.py | 68 ++++-- groupmanagement/views.py | 198 ++++++++++++++---- stock/templates/public/base.html | 2 +- .../templates/registered/groupmanagement.html | 2 +- .../registered/groupmanagementmenu.html | 27 +++ stock/templates/registered/groupmembers.html | 44 ++++ .../templates/registered/groupmembership.html | 54 +++++ stock/templates/registered/groups.html | 26 +-- 17 files changed, 589 insertions(+), 102 deletions(-) create mode 100644 groupmanagement/context_processors.py create mode 100644 groupmanagement/managers.py create mode 100644 groupmanagement/migrations/0004_authgroup.py create mode 100644 stock/templates/registered/groupmanagementmenu.html create mode 100644 stock/templates/registered/groupmembers.html create mode 100644 stock/templates/registered/groupmembership.html diff --git a/alliance_auth/settings.py.example b/alliance_auth/settings.py.example index fa6a5aa3..b9dc87fd 100644 --- a/alliance_auth/settings.py.example +++ b/alliance_auth/settings.py.example @@ -111,6 +111,7 @@ TEMPLATES = [ 'authentication.context_processors.states', 'authentication.context_processors.membership_state', 'authentication.context_processors.sso', + 'groupmanagement.context_processors.can_manage_groups', ], }, }, diff --git a/alliance_auth/urls.py b/alliance_auth/urls.py index bbea8f80..056d4579 100755 --- a/alliance_auth/urls.py +++ b/alliance_auth/urls.py @@ -190,6 +190,12 @@ urlpatterns += i18n_patterns( url(_(r'^groups/'), groupmanagement.views.groups_view, name='auth_groups'), url(_(r'^group/management/'), groupmanagement.views.group_management, name='auth_group_management'), + url(_(r'^group/membership/$'), groupmanagement.views.group_membership, + name='auth_group_membership'), + url(_(r'^group/membership/(\w+)/$'), groupmanagement.views.group_membership_list, + name='auth_group_membership_list'), + url(_(r'^group/membership/(\w+)/remove/(\w+)/$'), groupmanagement.views.group_membership_remove, + name='auth_group_membership_remove'), url(_(r'^group/request_add/(\w+)'), groupmanagement.views.group_request_add, name='auth_group_request_add'), url(_(r'^group/request/accept/(\w+)'), groupmanagement.views.group_accept_request, diff --git a/authentication/context_processors.py b/authentication/context_processors.py index d403df83..7f1c39f1 100644 --- a/authentication/context_processors.py +++ b/authentication/context_processors.py @@ -1,14 +1,11 @@ from __future__ import unicode_literals -from authentication.models import AuthServicesInfo from authentication.states import NONE_STATE, BLUE_STATE, MEMBER_STATE +from authentication.managers import UserState from django.conf import settings def membership_state(request): - if request.user.is_authenticated: - auth = AuthServicesInfo.objects.get_or_create(user=request.user)[0] - return {'STATE': auth.state} - return {'STATE': NONE_STATE} + return UserState.get_membership_state(request) def states(request): @@ -19,6 +16,7 @@ def states(request): 'MEMBER_BLUE_STATE': [MEMBER_STATE, BLUE_STATE], } + def sso(request): return { 'EVE_SSO_CALLBACK_URL': settings.EVE_SSO_CALLBACK_URL, diff --git a/authentication/decorators.py b/authentication/decorators.py index 1d3167cb..c08fc8cb 100644 --- a/authentication/decorators.py +++ b/authentication/decorators.py @@ -1,33 +1,23 @@ from __future__ import unicode_literals from django.contrib.auth.decorators import user_passes_test -from authentication.models import AuthServicesInfo -from authentication.states import MEMBER_STATE, BLUE_STATE, NONE_STATE -from django.conf import settings +from authentication.managers import UserState -def _state_required(states, *args, **kwargs): - def test_func(user): - if user.is_superuser and settings.SUPERUSER_STATE_BYPASS: - return True - if user.is_authenticated: - auth = AuthServicesInfo.objects.get_or_create(user=user)[0] - return auth.state in states - return False - - return user_passes_test(test_func, *args, **kwargs) +def _state_required(state_test, *args, **kwargs): + return user_passes_test(state_test, *args, **kwargs) def members(*args, **kwargs): - return _state_required([MEMBER_STATE], *args, **kwargs) + return _state_required(UserState.member_state, *args, **kwargs) def blues(*args, **kwargs): - return _state_required([BLUE_STATE], *args, **kwargs) + return _state_required(UserState.blue_state, *args, **kwargs) def members_and_blues(*args, **kwargs): - return _state_required([MEMBER_STATE, BLUE_STATE], *args, **kwargs) + return _state_required(UserState.member_or_blue_state, *args, **kwargs) def none_state(*args, **kwargs): - return _state_required([NONE_STATE], *args, **kwargs) + return _state_required(UserState.none_state, *args, **kwargs) diff --git a/authentication/managers.py b/authentication/managers.py index 6471880b..a577a05a 100755 --- a/authentication/managers.py +++ b/authentication/managers.py @@ -1,6 +1,7 @@ from __future__ import unicode_literals from django.contrib.auth.models import User - +from django.conf import settings +from authentication.states import NONE_STATE, BLUE_STATE, MEMBER_STATE from authentication.models import AuthServicesInfo import logging @@ -143,3 +144,44 @@ class AuthServicesInfoManager: logger.info("Updated user %s market info in authservicesinfo model." % user) else: logger.error("Failed to update user %s market info: user does not exist." % user) + + +class UserState: + def __init__(self): + pass + + MEMBER_STATE = MEMBER_STATE + BLUE_STATE = BLUE_STATE + NONE_STATE = NONE_STATE + + @classmethod + def member_state(cls, user): + return cls.state_required(user, [cls.MEMBER_STATE]) + + @classmethod + def member_or_blue_state(cls, user): + return cls.state_required(user, [cls.MEMBER_STATE, cls.BLUE_STATE]) + + @classmethod + def blue_state(cls, user): + return cls.state_required(user, [cls.BLUE_STATE]) + + @classmethod + def none_state(cls, user): + return cls.state_required(user, [cls.NONE_STATE]) + + @classmethod + def get_membership_state(cls, request): + if request.user.is_authenticated: + auth = AuthServicesInfo.objects.get_or_create(user=request.user)[0] + return {'STATE': auth.state} + return {'STATE': cls.NONE_STATE} + + @staticmethod + def state_required(user, states): + if user.is_superuser and settings.SUPERUSER_STATE_BYPASS: + return True + if user.is_authenticated: + auth = AuthServicesInfo.objects.get_or_create(user=user)[0] + return auth.state in states + return False diff --git a/groupmanagement/admin.py b/groupmanagement/admin.py index 4d21a00f..aa7ea35a 100644 --- a/groupmanagement/admin.py +++ b/groupmanagement/admin.py @@ -1,13 +1,15 @@ from __future__ import unicode_literals from django.contrib import admin -from groupmanagement.models import GroupDescription from groupmanagement.models import GroupRequest -from groupmanagement.models import HiddenGroup -from groupmanagement.models import OpenGroup +from groupmanagement.models import AuthGroup -admin.site.register(GroupDescription) +class AuthGroupAdmin(admin.ModelAdmin): + """ + Admin model for AuthGroup + """ + filter_horizontal = ('group_leaders',) + admin.site.register(GroupRequest) -admin.site.register(HiddenGroup) -admin.site.register(OpenGroup) +admin.site.register(AuthGroup, AuthGroupAdmin) diff --git a/groupmanagement/context_processors.py b/groupmanagement/context_processors.py new file mode 100644 index 00000000..0d60be3f --- /dev/null +++ b/groupmanagement/context_processors.py @@ -0,0 +1,5 @@ +from groupmanagement.managers import GroupManager + + +def can_manage_groups(request): + return {'can_manage_groups': GroupManager.can_manage_groups(request.user)} diff --git a/groupmanagement/managers.py b/groupmanagement/managers.py new file mode 100644 index 00000000..41188257 --- /dev/null +++ b/groupmanagement/managers.py @@ -0,0 +1,56 @@ +from django.contrib.auth.models import Group +from django.conf import settings +from authentication.managers import UserState + +class GroupManager: + def __init__(self): + pass + + @staticmethod + def get_joinable_groups(): + return Group.objects.exclude(authgroup__internal=True) + + @staticmethod + def get_group_leaders_groups(user): + return Group.objects.filter(authgroup__group_leaders__in=[user]) + + @staticmethod + def joinable_group(group): + """ + Check if a group is a user joinable group, i.e. + not an internal group for Corp, Alliance, Members etc + :param group: django.contrib.auth.models.Group object + :return: bool True if its joinable, False otherwise + """ + return not group.authgroup.internal + + @staticmethod + def has_management_permission(user): + return user.has_perm('auth.group_management') + + @classmethod + def can_manage_groups(cls, user): + """ + For use with user_passes_test decorator. + Check if the user can manage groups. Either has the + auth.group_management permission or is a leader of at least one group + and is also a Member. + :param user: django.contrib.auth.models.User for the request + :return: bool True if user can manage groups, False otherwise + """ + if user.is_authenticated: + return cls.has_management_permission(user) or (user.leads_groups.all() and UserState.member_state(user)) + return False + + @classmethod + def can_manage_group(cls, user, group): + """ + Check user has permission to manage the given group + :param user: User object to test permission of + :param group: Group object the user is attempting to manage + :return: True if the user can manage the group + """ + if user.is_authenticated: + return cls.has_management_permission(user) or ( + user.leads_groups.filter(group=group).exists() and UserState.member_state(user)) + return False diff --git a/groupmanagement/migrations/0004_authgroup.py b/groupmanagement/migrations/0004_authgroup.py new file mode 100644 index 00000000..a7bbda98 --- /dev/null +++ b/groupmanagement/migrations/0004_authgroup.py @@ -0,0 +1,112 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.2 on 2016-12-04 10:25 +from __future__ import unicode_literals + +from django.conf import settings +from django.db import migrations, models +from django.core.exceptions import ObjectDoesNotExist +import django.db.models.deletion + + +def internal_group(group): + return ( + "Corp_" in group.name or + "Alliance_" in group.name or + settings.DEFAULT_AUTH_GROUP in group.name or + settings.DEFAULT_BLUE_GROUP in group.name + ) + + +def combine_group_models(apps, schema_editor): + Group = apps.get_model("auth", "Group") + AuthGroup = apps.get_model("groupmanagement", "AuthGroup") + + for group in Group.objects.all(): + authgroup = AuthGroup(group=group) + if not hasattr(group, 'hiddengroup'): + authgroup.hidden = False + if hasattr(group, 'opengroup'): + authgroup.open = True + + if hasattr(group, 'groupdescription'): + authgroup.description = group.groupdescription.description + + authgroup.internal = internal_group(group) + authgroup.save() + + +def reverse_group_models(apps, schema_editor): + Group = apps.get_model("auth", "Group") + GroupDescription = apps.get_model("groupmanagement", "GroupDescription") + OpenGroup = apps.get_model("groupmanagement", "OpenGroup") + HiddenGroup = apps.get_model("groupmanagement", "HiddenGroup") + + for group in Group.objects.all(): + if not hasattr(group, 'authgroup') or group.authgroup is None: + continue + if group.authgroup.open: + OpenGroup.objects.get_or_create(group=group) + else: + try: + OpenGroup.objects.get(group=group).delete() + except ObjectDoesNotExist: + pass + + if group.authgroup.hidden: + HiddenGroup.objects.get_or_create(group=group) + else: + try: + HiddenGroup.objects.get(group=group).delete() + except ObjectDoesNotExist: + pass + + if len(group.authgroup.description): + GroupDescription.objects.update_or_create(group=group, + defaults={'description': group.authgroup.description}) + + +class Migration(migrations.Migration): + + dependencies = [ + ('auth', '0008_alter_user_username_max_length'), + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ('groupmanagement', '0003_default_groups'), + ] + + operations = [ + migrations.CreateModel( + name='AuthGroup', + fields=[ + ('group', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, primary_key=True, + serialize=False, to='auth.Group')), + ('internal', models.BooleanField(default=True, help_text='Internal group, users cannot see, join or request to join this group.
Used for groups such as Members, Corp_*, Alliance_* etc.
Overrides Hidden and Open options when selected.')), + ('hidden', models.BooleanField(default=True, help_text='Group is hidden from users but can still join with the correct link.')), + ('open', models.BooleanField(default=False, help_text='Group is open and users will be automatically added upon request.
If the group is not open users will need their request manually approved.')), + ('description', models.CharField(max_length=512, blank=True, help_text='Description of the group shown to users.', )), + + ('group_leaders', models.ManyToManyField(related_name='leads_groups', to=settings.AUTH_USER_MODEL, blank=True, help_text='Group leaders can process group requests for this group specifically. Use the auth.group_management permission to allow a user to manage all groups.',)), + ], + ), + migrations.RunPython(combine_group_models, reverse_group_models), + migrations.RemoveField( + model_name='groupdescription', + name='group', + ), + migrations.RemoveField( + model_name='hiddengroup', + name='group', + ), + migrations.RemoveField( + model_name='opengroup', + name='group', + ), + migrations.DeleteModel( + name='GroupDescription', + ), + migrations.DeleteModel( + name='HiddenGroup', + ), + migrations.DeleteModel( + name='OpenGroup', + ), + ] diff --git a/groupmanagement/models.py b/groupmanagement/models.py index ffad61aa..52d98343 100644 --- a/groupmanagement/models.py +++ b/groupmanagement/models.py @@ -3,19 +3,12 @@ from django.utils.encoding import python_2_unicode_compatible from django.db import models from django.contrib.auth.models import User from django.contrib.auth.models import Group +from django.db.models.signals import post_save +from django.dispatch import receiver from eveonline.models import EveCharacter -@python_2_unicode_compatible -class GroupDescription(models.Model): - description = models.CharField(max_length=512) - group = models.OneToOneField(Group) - - def __str__(self): - return self.group.name + " - Description" - - @python_2_unicode_compatible class GroupRequest(models.Model): status = models.CharField(max_length=254) @@ -29,16 +22,57 @@ class GroupRequest(models.Model): @python_2_unicode_compatible -class HiddenGroup(models.Model): - group = models.OneToOneField(Group) +class AuthGroup(models.Model): + """ + Extends Django Group model with a one-to-one field + Attributes are accessible via group as if they were in the model + e.g. group.authgroup.internal + + Logic: + Internal - not requestable by users, at all. Covers Corp_, Alliance_, Members etc groups. + Groups are internal by default + + Not Internal and: + Hidden - users cannot view, can request if they have the direct link. + Not Hidden - Users can view and request the group + Open - Users are automatically accepted into the group + Not Open - Users requests must be approved before they are added to the group + """ + group = models.OneToOneField(Group, on_delete=models.CASCADE, primary_key=True) + + internal = models.BooleanField(default=True, + help_text="Internal group, users cannot see, join or request to join this group.
" + "Used for groups such as Members, Corp_*, Alliance_* etc.
" + "Overrides Hidden and Open options when selected.") + hidden = models.BooleanField(default=True, + help_text="Group is hidden from users but can still join with the correct link.") + open = models.BooleanField(default=False, + help_text="Group is open and users will be automatically added upon request.
" + "If the group is not open users will need their request manually approved.") + # Group leaders have management access to this group + group_leaders = models.ManyToManyField(User, related_name='leads_groups', blank=True, + help_text="Group leaders can process group requests for this group " + "specifically. Use the auth.group_management permission to allow " + "a user to manage all groups.") + + description = models.CharField(max_length=512, blank=True, help_text="Description of the group shown to users.") def __str__(self): - return self.group.name + " - Hidden" + return self.group.name -@python_2_unicode_compatible -class OpenGroup(models.Model): - group = models.OneToOneField(Group) +@receiver(post_save, sender=Group) +def create_auth_group(sender, instance, created, **kwargs): + """ + Creates the AuthGroup model when a group is created + """ + if created: + AuthGroup.objects.create(group=instance) - def __str__(self): - return self.group.name + " - Open" + +@receiver(post_save, sender=Group) +def save_auth_group(sender, instance, **kwargs): + """ + Ensures AuthGroup model is saved automatically + """ + instance.authgroup.save() diff --git a/groupmanagement/views.py b/groupmanagement/views.py index 9a9eaf91..d0a00b4d 100755 --- a/groupmanagement/views.py +++ b/groupmanagement/views.py @@ -1,18 +1,18 @@ from __future__ import unicode_literals from django.shortcuts import render, redirect -from django.conf import settings from django.contrib.auth.decorators import login_required -from django.contrib.auth.decorators import permission_required +from django.contrib.auth.decorators import user_passes_test from django.contrib.auth.models import Group from django.contrib import messages from notifications import notify -from groupmanagement.models import GroupDescription +from django.utils.translation import ugettext_lazy as _ +from django.db.models import Count +from django.core.exceptions import ObjectDoesNotExist, PermissionDenied +from django.http import Http404 +from groupmanagement.managers import GroupManager from groupmanagement.models import GroupRequest -from groupmanagement.models import HiddenGroup -from groupmanagement.models import OpenGroup from authentication.models import AuthServicesInfo from eveonline.managers import EveManager -from django.utils.translation import ugettext_lazy as _ import logging @@ -20,17 +20,25 @@ logger = logging.getLogger(__name__) @login_required -@permission_required('auth.group_management') +@user_passes_test(GroupManager.can_manage_groups) def group_management(request): logger.debug("group_management called by user %s" % request.user) acceptrequests = [] leaverequests = [] - for grouprequest in GroupRequest.objects.all(): + if GroupManager.has_management_permission(request.user): + # Full access + group_requests = GroupRequest.objects.all() + else: + # Group specific leader + group_requests = GroupRequest.objects.filter(group__authgroup__group_leaders__in=[request.user]) + + for grouprequest in group_requests: if grouprequest.leave_request: leaverequests.append(grouprequest) else: acceptrequests.append(grouprequest) + logger.debug("Providing user %s with %s acceptrequests and %s leaverequests." % ( request.user, len(acceptrequests), len(leaverequests))) @@ -40,12 +48,98 @@ def group_management(request): @login_required -@permission_required('auth.group_management') +@user_passes_test(GroupManager.can_manage_groups) +def group_membership(request): + logger.debug("group_membership called by user %s" % request.user) + # Get all open and closed groups + if GroupManager.has_management_permission(request.user): + # Full access + groups = GroupManager.get_joinable_groups() + else: + # Group leader specific + groups = GroupManager.get_group_leaders_groups(request.user) + + groups = groups.exclude(authgroup__internal=True).annotate(num_members=Count('user')).order_by('name') + + render_items = {'groups': groups} + + return render(request, 'registered/groupmembership.html', context=render_items) + + +@login_required +@user_passes_test(GroupManager.can_manage_groups) +def group_membership_list(request, group_id): + logger.debug("group_membership_list called by user %s for group id %s" % (request.user, group_id)) + try: + group = Group.objects.get(id=group_id) + + # Check its a joinable group i.e. not corp or internal + # And the user has permission to manage it + if not GroupManager.joinable_group(group) or not GroupManager.can_manage_group(request.user, group): + logger.warning("User %s attempted to view the membership of group %s but permission was denied" % + (request.user, group_id)) + raise PermissionDenied + + except ObjectDoesNotExist: + raise Http404("Group does not exist") + + members = list() + + for member in group.user_set.all().order_by('username'): + authinfo = AuthServicesInfo.objects.get_or_create(user=member)[0] + + members.append({ + 'user': member, + 'main_char': EveManager.get_character_by_id(authinfo.main_char_id) + }) + + render_items = {'group': group, 'members': members} + + return render(request, 'registered/groupmembers.html', context=render_items) + + +@login_required +@user_passes_test(GroupManager.can_manage_groups) +def group_membership_remove(request, group_id, user_id): + logger.debug("group_membership_remove called by user %s for group id %s on user id %s" % + (request.user, group_id, user_id)) + try: + group = Group.objects.get(id=group_id) + + # Check its a joinable group i.e. not corp or internal + # And the user has permission to manage it + if not GroupManager.joinable_group(group) or not GroupManager.can_manage_group(request.user, group): + logger.warning("User %s attempted to remove a user from group %s but permission was denied" % (request.user, + group_id)) + raise PermissionDenied + + try: + user = group.user_set.get(id=user_id) + # Remove group from user + user.groups.remove(group) + logger.info("User %s removed user %s from group %s" % (request.user, user, group)) + messages.success(request, "Removed user %s from group %s" % (user, group)) + except ObjectDoesNotExist: + messages.warning(request, "User does not exist in that group") + + except ObjectDoesNotExist: + messages.warning(request, "Group does not exist") + + return redirect('auth_group_membership_list', group_id) + + +@login_required +@user_passes_test(GroupManager.can_manage_groups) def group_accept_request(request, group_request_id): logger.debug("group_accept_request called by user %s for grouprequest id %s" % (request.user, group_request_id)) try: group_request = GroupRequest.objects.get(id=group_request_id) group, created = Group.objects.get_or_create(name=group_request.group.name) + + if not GroupManager.joinable_group(group_request.group) or \ + not GroupManager.can_manage_group(request.user, group_request.group): + raise PermissionDenied + group_request.user.groups.add(group) group_request.user.save() group_request.delete() @@ -55,6 +149,11 @@ def group_accept_request(request, group_request_id): message="Your application to %s has been accepted." % group_request.group) messages.success(request, 'Accepted application from %s to %s.' % (group_request.main_char, group_request.group)) + + except PermissionDenied as p: + logger.warning("User %s attempted to accept group join request %s but permission was denied" % + (request.user, group_request_id)) + raise p except: messages.error(request, 'An unhandled error occurred while processing the application from %s to %s.' % ( group_request.main_char, group_request.group)) @@ -66,12 +165,15 @@ def group_accept_request(request, group_request_id): @login_required -@permission_required('auth.group_management') +@user_passes_test(GroupManager.can_manage_groups) def group_reject_request(request, group_request_id): logger.debug("group_reject_request called by user %s for group request id %s" % (request.user, group_request_id)) try: group_request = GroupRequest.objects.get(id=group_request_id) + if not GroupManager.can_manage_group(request.user, group_request.group): + raise PermissionDenied + if group_request: logger.info("User %s rejected group request from user %s to group %s" % ( request.user, group_request.user, group_request.group.name)) @@ -80,6 +182,11 @@ def group_reject_request(request, group_request_id): message="Your application to %s has been rejected." % group_request.group) messages.success(request, 'Rejected application from %s to %s.' % (group_request.main_char, group_request.group)) + + except PermissionDenied as p: + logger.warning("User %s attempted to reject group join request %s but permission was denied" % + (request.user, group_request_id)) + raise p except: messages.error(request, 'An unhandled error occured while processing the application from %s to %s.' % ( group_request.main_char, group_request.group)) @@ -91,12 +198,16 @@ def group_reject_request(request, group_request_id): @login_required -@permission_required('auth.group_management') +@user_passes_test(GroupManager.can_manage_groups) def group_leave_accept_request(request, group_request_id): logger.debug( "group_leave_accept_request called by user %s for group request id %s" % (request.user, group_request_id)) try: group_request = GroupRequest.objects.get(id=group_request_id) + + if not GroupManager.can_manage_group(request.user, group_request.group): + raise PermissionDenied + group, created = Group.objects.get_or_create(name=group_request.group.name) group_request.user.groups.remove(group) group_request.user.save() @@ -107,6 +218,10 @@ def group_leave_accept_request(request, group_request_id): message="Your request to leave %s has been accepted." % group_request.group) messages.success(request, 'Accepted application from %s to leave %s.' % (group_request.main_char, group_request.group)) + except PermissionDenied as p: + logger.warning("User %s attempted to accept group leave request %s but permission was denied" % + (request.user, group_request_id)) + raise p except: messages.error(request, 'An unhandled error occured while processing the application from %s to leave %s.' % ( group_request.main_char, group_request.group)) @@ -118,13 +233,16 @@ def group_leave_accept_request(request, group_request_id): @login_required -@permission_required('auth.group_management') +@user_passes_test(GroupManager.can_manage_groups) def group_leave_reject_request(request, group_request_id): logger.debug( "group_leave_reject_request called by user %s for group request id %s" % (request.user, group_request_id)) try: group_request = GroupRequest.objects.get(id=group_request_id) + if not GroupManager.can_manage_group(request.user, group_request.group): + raise PermissionDenied + if group_request: group_request.delete() logger.info("User %s rejected group leave request from user %s for group %s" % ( @@ -133,6 +251,10 @@ def group_leave_reject_request(request, group_request_id): message="Your request to leave %s has been rejected." % group_request.group) messages.success(request, 'Rejected application from %s to leave %s.' % ( group_request.main_char, group_request.group)) + except PermissionDenied as p: + logger.warning("User %s attempted to reject group leave request %s but permission was denied" % + (request.user, group_request_id)) + raise p except: messages.error(request, 'An unhandled error occured while processing the application from %s to leave %s.' % ( group_request.main_char, group_request.group)) @@ -146,37 +268,16 @@ def group_leave_reject_request(request, group_request_id): @login_required def groups_view(request): logger.debug("groups_view called by user %s" % request.user) - paired_list = [] + groups = [] - for group in Group.objects.all(): - # Check if group is a corp - if "Corp_" in group.name: - pass - elif "Alliance_" in group.name: - pass - elif settings.DEFAULT_AUTH_GROUP in group.name: - pass - elif settings.DEFAULT_BLUE_GROUP in group.name: - pass - elif HiddenGroup.objects.filter(group=group).exists(): - pass - else: - # Get the descriptionn - group_desc = GroupDescription.objects.filter(group=group) + for group in GroupManager.get_joinable_groups(): + # Exclude hidden + if not group.authgroup.hidden: group_request = GroupRequest.objects.filter(user=request.user).filter(group=group) - if group_desc: - if group_request: - paired_list.append((group, group_desc[0], group_request[0])) - else: - paired_list.append((group, group_desc[0], "")) - else: - if group_request: - paired_list.append((group, "", group_request[0])) - else: - paired_list.append((group, "", "")) + groups.append({'group': group, 'request': group_request[0] if group_request else None}) - render_items = {'pairs': paired_list} + render_items = {'groups': groups} return render(request, 'registered/groups.html', context=render_items) @@ -184,7 +285,12 @@ def groups_view(request): def group_request_add(request, group_id): logger.debug("group_request_add called by user %s for group id %s" % (request.user, group_id)) group = Group.objects.get(id=group_id) - if OpenGroup.objects.filter(group=group).exists(): + if not GroupManager.joinable_group(group): + logger.warning("User %s attempted to join group id %s but it is not a joinable group" % + (request.user, group_id)) + messages.warning(request, "You cannot join that group") + return redirect('auth_groups') + if group.authgroup.open: logger.info("%s joining %s as is an open group" % (request.user, group)) request.user.groups.add(group) return redirect("auth_groups") @@ -205,7 +311,17 @@ def group_request_add(request, group_id): def group_request_leave(request, group_id): logger.debug("group_request_leave called by user %s for group id %s" % (request.user, group_id)) group = Group.objects.get(id=group_id) - if OpenGroup.objects.filter(group=group).exists(): + if not GroupManager.joinable_group(group): + logger.warning("User %s attempted to leave group id %s but it is not a joinable group" % + (request.user, group_id)) + messages.warning(request, "You cannot leave that group") + return redirect('auth_groups') + if group not in request.user.groups.all(): + logger.debug("User %s attempted to leave group id %s but they are not a member" % + (request.user, group_id)) + messages.warning(request, "You are not a member of that group") + return redirect('auth_groups') + if group.authgroup.open: logger.info("%s leaving %s as is an open group" % (request.user, group)) request.user.groups.remove(group) return redirect("auth_groups") diff --git a/stock/templates/public/base.html b/stock/templates/public/base.html index 595dbfc5..7e6a6ce1 100755 --- a/stock/templates/public/base.html +++ b/stock/templates/public/base.html @@ -153,7 +153,7 @@ {% endif %} - {% if perms.auth.group_management %} + {% if can_manage_groups %}
  • {% trans " Group Management" %} diff --git a/stock/templates/registered/groupmanagement.html b/stock/templates/registered/groupmanagement.html index 9f48722d..767d7e75 100644 --- a/stock/templates/registered/groupmanagement.html +++ b/stock/templates/registered/groupmanagement.html @@ -9,7 +9,7 @@ {% block content %}
    - + {% include 'registered/groupmanagementmenu.html' %}