mirror of
https://github.com/cisagov/manage.get.gov.git
synced 2025-07-26 04:28:39 +02:00
401 lines
16 KiB
Python
401 lines
16 KiB
Python
from registrar.utility import StrEnum
|
|
from django.db import models
|
|
from django.db.models import Q
|
|
from django.apps import apps
|
|
from django.forms import ValidationError
|
|
from registrar.utility.waffle import flag_is_active_for_user
|
|
from django.contrib.auth import get_user_model
|
|
import logging
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
class UserPortfolioRoleChoices(models.TextChoices):
|
|
"""
|
|
Roles make it easier for admins to look at
|
|
"""
|
|
|
|
ORGANIZATION_ADMIN = "organization_admin", "Admin"
|
|
ORGANIZATION_MEMBER = "organization_member", "Basic"
|
|
|
|
@classmethod
|
|
def get_user_portfolio_role_label(cls, user_portfolio_role):
|
|
try:
|
|
return cls(user_portfolio_role).label if user_portfolio_role else None
|
|
except ValueError:
|
|
logger.warning(f"Invalid portfolio role: {user_portfolio_role}")
|
|
return f"Unknown ({user_portfolio_role})"
|
|
|
|
|
|
class UserPortfolioPermissionChoices(models.TextChoices):
|
|
""" """
|
|
|
|
VIEW_ALL_DOMAINS = "view_all_domains", "Viewer"
|
|
VIEW_MANAGED_DOMAINS = "view_managed_domains", "Viewer, limited (domains they manage)"
|
|
|
|
VIEW_MEMBERS = "view_members", "Viewer"
|
|
EDIT_MEMBERS = "edit_members", "Manager"
|
|
|
|
VIEW_ALL_REQUESTS = "view_all_requests", "Viewer"
|
|
EDIT_REQUESTS = "edit_requests", "Creator"
|
|
|
|
VIEW_PORTFOLIO = "view_portfolio", "Viewer"
|
|
EDIT_PORTFOLIO = "edit_portfolio", "Manager"
|
|
|
|
@classmethod
|
|
def get_user_portfolio_permission_label(cls, user_portfolio_permission):
|
|
return cls(user_portfolio_permission).label if user_portfolio_permission else None
|
|
|
|
@classmethod
|
|
def to_dict(cls):
|
|
return {key: value.value for key, value in cls.__members__.items()}
|
|
|
|
|
|
class DomainRequestPermissionDisplay(StrEnum):
|
|
"""Stores display values for domain request permission combinations.
|
|
|
|
Overview of values:
|
|
- VIEWER_REQUESTER: "Viewer Requester"
|
|
- VIEWER: "Viewer"
|
|
- NONE: "None"
|
|
"""
|
|
|
|
VIEWER_REQUESTER = "Viewer Requester"
|
|
VIEWER = "Viewer"
|
|
NONE = "None"
|
|
|
|
|
|
class MemberPermissionDisplay(StrEnum):
|
|
"""Stores display values for member permission combinations.
|
|
|
|
Overview of values:
|
|
- MANAGER: "Manager"
|
|
- VIEWER: "Viewer"
|
|
- NONE: "None"
|
|
"""
|
|
|
|
MANAGER = "Manager"
|
|
VIEWER = "Viewer"
|
|
NONE = "None"
|
|
|
|
|
|
def get_readable_roles(roles):
|
|
readable_roles = []
|
|
if roles:
|
|
readable_roles = sorted([UserPortfolioRoleChoices.get_user_portfolio_role_label(role) for role in roles])
|
|
return readable_roles
|
|
|
|
|
|
def get_role_display(roles):
|
|
"""
|
|
Returns a user-friendly display name for a given list of user roles.
|
|
|
|
- If the user has the ORGANIZATION_ADMIN role, return "Admin".
|
|
- If the user has the ORGANIZATION_MEMBER role, return "Basic".
|
|
- If the user has neither role, return "-".
|
|
|
|
Args:
|
|
roles (list): A list of role strings assigned to the user.
|
|
|
|
Returns:
|
|
str: The display name for the highest applicable role.
|
|
"""
|
|
if UserPortfolioRoleChoices.ORGANIZATION_ADMIN in roles:
|
|
return "Admin"
|
|
elif UserPortfolioRoleChoices.ORGANIZATION_MEMBER in roles:
|
|
return "Basic"
|
|
else:
|
|
return "-"
|
|
|
|
|
|
def get_domains_display(roles, permissions):
|
|
"""
|
|
Determines the display name for a user's domain viewing permissions.
|
|
|
|
- If the user has the VIEW_ALL_DOMAINS permission, return "Viewer".
|
|
- Otherwise, return "Viewer, limited".
|
|
|
|
Args:
|
|
roles (list): A list of role strings assigned to the user.
|
|
permissions (list): A list of additional permissions assigned to the user.
|
|
|
|
Returns:
|
|
str: A string representing the user's domain viewing access.
|
|
"""
|
|
UserPortfolioPermission = apps.get_model("registrar.UserPortfolioPermission")
|
|
all_permissions = UserPortfolioPermission.get_portfolio_permissions(roles, permissions)
|
|
if UserPortfolioPermissionChoices.VIEW_ALL_DOMAINS in all_permissions:
|
|
return "Viewer"
|
|
else:
|
|
return "Viewer, limited"
|
|
|
|
|
|
def get_domains_description_display(roles, permissions):
|
|
"""
|
|
Determines the display description for a user's domain viewing permissions.
|
|
|
|
Args:
|
|
roles (list): A list of role strings assigned to the user.
|
|
permissions (list): A list of additional permissions assigned to the user.
|
|
|
|
Returns:
|
|
str: A string representing the user's domain viewing access description.
|
|
"""
|
|
UserPortfolioPermission = apps.get_model("registrar.UserPortfolioPermission")
|
|
all_permissions = UserPortfolioPermission.get_portfolio_permissions(roles, permissions)
|
|
if UserPortfolioPermissionChoices.VIEW_ALL_DOMAINS in all_permissions:
|
|
return "Can view all domains for the organization"
|
|
else:
|
|
return "Can view only the domains they manage"
|
|
|
|
|
|
def get_domain_requests_display(roles, permissions):
|
|
"""
|
|
Determines the display name for a user's domain request permissions.
|
|
|
|
- If the user has the EDIT_REQUESTS permission, return "Creator".
|
|
- If the user has the VIEW_ALL_REQUESTS permission, return "Viewer".
|
|
- Otherwise, return "No access".
|
|
|
|
Args:
|
|
roles (list): A list of role strings assigned to the user.
|
|
permissions (list): A list of additional permissions assigned to the user.
|
|
|
|
Returns:
|
|
str: A string representing the user's domain request access level.
|
|
"""
|
|
UserPortfolioPermission = apps.get_model("registrar.UserPortfolioPermission")
|
|
all_permissions = UserPortfolioPermission.get_portfolio_permissions(roles, permissions)
|
|
if UserPortfolioPermissionChoices.EDIT_REQUESTS in all_permissions:
|
|
return "Creator"
|
|
elif UserPortfolioPermissionChoices.VIEW_ALL_REQUESTS in all_permissions:
|
|
return "Viewer"
|
|
else:
|
|
return "No access"
|
|
|
|
|
|
def get_domain_requests_description_display(roles, permissions):
|
|
"""
|
|
Determines the display description for a user's domain request permissions.
|
|
|
|
Args:
|
|
roles (list): A list of role strings assigned to the user.
|
|
permissions (list): A list of additional permissions assigned to the user.
|
|
|
|
Returns:
|
|
str: A string representing the user's domain request access level description.
|
|
"""
|
|
UserPortfolioPermission = apps.get_model("registrar.UserPortfolioPermission")
|
|
all_permissions = UserPortfolioPermission.get_portfolio_permissions(roles, permissions)
|
|
if UserPortfolioPermissionChoices.EDIT_REQUESTS in all_permissions:
|
|
return "Can view all domain requests for the organization and create requests"
|
|
elif UserPortfolioPermissionChoices.VIEW_ALL_REQUESTS in all_permissions:
|
|
return "Can view all domain requests for the organization"
|
|
else:
|
|
return "Cannot view or create domain requests"
|
|
|
|
|
|
def get_members_display(roles, permissions):
|
|
"""
|
|
Determines the display name for a user's member management permissions.
|
|
|
|
- If the user has the EDIT_MEMBERS permission, return "Manager".
|
|
- If the user has the VIEW_MEMBERS permission, return "Viewer".
|
|
- Otherwise, return "No access".
|
|
|
|
Args:
|
|
roles (list): A list of role strings assigned to the user.
|
|
permissions (list): A list of additional permissions assigned to the user.
|
|
|
|
Returns:
|
|
str: A string representing the user's member management access level.
|
|
"""
|
|
UserPortfolioPermission = apps.get_model("registrar.UserPortfolioPermission")
|
|
all_permissions = UserPortfolioPermission.get_portfolio_permissions(roles, permissions)
|
|
if UserPortfolioPermissionChoices.EDIT_MEMBERS in all_permissions:
|
|
return "Manager"
|
|
elif UserPortfolioPermissionChoices.VIEW_MEMBERS in all_permissions:
|
|
return "Viewer"
|
|
else:
|
|
return "No access"
|
|
|
|
|
|
def get_members_description_display(roles, permissions):
|
|
"""
|
|
Determines the display description for a user's member management permissions.
|
|
|
|
Args:
|
|
roles (list): A list of role strings assigned to the user.
|
|
permissions (list): A list of additional permissions assigned to the user.
|
|
|
|
Returns:
|
|
str: A string representing the user's member management access level description.
|
|
"""
|
|
UserPortfolioPermission = apps.get_model("registrar.UserPortfolioPermission")
|
|
all_permissions = UserPortfolioPermission.get_portfolio_permissions(roles, permissions)
|
|
if UserPortfolioPermissionChoices.EDIT_MEMBERS in all_permissions:
|
|
return "Can view and manage all member permissions"
|
|
elif UserPortfolioPermissionChoices.VIEW_MEMBERS in all_permissions:
|
|
return "Can view all member permissions"
|
|
else:
|
|
return "Cannot view member permissions"
|
|
|
|
|
|
def validate_user_portfolio_permission(user_portfolio_permission):
|
|
"""
|
|
Validates a UserPortfolioPermission instance. Located in portfolio_helper to avoid circular imports
|
|
between PortfolioInvitation and UserPortfolioPermission models.
|
|
|
|
Used in UserPortfolioPermission.clean() for model validation.
|
|
|
|
Validates:
|
|
1. A portfolio must be assigned if roles or additional permissions are specified, and vice versa.
|
|
2. Assigned roles do not include any forbidden permissions.
|
|
3. If the 'multiple_portfolios' flag is inactive for the user,
|
|
they must not have existing portfolio permissions or invitations.
|
|
|
|
Raises:
|
|
ValidationError: If any of the validation rules are violated.
|
|
"""
|
|
PortfolioInvitation = apps.get_model("registrar.PortfolioInvitation")
|
|
UserPortfolioPermission = apps.get_model("registrar.UserPortfolioPermission")
|
|
|
|
has_portfolio = bool(user_portfolio_permission.portfolio_id)
|
|
portfolio_permissions = set(user_portfolio_permission._get_portfolio_permissions())
|
|
|
|
# == Validate required fields == #
|
|
if not has_portfolio and portfolio_permissions:
|
|
raise ValidationError("When portfolio roles or additional permissions are assigned, portfolio is required.")
|
|
|
|
if has_portfolio and not portfolio_permissions:
|
|
raise ValidationError("When portfolio is assigned, portfolio roles or additional permissions are required.")
|
|
|
|
# == Validate role permissions. Compares existing permissions to forbidden ones. == #
|
|
roles = user_portfolio_permission.roles if user_portfolio_permission.roles is not None else []
|
|
bad_perms = user_portfolio_permission.get_forbidden_permissions(
|
|
roles, user_portfolio_permission.additional_permissions
|
|
)
|
|
if bad_perms:
|
|
readable_perms = [
|
|
UserPortfolioPermissionChoices.get_user_portfolio_permission_label(perm) for perm in bad_perms
|
|
]
|
|
readable_roles = [UserPortfolioRoleChoices.get_user_portfolio_role_label(role) for role in roles]
|
|
raise ValidationError(
|
|
f"These permissions cannot be assigned to {', '.join(readable_roles)}: <{', '.join(readable_perms)}>"
|
|
)
|
|
|
|
# == Validate the multiple_porfolios flag. == #
|
|
if not flag_is_active_for_user(user_portfolio_permission.user, "multiple_portfolios"):
|
|
existing_permissions = UserPortfolioPermission.objects.exclude(id=user_portfolio_permission.id).filter(
|
|
user=user_portfolio_permission.user
|
|
)
|
|
if existing_permissions.exists():
|
|
raise ValidationError(
|
|
"This user is already assigned to a portfolio. "
|
|
"Based on current waffle flag settings, users cannot be assigned to multiple portfolios."
|
|
)
|
|
|
|
existing_invitations = PortfolioInvitation.objects.filter(email=user_portfolio_permission.user.email).exclude(
|
|
Q(portfolio=user_portfolio_permission.portfolio)
|
|
| Q(status=PortfolioInvitation.PortfolioInvitationStatus.RETRIEVED)
|
|
)
|
|
if existing_invitations.exists():
|
|
raise ValidationError(
|
|
"This user is already assigned to a portfolio invitation. "
|
|
"Based on current waffle flag settings, users cannot be assigned to multiple portfolios."
|
|
)
|
|
|
|
|
|
def validate_portfolio_invitation(portfolio_invitation):
|
|
"""
|
|
Validates a PortfolioInvitation instance. Located in portfolio_helper to avoid circular imports
|
|
between PortfolioInvitation and UserPortfolioPermission models.
|
|
|
|
Used in PortfolioInvitation.clean() for model validation.
|
|
|
|
Validates:
|
|
1. A portfolio must be assigned if roles or additional permissions are specified, and vice versa.
|
|
2. Assigned roles do not include any forbidden permissions.
|
|
3. If the 'multiple_portfolios' flag is inactive for the user,
|
|
they must not have existing portfolio permissions or invitations.
|
|
|
|
Raises:
|
|
ValidationError: If any of the validation rules are violated.
|
|
"""
|
|
PortfolioInvitation = apps.get_model("registrar.PortfolioInvitation")
|
|
UserPortfolioPermission = apps.get_model("registrar.UserPortfolioPermission")
|
|
User = get_user_model()
|
|
|
|
has_portfolio = bool(portfolio_invitation.portfolio_id)
|
|
portfolio_permissions = set(portfolio_invitation.get_portfolio_permissions())
|
|
|
|
# == Validate required fields == #
|
|
if not has_portfolio and portfolio_permissions:
|
|
raise ValidationError("When portfolio roles or additional permissions are assigned, portfolio is required.")
|
|
|
|
if has_portfolio and not portfolio_permissions:
|
|
raise ValidationError("When portfolio is assigned, portfolio roles or additional permissions are required.")
|
|
|
|
# == Validate role permissions. Compares existing permissions to forbidden ones. == #
|
|
roles = portfolio_invitation.roles if portfolio_invitation.roles is not None else []
|
|
bad_perms = UserPortfolioPermission.get_forbidden_permissions(roles, portfolio_invitation.additional_permissions)
|
|
if bad_perms:
|
|
readable_perms = [
|
|
UserPortfolioPermissionChoices.get_user_portfolio_permission_label(perm) for perm in bad_perms
|
|
]
|
|
readable_roles = [UserPortfolioRoleChoices.get_user_portfolio_role_label(role) for role in roles]
|
|
raise ValidationError(
|
|
f"These permissions cannot be assigned to {', '.join(readable_roles)}: <{', '.join(readable_perms)}>"
|
|
)
|
|
|
|
# == Validate the multiple_porfolios flag. == #
|
|
user = User.objects.filter(email=portfolio_invitation.email).first()
|
|
# If user returns None, then we check for global assignment of multiple_portfolios.
|
|
# Otherwise we just check on the user.
|
|
if not flag_is_active_for_user(user, "multiple_portfolios"):
|
|
existing_permissions = UserPortfolioPermission.objects.filter(user=user)
|
|
|
|
existing_invitations = PortfolioInvitation.objects.filter(email=portfolio_invitation.email).exclude(
|
|
Q(id=portfolio_invitation.id) | Q(status=PortfolioInvitation.PortfolioInvitationStatus.RETRIEVED)
|
|
)
|
|
|
|
if existing_permissions.exists():
|
|
raise ValidationError(
|
|
"This user is already assigned to a portfolio. "
|
|
"Based on current waffle flag settings, users cannot be assigned to multiple portfolios."
|
|
)
|
|
|
|
if existing_invitations.exists():
|
|
raise ValidationError(
|
|
"This user is already assigned to a portfolio invitation. "
|
|
"Based on current waffle flag settings, users cannot be assigned to multiple portfolios."
|
|
)
|
|
|
|
|
|
def cleanup_after_portfolio_member_deletion(portfolio, email, user=None):
|
|
"""
|
|
Cleans up after removing a portfolio member or a portfolio invitation.
|
|
|
|
Args:
|
|
portfolio: portfolio
|
|
user: passed when removing a portfolio member.
|
|
email: passed when removing a portfolio invitation, or passed as user.email
|
|
when removing a portfolio member.
|
|
"""
|
|
|
|
DomainInvitation = apps.get_model("registrar.DomainInvitation")
|
|
UserDomainRole = apps.get_model("registrar.UserDomainRole")
|
|
|
|
# Fetch domain invitations matching the criteria
|
|
invitations = DomainInvitation.objects.filter(
|
|
email=email, domain__domain_info__portfolio=portfolio, status=DomainInvitation.DomainInvitationStatus.INVITED
|
|
)
|
|
|
|
# Call `cancel_invitation` on each invitation
|
|
for invitation in invitations:
|
|
invitation.cancel_invitation()
|
|
invitation.save()
|
|
|
|
if user:
|
|
# Remove user's domain roles for the current portfolio
|
|
UserDomainRole.objects.filter(user=user, domain__domain_info__portfolio=portfolio).delete()
|