diff --git a/jasmin_services/forms.py b/jasmin_services/forms.py index 4ddecdc..39f2859 100644 --- a/jasmin_services/forms.py +++ b/jasmin_services/forms.py @@ -22,7 +22,7 @@ from markdown_deux.templatetags.markdown_deux_tags import markdown_allowed -from .models import Grant, RequestState, LdapGroupBehaviour +from .models import Grant, RequestState, LdapGroupBehaviour, Role def message_form_factory(sender, *roles): @@ -65,6 +65,35 @@ def message_form_factory(sender, *roles): }) +def group_form_factory(service): + """ + Factory function that creates a group form for a set of roles. + """ + ROLE_CHOICES = [(role, role.name) for role in service.roles.all()] + + return type(uuid.uuid4().hex, (forms.Form, ), { + 'name' : forms.CharField(label = 'Name', required = True, max_length = 15), + 'description' : forms.CharField(label = 'Description'), + 'approver_roles' : forms.MultipleChoiceField( + required = False, + choices = ROLE_CHOICES, + label = 'Approver roles', + help_text = mark_safe( + 'The selected roles will be able to approve request for ' + 'the created role.' + ) + ), + }) + + +class GroupForm(forms.Form): + """ + Form for creating a key LDAP group for an object store. + """ + name = forms.CharField(label = 'Name', required = True, max_length = 15) + description = forms.CharField(label = 'Description') + + class DecisionForm(forms.Form): """ Form for making a decision on a request. diff --git a/jasmin_services/migrations/0011_auto_20210308_0936.py b/jasmin_services/migrations/0011_auto_20210308_0936.py new file mode 100644 index 0000000..82d4b9d --- /dev/null +++ b/jasmin_services/migrations/0011_auto_20210308_0936.py @@ -0,0 +1,19 @@ +# Generated by Django 2.2.9 on 2021-03-08 09:36 + +import django.core.validators +from django.db import migrations +import jasmin_ldap_django.models + + +class Migration(migrations.Migration): + + dependencies = [ + ('jasmin_services', '0010_auto_20190916_1328'), + ] + + operations = [ + migrations.AlterModelOptions( + name='service', + options={'ordering': ('category__position', 'category__long_name', 'position', 'name'), 'permissions': (('create_group_role', 'Can great a new group'),)}, + ), + ] diff --git a/jasmin_services/models/base.py b/jasmin_services/models/base.py index ac59a5c..cb6c767 100644 --- a/jasmin_services/models/base.py +++ b/jasmin_services/models/base.py @@ -64,6 +64,9 @@ class Meta: 'position', 'name' ) + permissions = ( + ('create_group_role', 'Can great a new group'), + ) #: The category that the service belongs to category = models.ForeignKey(Category, models.CASCADE, diff --git a/jasmin_services/templates/jasmin_services/includes/service_tabs.html b/jasmin_services/templates/jasmin_services/includes/service_tabs.html index 5dfd2ee..26d4016 100644 --- a/jasmin_services/templates/jasmin_services/includes/service_tabs.html +++ b/jasmin_services/templates/jasmin_services/includes/service_tabs.html @@ -3,9 +3,10 @@ {% user_has_service_perm service request.user 'jasmin_services.view_users_role' as view_users %} {% user_has_service_perm service request.user 'jasmin_services.decide_request' as approver %} {% user_has_service_perm service request.user 'jasmin_services.send_message_role' as send_message %} +{% user_has_service_perm service request.user 'jasmin_services.create_group_role' as create_group %} {% with active=request.resolver_match.url_name %} - {% if view_users or approver or send_message %} + {% if view_users or approver or send_message or create_group %} {% endif %} {% endwith %} diff --git a/jasmin_services/templates/jasmin_services/service_group.html b/jasmin_services/templates/jasmin_services/service_group.html new file mode 100644 index 0000000..162217a --- /dev/null +++ b/jasmin_services/templates/jasmin_services/service_group.html @@ -0,0 +1,60 @@ +{% extends 'layout.html' %} +{% load staticfiles bootstrap3 %} + +{% block head_js_extra %}{{ block.super }} + +{% endblock %} + +{% block stylesheets_extra %}{{ block.super }} + +{% endblock %} + +{% block page_title %}Create a group{% endblock %} + +{% block breadcrumbs %} + +{% endblock %} + +{% block content_header %}{{ block.super }} +
+
+ {% include "jasmin_services/includes/service_tabs.html" %} +
+
+{% endblock %} + +{% block content %} +
+
+ {% csrf_token %} + + {% bootstrap_form form layout='horizontal' %} + +
+
+ +
+
+
+
+ +{% endblock %} diff --git a/jasmin_services/urls.py b/jasmin_services/urls.py index 989e555..6354242 100644 --- a/jasmin_services/urls.py +++ b/jasmin_services/urls.py @@ -26,6 +26,7 @@ url(r'^requests/$', views.service_requests, name = 'service_requests'), url(r'^users/$', views.service_users, name = 'service_users'), url(r'^message/$', views.service_message, name = 'service_message'), + url(r'^groups/$', views.service_groups, name = 'service_groups'), ])), url(r'^(?P[\w-]+)/(?P[\w-]+)/apply/(?P[\w-]+)/$', views.role_apply, diff --git a/jasmin_services/views.py b/jasmin_services/views.py index dfaf995..237a352 100644 --- a/jasmin_services/views.py +++ b/jasmin_services/views.py @@ -25,12 +25,15 @@ from django.template.loader import render_to_string from django.db import transaction from django.contrib.auth import get_user_model +from django.contrib.auth.models import Permission from django.contrib.auth.decorators import user_passes_test +from django.contrib.contenttypes.models import ContentType from django.contrib.auth.decorators import login_required -from .models import Grant, Request, RequestState, Category, Service, Role -from .forms import DecisionForm, message_form_factory +from jasmin_metadata.models import Metadatum, Form +from .models import Grant, Request, RequestState, Category, Service, Role, Group, RoleObjectPermission, LdapGroupBehaviour, CedaLdapGroup +from .forms import DecisionForm, message_form_factory, group_form_factory _log = logging.getLogger(__name__) @@ -801,6 +804,129 @@ def service_message(request, service): 'form': form, }) +# from . import models as service_models +# CedaLdapGroup = getattr(models, 'CedaLdapGroup') +@require_http_methods(['GET', 'POST']) +@login_required +@with_service +def service_groups(request, service): + """ + Handler for ``///groups/``. + + Responds to GET and POST. The user must have the ``create_groups_role`` + permission for the service. + + Allows a user with suitable permissions to create an LDAP group which other + users of the service can apply for. + """ + # The current user must have permission to create a group. + permission = 'jasmin_services.create_group_role' + if not request.user.has_perm(permission) and \ + not request.user.has_perm(permission, service): + messages.error(request, 'Insufficient permissions') + return redirect_to_service(service) + + GroupForm = group_form_factory(service) + if request.method == 'POST': + form = GroupForm(request.POST) + if form.is_valid(): + try: + name = form.cleaned_data['name'] + description = form.cleaned_data['description'] + approver_roles = form.cleaned_data['approver_roles'] + LDAP_group_name = (service.name + '_' + name).replace('-', '_') + default_form = Form.objects.get(pk = settings.JASMIN_SERVICES['DEFAULT_METADATA_FORM']) + + # Check if there is already a role with this name. + try: + Role.objects.get( + service = service, + name = name, + ) + raise Exception('Already exists') + except Role.DoesNotExist: + role = Role( + service = service, + name = name, + description = description, + metadata_form = default_form, + hidden = False, + ) + + # Check if there is already a ceda ldap group with this name. + try: + CedaLdapGroup.objects.get(name = LDAP_group_name) + raise Exception('Already exists') + except CedaLdapGroup.DoesNotExist: + group = CedaLdapGroup( + name = LDAP_group_name, + description = description, + ) + + # Create the LDAP group behaviour. + ldap_group_behaviour = LdapGroupBehaviour.objects.create( + ldap_model = group, + group_name = LDAP_group_name, + ) + + # Save both after check, as not to create only one. + group.save() + role.save() + role.behaviours.add(ldap_group_behaviour) + + # Create relevant role_object_permissions for the approver roles. + permissions = [ + Permission.objects.get( + content_type = ContentType.objects.get_for_model(Role), + codename = 'view_users_role', + ), + Permission.objects.get( + content_type = ContentType.objects.get_for_model(Role), + codename = 'send_message_role', + ), + Permission.objects.get( + content_type = ContentType.objects.get_for_model(Request), + codename = 'decide_request', + ), + ] + + for approver_role in approver_roles: + RoleObjectPermission.objects.bulk_create([ + RoleObjectPermission( + role = approver_role, + permission = permission, + content_type = ContentType.objects.get_for_model(role), + object_pk = role.pk + ) + for permission in permissions + ]) + + messages.success(request, 'Group created') + return redirect_to_service(service) + except Exception as e: + print(e) + if str(e) == 'Already exists': + messages.error(request, 'Group with this name already exists') + else: + messages.error(request, 'Error creating group') + + else: + messages.error(request, 'Error with one or more fields') + else: + form = GroupForm() + templates = [ + 'jasmin_services/{}/{}/service_group.html'.format( + service.category.name, + service.name + ), + 'jasmin_services/{}/service_group.html'.format(service.category.name), + 'jasmin_services/service_group.html', + ] + return render(request, templates, { + 'service': service, + 'form': form, + }) + @require_safe def reverse_dns_check(request):