mirror of
https://github.com/cisagov/manage.get.gov.git
synced 2025-07-30 14:36:32 +02:00
220 lines
9.4 KiB
Python
220 lines
9.4 KiB
Python
from registrar.utility import StrEnum
|
||
from django.db import models
|
||
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
|
||
|
||
|
||
class UserPortfolioRoleChoices(models.TextChoices):
|
||
"""
|
||
Roles make it easier for admins to look at
|
||
"""
|
||
|
||
ORGANIZATION_ADMIN = "organization_admin", "Admin"
|
||
ORGANIZATION_MEMBER = "organization_member", "Member"
|
||
|
||
@classmethod
|
||
def get_user_portfolio_role_label(cls, user_portfolio_role):
|
||
return cls(user_portfolio_role).label if user_portfolio_role else None
|
||
|
||
@classmethod
|
||
def get_role_description(cls, user_portfolio_role):
|
||
"""Returns a detailed description for a given role."""
|
||
descriptions = {
|
||
cls.ORGANIZATION_ADMIN: (
|
||
"Grants this member access to the organization-wide information "
|
||
"on domains, domain requests, and members. Domain management can be assigned separately."
|
||
),
|
||
cls.ORGANIZATION_MEMBER: (
|
||
"Grants this member access to the organization. They can be given extra permissions to view all "
|
||
"organization domain requests and submit domain requests on behalf of the organization. Basic access "
|
||
"members can’t view all members of an organization or manage them. "
|
||
"Domain management can be assigned separately."
|
||
)
|
||
}
|
||
return descriptions.get(user_portfolio_role)
|
||
|
||
|
||
class UserPortfolioPermissionChoices(models.TextChoices):
|
||
""" """
|
||
|
||
VIEW_ALL_DOMAINS = "view_all_domains", "View all domains and domain reports"
|
||
VIEW_MANAGED_DOMAINS = "view_managed_domains", "View managed domains"
|
||
|
||
VIEW_MEMBERS = "view_members", "View members"
|
||
EDIT_MEMBERS = "edit_members", "Create and edit members"
|
||
|
||
VIEW_ALL_REQUESTS = "view_all_requests", "View all requests"
|
||
EDIT_REQUESTS = "edit_requests", "Create and edit requests"
|
||
|
||
VIEW_PORTFOLIO = "view_portfolio", "View organization"
|
||
EDIT_PORTFOLIO = "edit_portfolio", "Edit organization"
|
||
|
||
# Domain: field specific permissions
|
||
VIEW_SUBORGANIZATION = "view_suborganization", "View suborganization"
|
||
EDIT_SUBORGANIZATION = "edit_suborganization", "Edit suborganization"
|
||
|
||
@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 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)
|
||
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.exclude(id=portfolio_invitation.id).filter(
|
||
email=portfolio_invitation.email
|
||
)
|
||
|
||
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."
|
||
)
|