diff --git a/core b/core deleted file mode 100644 index e69de29..0000000 diff --git a/jasmin_services/templates/jasmin_services/role_apply.html b/jasmin_services/templates/jasmin_services/role_apply.html index 8cf12cd..a24b39d 100644 --- a/jasmin_services/templates/jasmin_services/role_apply.html +++ b/jasmin_services/templates/jasmin_services/role_apply.html @@ -33,22 +33,6 @@ - - {% if licence_url %} -
-
- -
-

- - Click here - -

-
-
-
- {% endif %} - {% if req or grant %}
diff --git a/jasmin_services/urls.py b/jasmin_services/urls.py index 5291120..2223772 100644 --- a/jasmin_services/urls.py +++ b/jasmin_services/urls.py @@ -35,12 +35,12 @@ ), path( "//apply//", - views.role_apply, + views.RoleApplyView.as_view(), name="role_apply", ), path( "//apply////", - views.role_apply, + views.RoleApplyView.as_view(), name="role_apply", ), path("request//decide/", views.request_decide, name="request_decide"), diff --git a/jasmin_services/views/__init__.py b/jasmin_services/views/__init__.py index 66d56f5..09d19e0 100644 --- a/jasmin_services/views/__init__.py +++ b/jasmin_services/views/__init__.py @@ -3,7 +3,7 @@ from .my_services import my_services from .request_decide import request_decide from .reverse_dns_check import reverse_dns_check -from .role_apply import role_apply +from .role_apply import RoleApplyView from .service_details import service_details from .service_list import service_list from .service_message import service_message @@ -16,7 +16,7 @@ "my_services", "request_decide", "reverse_dns_check", - "role_apply", + "RoleApplyView", "service_details", "service_list", "service_message", diff --git a/jasmin_services/views/role_apply.py b/jasmin_services/views/role_apply.py index 46cb659..cd2204b 100644 --- a/jasmin_services/views/role_apply.py +++ b/jasmin_services/views/role_apply.py @@ -1,158 +1,195 @@ import logging from datetime import date +import django.contrib.auth.mixins +import django.core.exceptions import django.http -import requests as reqs +import django.views.generic +import django.views.generic.edit +import requests from dateutil.relativedelta import relativedelta from django.conf import settings from django.contrib import messages -from django.contrib.auth.decorators import login_required from django.db import transaction -from django.shortcuts import render -from django.views.decorators.http import require_http_methods +from .. import models from ..models import Access, Grant, Group, Request, RequestState, Role from . import common _log = logging.getLogger(__name__) -@require_http_methods(["GET", "POST"]) -@login_required -@common.with_service -def role_apply(request, service, role, bool_grant=None, previous=None): +class RoleApplyView( + django.contrib.auth.mixins.LoginRequiredMixin, django.views.generic.edit.FormView +): """Handle for ``///apply//``. - Responds to GET and POST requests. The user must be authenticated. - Collects the necessary information to raise a request for a role. """ - # Prevent users who are not allowed to apply for this service from doing so. - user_may_apply = common.user_may_apply(request.user, service) - if not user_may_apply[0]: - return django.http.HttpResponseForbidden( - "You do not have permission to apply for this service." - ) - try: - role = Role.objects.get(service=service, name=role) - except Role.DoesNotExist: - messages.error(request, "Role does not exist") - return common.redirect_to_service(service) - - previous_grant = None - previous_request = None - # bool_grant = 1 if the new request is being made from a previous grant - if bool_grant == 1: - previous_grant = Grant.objects.get(pk=previous) - previous_request = ( - Request.objects.filter_active().filter(previous_grant=previous_grant).first() + @staticmethod + def get_service(category_name, service_name): + """Get a service from it's category and name.""" + try: + return models.Service.objects.get(name=service_name, category__name=category_name) + except models.Service.DoesNotExist as err: + raise django.http.Http404("Service does not exist.") from err + + @staticmethod + def get_previous_request_and_grant(bool_grant, previous): + """Get the previous request or grant from the id supplied.""" + error = None + previous_grant = None + previous_request = None + # bool_grant = 1 if the new request is being made from a previous grant + if bool_grant == 1: + previous_grant = Grant.objects.get(pk=previous) + previous_request = ( + Request.objects.filter_active().filter(previous_grant=previous_grant).first() + ) + # bool_grant = 0 if the new request is being made from a previous request + elif bool_grant == 0: + previous_request = Request.objects.get(pk=previous) + if previous_request.previous_grant: + previous_grant = previous_request.previous_grant + + # If the user has a more recent request or grant for this chain they must use that + if (previous_request and hasattr(previous_request, "next_request")) or ( + previous_grant and hasattr(previous_grant, "next_grant") + ): + error = "Please use the most recent request or grant" + + # If the user has an active request for this chain it must be rejected + elif previous_request and previous_request.state != RequestState.REJECTED: + error = "You have already have an active request for the specified grant" + + return error, previous_grant, previous_request + + def setup(self, request, *args, **kwargs): + """Set up extra class attributes depending on the service and role we are dealing with. + + These are needed throughout and not just in the context. + """ + # pylint: disable=attribute-defined-outside-init + super().setup(request, *args, **kwargs) + self.redirect_to_service = True + + # Get the service from the request. + self.service = self.get_service(kwargs["category"], kwargs["service"]) + if self.service.disabled: + raise django.http.Http404("Service has been retired.") + + # Prevent users who are not allowed to apply for this service from doing so. + user_may_apply = common.user_may_apply(request.user, self.service) + if not user_may_apply[0]: + raise django.core.exceptions.PermissionDenied( + "You do not have permission to apply for this service." + ) + + # Get the role we are applying for from the request. + try: + self.role = Role.objects.get(service=self.service, name=kwargs["role"]) + except Role.DoesNotExist: + messages.error(request, "Role does not exist") + self.redirect_to_service = True + return + + # Get the previous request or grant, if it is supplies. + error, self.previous_request, self.previous_grant = self.get_previous_request_and_grant( + kwargs.get("bool_grant", None), kwargs.get("previous", None) ) - # bool_grant = 0 if the new request is being made from a previous request - elif bool_grant == 0: - previous_request = Request.objects.get(pk=previous) - if previous_request.previous_grant: - previous_grant = previous_request.previous_grant - - # If the user has a more recent request or grant for this chain they must use that - if (previous_request and hasattr(previous_request, "next_request")) or ( - previous_grant and hasattr(previous_grant, "next_grant") - ): - messages.info(request, "Please use the most recent request or grant") - return common.redirect_to_service(service) - - # If the user has an active request for this chain it must be rejected - if previous_request and previous_request.state != RequestState.REJECTED: - messages.info(request, "You have already have an active request for the specified grant") - return common.redirect_to_service(service) - - # ONLY FOR CEDA SERVICES: Get licence url - licence_url = None - if settings.LICENCE_REQUIRED: - groups = [b for b in role.behaviours.all() if isinstance(b, Group)] - if groups: - group = groups[0] - response = reqs.get( - settings.licence_url, - params={"group": group.name}, + if error is not None: + messages.info(request, error) + self.redirect_to_service = True + return + + # If we have sucessfuly got all the info, we do not need to redirect to the service. + self.redirect_to_service = False + + def dispatch(self, request, *args, **kwargs): + """Check the user may apply for the role before dispatching.""" + if self.redirect_to_service: + return common.redirect_to_service(self.service) + + return super().dispatch(request, *args, **kwargs) + + def get_form_class(self): + """Use the form class for the role being applied for. + + This includes the correct metadata forms for the role. + """ + return self.role.metadata_form_class + + def get_initial(self): + """Add previous requests to inital form data.""" + initial = {} + if self.previous_request is not None: + for datum in self.previous_request.metadata.all(): + initial[datum.key] = datum.value + return initial + + def get_template_names(self): + """Allow overriding the template at multiple levels.""" + return [ + f"jasmin_services/{self.service.category.name}/{self.service.name}/{self.role.name}/role_apply.html", + f"jasmin_services/{self.service.category.name}/{self.service.name}/role_apply.html", + f"jasmin_services/{self.service.category.name}/role_apply.html", + "jasmin_services/role_apply.html", + ] + + def get_context_data(self, **kwargs): + """Add the role, grant and request to the context.""" + context = super().get_context_data(**kwargs) + context |= { + "role": self.role, + "grant": self.previous_grant, + "req": self.previous_request, + } + return context + + def form_valid(self, form): + """Handle the form and create the role request.""" + with transaction.atomic(): + access, _ = Access.objects.get_or_create( + role=self.role, + user=self.request.user, ) - json_response = response.json() - licence_url = json_response["licence"] - - # Otherwise, attempt to do something - form_class = role.metadata_form_class - if request.method == "POST": - form = form_class(data=request.POST) - if form.is_valid(): - with transaction.atomic(): - access, _ = Access.objects.get_or_create( - role=role, - user=request.user, + # If the role is set to auto accept, grant before saving + if self.role.auto_accept: + req = Request.objects.create( + access=access, + requested_by=self.request.user.username, + state=RequestState.APPROVED, + ) + req.resulting_grant = Grant.objects.create( + access=access, + granted_by="automatic", + expires=date.today() + relativedelta(years=1), ) - # If the role is set to auto accept, grant before saving - if role.auto_accept: - req = Request.objects.create( - access=access, - requested_by=request.user.username, - state=RequestState.APPROVED, - ) - req.resulting_grant = Grant.objects.create( - access=access, - granted_by="automatic", - expires=date.today() + relativedelta(years=1), - ) - - if previous_request: - req.previous_request = previous_request - req.save() - - if previous_grant: - req.resulting_grant.previous_grant = previous_grant - req.previous_grant = previous_grant - req.resulting_grant.save() + if self.previous_request is not None: + req.previous_request = self.previous_request req.save() - form.save(req) - req.copy_metadata_to(req.resulting_grant) - else: - req = Request.objects.create(access=access, requested_by=request.user.username) - if previous_request: - previous_request.next_request = req - previous_request.save() + if self.previous_grant is not None: + req.resulting_grant.previous_grant = self.previous_grant + req.previous_grant = self.previous_grant + req.resulting_grant.save() - if previous_grant: - req.previous_grant = previous_grant + req.save() + form.save(req) + req.copy_metadata_to(req.resulting_grant) + else: + req = Request.objects.create(access=access, requested_by=self.request.user.username) - req.save() - form.save(req) - messages.success(request, "Request submitted successfully") - return common.redirect_to_service(service) - else: - messages.error(request, "Error with one or more fields") - else: - # Set the initial data to the metadata attached to the active request - initial = {} - if previous_request: - for datum in previous_request.metadata.all(): - initial[datum.key] = datum.value - form = form_class(initial=initial) - templates = [ - "jasmin_services/{}/{}/{}/role_apply.html".format( - service.category.name, service.name, role.name - ), - "jasmin_services/{}/{}/role_apply.html".format(service.category.name, service.name), - "jasmin_services/{}/role_apply.html".format(service.category.name), - "jasmin_services/role_apply.html", - ] - return render( - request, - templates, - { - "role": role, - "grant": previous_grant, - "req": previous_request, - "form": form, - "licence_url": licence_url, - }, - ) + if self.previous_request is not None: + self.previous_request.next_request = req + self.previous_request.save() + + if self.previous_grant is not None: + req.previous_grant = self.previous_grant + req.save() + form.save(req) + + messages.success(self.request, "Request submitted successfully") + return common.redirect_to_service(self.service) diff --git a/pyproject.toml b/pyproject.toml index 0bb1c4c..32febdb 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -40,6 +40,7 @@ isort = "^5.10.1" mypy = "^1.2.0" types-python-dateutil = "^2.8.19.12" django-stubs = {extras = ["compatible-mypy"], version = "^4.2.0"} +pylint-django = "^2.5.5" [tool.poetry.group.test.dependencies] django-oauth-toolkit = "^1.7.0" @@ -62,6 +63,9 @@ django_settings_module = "tests.settings" [tool.pydocstyle] ignore = ['D106'] +[tool.pylint.main] +load-plugins = 'pylint_django' + [tool.pylint.'MESSAGES CONTROL'] disable = ['too-few-public-methods', 'missing-class-docstring']