mirror of
https://github.com/cisagov/manage.get.gov.git
synced 2025-07-26 04:28:39 +02:00
additional cleanup of permission views and mixins, handling of domain invitations and user domain roles in decorators
This commit is contained in:
parent
40737cbcf7
commit
e4021b76c1
7 changed files with 196 additions and 283 deletions
|
@ -364,7 +364,7 @@ urlpatterns = [
|
||||||
name="user-profile",
|
name="user-profile",
|
||||||
),
|
),
|
||||||
path(
|
path(
|
||||||
"invitation/<int:pk>/cancel",
|
"invitation/<int:domain_invitation_pk>/cancel",
|
||||||
views.DomainInvitationCancelView.as_view(http_method_names=["post"]),
|
views.DomainInvitationCancelView.as_view(http_method_names=["post"]),
|
||||||
name="invitation-cancel",
|
name="invitation-cancel",
|
||||||
),
|
),
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import functools
|
import functools
|
||||||
from django.core.exceptions import PermissionDenied
|
from django.core.exceptions import PermissionDenied
|
||||||
from django.utils.decorators import method_decorator
|
from django.utils.decorators import method_decorator
|
||||||
from registrar.models import Domain, DomainInformation, DomainRequest, UserDomainRole
|
from registrar.models import Domain, DomainInformation, DomainInvitation, DomainRequest, UserDomainRole
|
||||||
|
|
||||||
# Constants for clarity
|
# Constants for clarity
|
||||||
ALL = "all"
|
ALL = "all"
|
||||||
|
@ -18,7 +18,6 @@ HAS_PORTFOLIO_DOMAINS_VIEW_ALL = "has_portfolio_domains_view_all"
|
||||||
HAS_PORTFOLIO_DOMAIN_REQUESTS_ANY_PERM = "has_portfolio_domain_requests_any_perm"
|
HAS_PORTFOLIO_DOMAIN_REQUESTS_ANY_PERM = "has_portfolio_domain_requests_any_perm"
|
||||||
HAS_PORTFOLIO_DOMAIN_REQUESTS_VIEW_ALL = "has_portfolio_domain_requests_view_all"
|
HAS_PORTFOLIO_DOMAIN_REQUESTS_VIEW_ALL = "has_portfolio_domain_requests_view_all"
|
||||||
HAS_PORTFOLIO_DOMAIN_REQUESTS_EDIT = "has_portfolio_domain_requests_edit"
|
HAS_PORTFOLIO_DOMAIN_REQUESTS_EDIT = "has_portfolio_domain_requests_edit"
|
||||||
# HAS_PORTFOLIO_DOMAINS_VIEW_MANAGED = "has_portfolio_domains_view_managed"
|
|
||||||
|
|
||||||
|
|
||||||
def grant_access(*rules):
|
def grant_access(*rules):
|
||||||
|
@ -90,13 +89,11 @@ def _user_has_permission(user, request, rules, **kwargs):
|
||||||
conditions_met.append(user.is_superuser)
|
conditions_met.append(user.is_superuser)
|
||||||
|
|
||||||
if not any(conditions_met) and IS_DOMAIN_MANAGER in rules:
|
if not any(conditions_met) and IS_DOMAIN_MANAGER in rules:
|
||||||
domain_id = kwargs.get("domain_pk")
|
has_permission = _is_domain_manager(user, **kwargs)
|
||||||
has_permission = _is_domain_manager(user, domain_id)
|
|
||||||
conditions_met.append(has_permission)
|
conditions_met.append(has_permission)
|
||||||
|
|
||||||
if not any(conditions_met) and IS_STAFF_MANAGING_DOMAIN in rules:
|
if not any(conditions_met) and IS_STAFF_MANAGING_DOMAIN in rules:
|
||||||
domain_id = kwargs.get("domain_pk")
|
has_permission = _is_staff_managing_domain(request, **kwargs)
|
||||||
has_permission = _can_access_other_user_domains(request, domain_id)
|
|
||||||
conditions_met.append(has_permission)
|
conditions_met.append(has_permission)
|
||||||
|
|
||||||
if not any(conditions_met) and IS_PORTFOLIO_MEMBER in rules:
|
if not any(conditions_met) and IS_PORTFOLIO_MEMBER in rules:
|
||||||
|
@ -115,13 +112,11 @@ def _user_has_permission(user, request, rules, **kwargs):
|
||||||
conditions_met.append(has_permission)
|
conditions_met.append(has_permission)
|
||||||
|
|
||||||
if not any(conditions_met) and IS_PORTFOLIO_MEMBER_AND_DOMAIN_MANAGER in rules:
|
if not any(conditions_met) and IS_PORTFOLIO_MEMBER_AND_DOMAIN_MANAGER in rules:
|
||||||
domain_id = kwargs.get("domain_pk")
|
has_permission = _is_domain_manager(user, **kwargs) and _is_portfolio_member(request)
|
||||||
has_permission = _is_domain_manager(user, domain_id) and _is_portfolio_member(request)
|
|
||||||
conditions_met.append(has_permission)
|
conditions_met.append(has_permission)
|
||||||
|
|
||||||
if not any(conditions_met) and IS_DOMAIN_MANAGER_AND_NOT_PORTFOLIO_MEMBER in rules:
|
if not any(conditions_met) and IS_DOMAIN_MANAGER_AND_NOT_PORTFOLIO_MEMBER in rules:
|
||||||
domain_id = kwargs.get("domain_pk")
|
has_permission = _is_domain_manager(user, **kwargs) and not _is_portfolio_member(request)
|
||||||
has_permission = _is_domain_manager(user, domain_id) and not _is_portfolio_member(request)
|
|
||||||
conditions_met.append(has_permission)
|
conditions_met.append(has_permission)
|
||||||
|
|
||||||
if not any(conditions_met) and IS_DOMAIN_REQUEST_CREATOR in rules:
|
if not any(conditions_met) and IS_DOMAIN_REQUEST_CREATOR in rules:
|
||||||
|
@ -156,10 +151,24 @@ def _has_portfolio_domain_requests_edit(user, request, domain_request_id):
|
||||||
return user.is_org_user(request) and user.has_edit_request_portfolio_permission(request.session.get("portfolio"))
|
return user.is_org_user(request) and user.has_edit_request_portfolio_permission(request.session.get("portfolio"))
|
||||||
|
|
||||||
|
|
||||||
def _is_domain_manager(user, domain_pk):
|
def _is_domain_manager(user, **kwargs):
|
||||||
"""Checks to see if the user is a domain manager of the
|
"""
|
||||||
domain with domain_pk."""
|
Determines if the given user is a domain manager for a specified domain.
|
||||||
return UserDomainRole.objects.filter(user=user, domain_id=domain_pk).exists()
|
|
||||||
|
- First, it checks if 'domain_pk' is present in the URL parameters.
|
||||||
|
- If 'domain_pk' exists, it verifies if the user has a domain role for that domain.
|
||||||
|
- If 'domain_pk' is absent, it checks for 'domain_invitation_pk' to determine if the user has domain permissions through an invitation.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
bool: True if the user is a domain manager, False otherwise.
|
||||||
|
"""
|
||||||
|
domain_id = kwargs.get("domain_pk")
|
||||||
|
if domain_id:
|
||||||
|
return UserDomainRole.objects.filter(user=user, domain_id=domain_id).exists()
|
||||||
|
domain_invitation_id = kwargs.get("domain_invitation_pk")
|
||||||
|
if domain_invitation_id:
|
||||||
|
return DomainInvitation.objects.filter(id=domain_invitation_id, domain__permissions__user=user).exists()
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
def _is_domain_request_creator(user, domain_request_pk):
|
def _is_domain_request_creator(user, domain_request_pk):
|
||||||
|
@ -176,10 +185,35 @@ def _is_portfolio_member(request):
|
||||||
return request.user.is_org_user(request)
|
return request.user.is_org_user(request)
|
||||||
|
|
||||||
|
|
||||||
def _can_access_other_user_domains(request, domain_pk):
|
def _is_staff_managing_domain(request, **kwargs):
|
||||||
"""Checks to see if an authorized user (staff or superuser)
|
|
||||||
can access a domain that they did not create or were invited to.
|
|
||||||
"""
|
"""
|
||||||
|
Determines whether a staff user (analyst or superuser) has permission to manage a domain
|
||||||
|
that they did not create or were not invited to.
|
||||||
|
|
||||||
|
The function enforces:
|
||||||
|
1. **User Authorization** - The user must have `analyst_access_permission` or `full_access_permission`.
|
||||||
|
2. **Valid Session Context** - The user must have explicitly selected the domain for management
|
||||||
|
via an 'analyst action' (e.g., by clicking 'Manage Domain' in the admin interface).
|
||||||
|
3. **Domain Status Check** - Only domains in specific statuses (e.g., APPROVED, IN_REVIEW, etc.)
|
||||||
|
can be managed, except in cases where the domain lacks a status due to errors.
|
||||||
|
|
||||||
|
Process:
|
||||||
|
- First, the function retrieves the `domain_pk` from the URL parameters.
|
||||||
|
- If `domain_pk` is not provided, it attempts to resolve the domain via `domain_invitation_pk`.
|
||||||
|
- It checks if the user has the required permissions.
|
||||||
|
- It verifies that the user has an active 'analyst action' session for the domain.
|
||||||
|
- Finally, it ensures that the domain is in a status that allows management.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
bool: True if the user is allowed to manage the domain, False otherwise.
|
||||||
|
"""
|
||||||
|
|
||||||
|
domain_id = kwargs.get("domain_pk")
|
||||||
|
if not domain_id:
|
||||||
|
domain_invitation_id = kwargs.get("domain_invitation_pk")
|
||||||
|
domain_invitation = DomainInvitation.objects.filter(id=domain_invitation_id).first()
|
||||||
|
if domain_invitation:
|
||||||
|
domain_id = domain_invitation.domain_id
|
||||||
|
|
||||||
# Check if the request user is permissioned...
|
# Check if the request user is permissioned...
|
||||||
user_is_analyst_or_superuser = request.user.has_perm(
|
user_is_analyst_or_superuser = request.user.has_perm(
|
||||||
|
@ -197,7 +231,7 @@ def _can_access_other_user_domains(request, domain_pk):
|
||||||
can_do_action = (
|
can_do_action = (
|
||||||
"analyst_action" in session
|
"analyst_action" in session
|
||||||
and "analyst_action_location" in session
|
and "analyst_action_location" in session
|
||||||
and session["analyst_action_location"] == domain_pk
|
and session["analyst_action_location"] == domain_id
|
||||||
)
|
)
|
||||||
|
|
||||||
if not can_do_action:
|
if not can_do_action:
|
||||||
|
@ -215,7 +249,7 @@ def _can_access_other_user_domains(request, domain_pk):
|
||||||
None,
|
None,
|
||||||
]
|
]
|
||||||
|
|
||||||
requested_domain = DomainInformation.objects.filter(domain_id=domain_pk).first()
|
requested_domain = DomainInformation.objects.filter(domain_id=domain_id).first()
|
||||||
|
|
||||||
# if no domain information or domain request exist, the user
|
# if no domain information or domain request exist, the user
|
||||||
# should be able to manage the domain; however, if domain information
|
# should be able to manage the domain; however, if domain information
|
||||||
|
|
|
@ -154,7 +154,7 @@
|
||||||
{% if not portfolio %}<td data-label="Status">{{ invitation.domain_invitation.status|title }}</td>{% endif %}
|
{% if not portfolio %}<td data-label="Status">{{ invitation.domain_invitation.status|title }}</td>{% endif %}
|
||||||
<td>
|
<td>
|
||||||
{% if invitation.domain_invitation.status == invitation.domain_invitation.DomainInvitationStatus.INVITED %}
|
{% if invitation.domain_invitation.status == invitation.domain_invitation.DomainInvitationStatus.INVITED %}
|
||||||
<form method="POST" action="{% url "invitation-cancel" pk=invitation.domain_invitation.id %}">
|
<form method="POST" action="{% url "invitation-cancel" domain_invitation_pk=invitation.domain_invitation.id %}">
|
||||||
{% csrf_token %}<input type="submit" class="usa-button--unstyled text-no-underline cursor-pointer" value="Cancel">
|
{% csrf_token %}<input type="submit" class="usa-button--unstyled text-no-underline cursor-pointer" value="Cancel">
|
||||||
</form>
|
</form>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
|
@ -1,10 +1,3 @@
|
||||||
"""Views for a single Domain.
|
|
||||||
|
|
||||||
Authorization is handled by the `DomainPermissionView`. To ensure that only
|
|
||||||
authorized users can see information on a domain, every view here should
|
|
||||||
inherit from `DomainPermissionView` (or DomainInvitationPermissionCancelView).
|
|
||||||
"""
|
|
||||||
|
|
||||||
from datetime import date
|
from datetime import date
|
||||||
import logging
|
import logging
|
||||||
import requests
|
import requests
|
||||||
|
@ -13,7 +6,7 @@ from django.contrib.messages.views import SuccessMessageMixin
|
||||||
from django.http import HttpResponseRedirect
|
from django.http import HttpResponseRedirect
|
||||||
from django.shortcuts import redirect, render, get_object_or_404
|
from django.shortcuts import redirect, render, get_object_or_404
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
from django.views.generic import DeleteView
|
from django.views.generic import DeleteView, DetailView, UpdateView
|
||||||
from django.views.generic.edit import FormMixin
|
from django.views.generic.edit import FormMixin
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from registrar.decorators import (
|
from registrar.decorators import (
|
||||||
|
@ -49,7 +42,6 @@ from registrar.utility.errors import (
|
||||||
SecurityEmailErrorCodes,
|
SecurityEmailErrorCodes,
|
||||||
)
|
)
|
||||||
from registrar.models.utility.contact_error import ContactError
|
from registrar.models.utility.contact_error import ContactError
|
||||||
from registrar.views.utility.permission_views import UserDomainRolePermissionDeleteView
|
|
||||||
from registrar.utility.waffle import flag_is_active_for_user
|
from registrar.utility.waffle import flag_is_active_for_user
|
||||||
from registrar.views.utility.invitation_helper import (
|
from registrar.views.utility.invitation_helper import (
|
||||||
get_org_membership,
|
get_org_membership,
|
||||||
|
@ -76,19 +68,22 @@ from epplibwrapper import (
|
||||||
|
|
||||||
from ..utility.email import send_templated_email, EmailSendingError
|
from ..utility.email import send_templated_email, EmailSendingError
|
||||||
from ..utility.email_invitations import send_domain_invitation_email, send_portfolio_invitation_email
|
from ..utility.email_invitations import send_domain_invitation_email, send_portfolio_invitation_email
|
||||||
from .utility import DomainPermissionView, DomainInvitationPermissionCancelView
|
|
||||||
from django import forms
|
from django import forms
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class DomainBaseView(DomainPermissionView):
|
class DomainBaseView(DetailView):
|
||||||
"""
|
"""
|
||||||
Base View for the Domain. Handles getting and setting the domain
|
Base View for the Domain. Handles getting and setting the domain
|
||||||
in session cache on GETs. Also provides methods for getting
|
in session cache on GETs. Also provides methods for getting
|
||||||
and setting the domain in cache
|
and setting the domain in cache
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
model = Domain
|
||||||
|
pk_url_kwarg = "domain_pk"
|
||||||
|
context_object_name = "domain"
|
||||||
|
|
||||||
def get(self, request, *args, **kwargs):
|
def get(self, request, *args, **kwargs):
|
||||||
self._get_domain(request)
|
self._get_domain(request)
|
||||||
context = self.get_context_data(object=self.object)
|
context = self.get_context_data(object=self.object)
|
||||||
|
@ -120,6 +115,134 @@ class DomainBaseView(DomainPermissionView):
|
||||||
domain_pk = "domain:" + str(self.kwargs.get("domain_pk"))
|
domain_pk = "domain:" + str(self.kwargs.get("domain_pk"))
|
||||||
self.session[domain_pk] = self.object
|
self.session[domain_pk] = self.object
|
||||||
|
|
||||||
|
def get_context_data(self, **kwargs):
|
||||||
|
context = super().get_context_data(**kwargs)
|
||||||
|
user = self.request.user
|
||||||
|
context["is_analyst_or_superuser"] = user.has_perm("registrar.analyst_access_permission") or user.has_perm(
|
||||||
|
"registrar.full_access_permission"
|
||||||
|
)
|
||||||
|
context["is_domain_manager"] = UserDomainRole.objects.filter(user=user, domain=self.object).exists()
|
||||||
|
context["is_portfolio_user"] = self.can_access_domain_via_portfolio(self.object.pk)
|
||||||
|
context["is_editable"] = self.is_editable()
|
||||||
|
# Stored in a variable for the linter
|
||||||
|
action = "analyst_action"
|
||||||
|
action_location = "analyst_action_location"
|
||||||
|
# Flag to see if an analyst is attempting to make edits
|
||||||
|
if action in self.request.session:
|
||||||
|
context[action] = self.request.session[action]
|
||||||
|
if action_location in self.request.session:
|
||||||
|
context[action_location] = self.request.session[action_location]
|
||||||
|
|
||||||
|
return context
|
||||||
|
|
||||||
|
def is_editable(self):
|
||||||
|
"""Returns whether domain is editable in the context of the view"""
|
||||||
|
domain_editable = self.object.is_editable()
|
||||||
|
if not domain_editable:
|
||||||
|
return False
|
||||||
|
|
||||||
|
# if user is domain manager or analyst or admin, return True
|
||||||
|
if (
|
||||||
|
self.can_access_other_user_domains(self.object.id)
|
||||||
|
or UserDomainRole.objects.filter(user=self.request.user, domain=self.object).exists()
|
||||||
|
):
|
||||||
|
return True
|
||||||
|
|
||||||
|
return False
|
||||||
|
|
||||||
|
def can_access_domain_via_portfolio(self, pk):
|
||||||
|
"""Most views should not allow permission to portfolio users.
|
||||||
|
If particular views allow access to the domain pages, they will need to override
|
||||||
|
this function.
|
||||||
|
"""
|
||||||
|
return False
|
||||||
|
|
||||||
|
def has_permission(self):
|
||||||
|
"""Check if this user has access to this domain.
|
||||||
|
|
||||||
|
The user is in self.request.user and the domain needs to be looked
|
||||||
|
up from the domain's primary key in self.kwargs["domain_pk"]
|
||||||
|
"""
|
||||||
|
pk = self.kwargs["domain_pk"]
|
||||||
|
|
||||||
|
# test if domain in editable state
|
||||||
|
if not self.in_editable_state(pk):
|
||||||
|
return False
|
||||||
|
|
||||||
|
# if we need to check more about the nature of role, do it here.
|
||||||
|
return True
|
||||||
|
|
||||||
|
def in_editable_state(self, pk):
|
||||||
|
"""Is the domain in an editable state"""
|
||||||
|
|
||||||
|
requested_domain = None
|
||||||
|
if Domain.objects.filter(id=pk).exists():
|
||||||
|
requested_domain = Domain.objects.get(id=pk)
|
||||||
|
|
||||||
|
# if domain is editable return true
|
||||||
|
if requested_domain and requested_domain.is_editable():
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
def can_access_other_user_domains(self, pk):
|
||||||
|
"""Checks to see if an authorized user (staff or superuser)
|
||||||
|
can access a domain that they did not create or was invited to.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Check if the user is permissioned...
|
||||||
|
user_is_analyst_or_superuser = self.request.user.has_perm(
|
||||||
|
"registrar.analyst_access_permission"
|
||||||
|
) or self.request.user.has_perm("registrar.full_access_permission")
|
||||||
|
|
||||||
|
if not user_is_analyst_or_superuser:
|
||||||
|
return False
|
||||||
|
|
||||||
|
# Check if the user is attempting a valid edit action.
|
||||||
|
# In other words, if the analyst/admin did not click
|
||||||
|
# the 'Manage Domain' button in /admin,
|
||||||
|
# then they cannot access this page.
|
||||||
|
session = self.request.session
|
||||||
|
can_do_action = (
|
||||||
|
"analyst_action" in session
|
||||||
|
and "analyst_action_location" in session
|
||||||
|
and session["analyst_action_location"] == pk
|
||||||
|
)
|
||||||
|
|
||||||
|
if not can_do_action:
|
||||||
|
return False
|
||||||
|
|
||||||
|
# Analysts may manage domains, when they are in these statuses:
|
||||||
|
valid_domain_statuses = [
|
||||||
|
DomainRequest.DomainRequestStatus.APPROVED,
|
||||||
|
DomainRequest.DomainRequestStatus.IN_REVIEW,
|
||||||
|
DomainRequest.DomainRequestStatus.REJECTED,
|
||||||
|
DomainRequest.DomainRequestStatus.ACTION_NEEDED,
|
||||||
|
# Edge case - some domains do not have
|
||||||
|
# a status or DomainInformation... aka a status of 'None'.
|
||||||
|
# It is necessary to access those to correct errors.
|
||||||
|
None,
|
||||||
|
]
|
||||||
|
|
||||||
|
requested_domain = None
|
||||||
|
if DomainInformation.objects.filter(id=pk).exists():
|
||||||
|
requested_domain = DomainInformation.objects.get(id=pk)
|
||||||
|
|
||||||
|
# if no domain information or domain request exist, the user
|
||||||
|
# should be able to manage the domain; however, if domain information
|
||||||
|
# and domain request exist, and domain request is not in valid status,
|
||||||
|
# user should not be able to manage domain
|
||||||
|
if (
|
||||||
|
requested_domain
|
||||||
|
and requested_domain.domain_request
|
||||||
|
and requested_domain.domain_request.status not in valid_domain_statuses
|
||||||
|
):
|
||||||
|
return False
|
||||||
|
|
||||||
|
# Valid session keys exist,
|
||||||
|
# the user is permissioned,
|
||||||
|
# and it is in a valid status
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
class DomainFormBaseView(DomainBaseView, FormMixin):
|
class DomainFormBaseView(DomainBaseView, FormMixin):
|
||||||
"""
|
"""
|
||||||
|
@ -433,7 +556,7 @@ class DomainOrgNameAddressView(DomainFormBaseView):
|
||||||
return super().has_permission()
|
return super().has_permission()
|
||||||
|
|
||||||
|
|
||||||
@grant_access(IS_PORTFOLIO_MEMBER_AND_DOMAIN_MANAGER)
|
@grant_access(IS_PORTFOLIO_MEMBER_AND_DOMAIN_MANAGER, IS_STAFF_MANAGING_DOMAIN)
|
||||||
class DomainSubOrganizationView(DomainFormBaseView):
|
class DomainSubOrganizationView(DomainFormBaseView):
|
||||||
"""Suborganization view"""
|
"""Suborganization view"""
|
||||||
|
|
||||||
|
@ -480,7 +603,7 @@ class DomainSubOrganizationView(DomainFormBaseView):
|
||||||
return super().form_valid(form)
|
return super().form_valid(form)
|
||||||
|
|
||||||
|
|
||||||
@grant_access(IS_DOMAIN_MANAGER_AND_NOT_PORTFOLIO_MEMBER)
|
@grant_access(IS_DOMAIN_MANAGER_AND_NOT_PORTFOLIO_MEMBER, IS_STAFF_MANAGING_DOMAIN)
|
||||||
class DomainSeniorOfficialView(DomainFormBaseView):
|
class DomainSeniorOfficialView(DomainFormBaseView):
|
||||||
"""Domain senior official editing view."""
|
"""Domain senior official editing view."""
|
||||||
|
|
||||||
|
@ -1307,8 +1430,9 @@ class DomainAddUserView(DomainFormBaseView):
|
||||||
|
|
||||||
|
|
||||||
@grant_access(IS_DOMAIN_MANAGER, IS_STAFF_MANAGING_DOMAIN)
|
@grant_access(IS_DOMAIN_MANAGER, IS_STAFF_MANAGING_DOMAIN)
|
||||||
class DomainInvitationCancelView(SuccessMessageMixin, DomainInvitationPermissionCancelView):
|
class DomainInvitationCancelView(SuccessMessageMixin, UpdateView):
|
||||||
object: DomainInvitation
|
model = DomainInvitation
|
||||||
|
pk_url_kwarg = "domain_invitation_pk"
|
||||||
fields = []
|
fields = []
|
||||||
|
|
||||||
def post(self, request, *args, **kwargs):
|
def post(self, request, *args, **kwargs):
|
||||||
|
|
|
@ -2,8 +2,6 @@ from .steps_helper import StepsHelper
|
||||||
from .always_404 import always_404
|
from .always_404 import always_404
|
||||||
|
|
||||||
from .permission_views import (
|
from .permission_views import (
|
||||||
DomainPermissionView,
|
|
||||||
PortfolioMembersPermission,
|
PortfolioMembersPermission,
|
||||||
DomainInvitationPermissionCancelView,
|
|
||||||
)
|
)
|
||||||
from .api_views import get_senior_official_from_federal_agency_json
|
from .api_views import get_senior_official_from_federal_agency_json
|
||||||
|
|
|
@ -1,14 +1,6 @@
|
||||||
"""Permissions-related mixin classes."""
|
"""Permissions-related mixin classes."""
|
||||||
|
|
||||||
from django.contrib.auth.mixins import PermissionRequiredMixin
|
from django.contrib.auth.mixins import PermissionRequiredMixin
|
||||||
|
|
||||||
from registrar.models import (
|
|
||||||
Domain,
|
|
||||||
DomainRequest,
|
|
||||||
DomainInvitation,
|
|
||||||
DomainInformation,
|
|
||||||
UserDomainRole,
|
|
||||||
)
|
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
|
|
||||||
|
@ -195,151 +187,6 @@ class PortfolioReportsPermission(PermissionsLoginMixin):
|
||||||
return self.request.user.is_org_user(self.request)
|
return self.request.user.is_org_user(self.request)
|
||||||
|
|
||||||
|
|
||||||
class DomainPermission(PermissionsLoginMixin):
|
|
||||||
"""Permission mixin that redirects to domain if user has access,
|
|
||||||
otherwise 403"""
|
|
||||||
|
|
||||||
def has_permission(self):
|
|
||||||
"""Check if this user has access to this domain.
|
|
||||||
|
|
||||||
The user is in self.request.user and the domain needs to be looked
|
|
||||||
up from the domain's primary key in self.kwargs["domain_pk"]
|
|
||||||
"""
|
|
||||||
pk = self.kwargs["domain_pk"]
|
|
||||||
|
|
||||||
# test if domain in editable state
|
|
||||||
if not self.in_editable_state(pk):
|
|
||||||
return False
|
|
||||||
|
|
||||||
# if we need to check more about the nature of role, do it here.
|
|
||||||
return True
|
|
||||||
|
|
||||||
def in_editable_state(self, pk):
|
|
||||||
"""Is the domain in an editable state"""
|
|
||||||
|
|
||||||
requested_domain = None
|
|
||||||
if Domain.objects.filter(id=pk).exists():
|
|
||||||
requested_domain = Domain.objects.get(id=pk)
|
|
||||||
|
|
||||||
# if domain is editable return true
|
|
||||||
if requested_domain and requested_domain.is_editable():
|
|
||||||
return True
|
|
||||||
return False
|
|
||||||
|
|
||||||
def can_access_other_user_domains(self, pk):
|
|
||||||
"""Checks to see if an authorized user (staff or superuser)
|
|
||||||
can access a domain that they did not create or was invited to.
|
|
||||||
"""
|
|
||||||
|
|
||||||
# Check if the user is permissioned...
|
|
||||||
user_is_analyst_or_superuser = self.request.user.has_perm(
|
|
||||||
"registrar.analyst_access_permission"
|
|
||||||
) or self.request.user.has_perm("registrar.full_access_permission")
|
|
||||||
|
|
||||||
if not user_is_analyst_or_superuser:
|
|
||||||
return False
|
|
||||||
|
|
||||||
# Check if the user is attempting a valid edit action.
|
|
||||||
# In other words, if the analyst/admin did not click
|
|
||||||
# the 'Manage Domain' button in /admin,
|
|
||||||
# then they cannot access this page.
|
|
||||||
session = self.request.session
|
|
||||||
can_do_action = (
|
|
||||||
"analyst_action" in session
|
|
||||||
and "analyst_action_location" in session
|
|
||||||
and session["analyst_action_location"] == pk
|
|
||||||
)
|
|
||||||
|
|
||||||
if not can_do_action:
|
|
||||||
return False
|
|
||||||
|
|
||||||
# Analysts may manage domains, when they are in these statuses:
|
|
||||||
valid_domain_statuses = [
|
|
||||||
DomainRequest.DomainRequestStatus.APPROVED,
|
|
||||||
DomainRequest.DomainRequestStatus.IN_REVIEW,
|
|
||||||
DomainRequest.DomainRequestStatus.REJECTED,
|
|
||||||
DomainRequest.DomainRequestStatus.ACTION_NEEDED,
|
|
||||||
# Edge case - some domains do not have
|
|
||||||
# a status or DomainInformation... aka a status of 'None'.
|
|
||||||
# It is necessary to access those to correct errors.
|
|
||||||
None,
|
|
||||||
]
|
|
||||||
|
|
||||||
requested_domain = None
|
|
||||||
if DomainInformation.objects.filter(id=pk).exists():
|
|
||||||
requested_domain = DomainInformation.objects.get(id=pk)
|
|
||||||
|
|
||||||
# if no domain information or domain request exist, the user
|
|
||||||
# should be able to manage the domain; however, if domain information
|
|
||||||
# and domain request exist, and domain request is not in valid status,
|
|
||||||
# user should not be able to manage domain
|
|
||||||
if (
|
|
||||||
requested_domain
|
|
||||||
and requested_domain.domain_request
|
|
||||||
and requested_domain.domain_request.status not in valid_domain_statuses
|
|
||||||
):
|
|
||||||
return False
|
|
||||||
|
|
||||||
# Valid session keys exist,
|
|
||||||
# the user is permissioned,
|
|
||||||
# and it is in a valid status
|
|
||||||
return True
|
|
||||||
|
|
||||||
|
|
||||||
class UserDeleteDomainRolePermission(PermissionsLoginMixin):
|
|
||||||
"""Permission mixin for UserDomainRole if user
|
|
||||||
has access, otherwise 403"""
|
|
||||||
|
|
||||||
def has_permission(self):
|
|
||||||
"""Check if this user has access to this domain request.
|
|
||||||
|
|
||||||
The user is in self.request.user and the domain needs to be looked
|
|
||||||
up from the domain's primary key in self.kwargs["pk"]
|
|
||||||
"""
|
|
||||||
domain_pk = self.kwargs["pk"]
|
|
||||||
user_pk = self.kwargs["user_pk"]
|
|
||||||
|
|
||||||
if not self.request.user.is_authenticated:
|
|
||||||
return False
|
|
||||||
|
|
||||||
# Check if the UserDomainRole object exists, then check
|
|
||||||
# if the user requesting the delete has permissions to do so
|
|
||||||
has_delete_permission = UserDomainRole.objects.filter(
|
|
||||||
user=user_pk,
|
|
||||||
domain=domain_pk,
|
|
||||||
domain__permissions__user=self.request.user,
|
|
||||||
).exists()
|
|
||||||
|
|
||||||
user_is_analyst_or_superuser = self.request.user.has_perm(
|
|
||||||
"registrar.analyst_access_permission"
|
|
||||||
) or self.request.user.has_perm("registrar.full_access_permission")
|
|
||||||
|
|
||||||
if not (has_delete_permission or user_is_analyst_or_superuser):
|
|
||||||
return False
|
|
||||||
|
|
||||||
return True
|
|
||||||
|
|
||||||
|
|
||||||
class DomainInvitationPermission(PermissionsLoginMixin):
|
|
||||||
"""Permission mixin that redirects to domain invitation if user has
|
|
||||||
access, otherwise 403"
|
|
||||||
|
|
||||||
A user has access to a domain invitation if they have a role on the
|
|
||||||
associated domain.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def has_permission(self):
|
|
||||||
"""Check if this user has a role on the domain of this invitation."""
|
|
||||||
if not self.request.user.is_authenticated:
|
|
||||||
return False
|
|
||||||
|
|
||||||
if not DomainInvitation.objects.filter(
|
|
||||||
id=self.kwargs["pk"], domain__permissions__user=self.request.user
|
|
||||||
).exists():
|
|
||||||
return False
|
|
||||||
return True
|
|
||||||
|
|
||||||
|
|
||||||
class UserProfilePermission(PermissionsLoginMixin):
|
class UserProfilePermission(PermissionsLoginMixin):
|
||||||
"""Permission mixin that redirects to user profile if user
|
"""Permission mixin that redirects to user profile if user
|
||||||
has access, otherwise 403"""
|
has access, otherwise 403"""
|
||||||
|
|
|
@ -2,18 +2,14 @@
|
||||||
|
|
||||||
import abc # abstract base class
|
import abc # abstract base class
|
||||||
|
|
||||||
from django.views.generic import DetailView, DeleteView, UpdateView
|
from django.views.generic import DetailView
|
||||||
from registrar.models import Domain, DomainInvitation, Portfolio
|
from registrar.models import Portfolio
|
||||||
from registrar.models.user import User
|
from registrar.models.user import User
|
||||||
from registrar.models.user_domain_role import UserDomainRole
|
|
||||||
|
|
||||||
from .mixins import (
|
from .mixins import (
|
||||||
DomainPermission,
|
|
||||||
DomainInvitationPermission,
|
|
||||||
PortfolioMemberDomainsPermission,
|
PortfolioMemberDomainsPermission,
|
||||||
PortfolioMemberDomainsEditPermission,
|
PortfolioMemberDomainsEditPermission,
|
||||||
PortfolioMemberEditPermission,
|
PortfolioMemberEditPermission,
|
||||||
UserDeleteDomainRolePermission,
|
|
||||||
UserProfilePermission,
|
UserProfilePermission,
|
||||||
PortfolioBasePermission,
|
PortfolioBasePermission,
|
||||||
PortfolioMembersPermission,
|
PortfolioMembersPermission,
|
||||||
|
@ -24,92 +20,6 @@ import logging
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class DomainPermissionView(DomainPermission, DetailView, abc.ABC):
|
|
||||||
"""Abstract base view for domains that enforces permissions.
|
|
||||||
|
|
||||||
This abstract view cannot be instantiated. Actual views must specify
|
|
||||||
`template_name`.
|
|
||||||
"""
|
|
||||||
|
|
||||||
# DetailView property for what model this is viewing
|
|
||||||
model = Domain
|
|
||||||
pk_url_kwarg = "domain_pk"
|
|
||||||
# variable name in template context for the model object
|
|
||||||
context_object_name = "domain"
|
|
||||||
|
|
||||||
# Adds context information for user permissions
|
|
||||||
def get_context_data(self, **kwargs):
|
|
||||||
context = super().get_context_data(**kwargs)
|
|
||||||
user = self.request.user
|
|
||||||
context["is_analyst_or_superuser"] = user.has_perm("registrar.analyst_access_permission") or user.has_perm(
|
|
||||||
"registrar.full_access_permission"
|
|
||||||
)
|
|
||||||
context["is_domain_manager"] = UserDomainRole.objects.filter(user=user, domain=self.object).exists()
|
|
||||||
context["is_portfolio_user"] = self.can_access_domain_via_portfolio(self.object.pk)
|
|
||||||
context["is_editable"] = self.is_editable()
|
|
||||||
# Stored in a variable for the linter
|
|
||||||
action = "analyst_action"
|
|
||||||
action_location = "analyst_action_location"
|
|
||||||
# Flag to see if an analyst is attempting to make edits
|
|
||||||
if action in self.request.session:
|
|
||||||
context[action] = self.request.session[action]
|
|
||||||
if action_location in self.request.session:
|
|
||||||
context[action_location] = self.request.session[action_location]
|
|
||||||
|
|
||||||
return context
|
|
||||||
|
|
||||||
def is_editable(self):
|
|
||||||
"""Returns whether domain is editable in the context of the view"""
|
|
||||||
domain_editable = self.object.is_editable()
|
|
||||||
if not domain_editable:
|
|
||||||
return False
|
|
||||||
|
|
||||||
# if user is domain manager or analyst or admin, return True
|
|
||||||
if (
|
|
||||||
self.can_access_other_user_domains(self.object.id)
|
|
||||||
or UserDomainRole.objects.filter(user=self.request.user, domain=self.object).exists()
|
|
||||||
):
|
|
||||||
return True
|
|
||||||
|
|
||||||
return False
|
|
||||||
|
|
||||||
def can_access_domain_via_portfolio(self, pk):
|
|
||||||
"""Most views should not allow permission to portfolio users.
|
|
||||||
If particular views allow access to the domain pages, they will need to override
|
|
||||||
this function.
|
|
||||||
"""
|
|
||||||
return False
|
|
||||||
|
|
||||||
# Abstract property enforces NotImplementedError on an attribute.
|
|
||||||
@property
|
|
||||||
@abc.abstractmethod
|
|
||||||
def template_name(self):
|
|
||||||
raise NotImplementedError
|
|
||||||
|
|
||||||
|
|
||||||
class DomainInvitationPermissionCancelView(DomainInvitationPermission, UpdateView, abc.ABC):
|
|
||||||
"""Abstract view for cancelling a DomainInvitation."""
|
|
||||||
|
|
||||||
model = DomainInvitation
|
|
||||||
object: DomainInvitation
|
|
||||||
|
|
||||||
|
|
||||||
class UserDomainRolePermissionDeleteView(UserDeleteDomainRolePermission, DeleteView, abc.ABC):
|
|
||||||
"""Abstract base view for deleting a UserDomainRole.
|
|
||||||
|
|
||||||
This abstract view cannot be instantiated. Actual views must specify
|
|
||||||
`template_name`.
|
|
||||||
"""
|
|
||||||
|
|
||||||
# DetailView property for what model this is viewing
|
|
||||||
model = UserDomainRole
|
|
||||||
# workaround for type mismatch in DeleteView
|
|
||||||
object: UserDomainRole
|
|
||||||
|
|
||||||
# variable name in template context for the model object
|
|
||||||
context_object_name = "userdomainrole"
|
|
||||||
|
|
||||||
|
|
||||||
class UserProfilePermissionView(UserProfilePermission, DetailView, abc.ABC):
|
class UserProfilePermissionView(UserProfilePermission, DetailView, abc.ABC):
|
||||||
"""Abstract base view for user profile view that enforces permissions.
|
"""Abstract base view for user profile view that enforces permissions.
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue