diff --git a/src/registrar/assets/src/js/getgov/helpers.js b/src/registrar/assets/src/js/getgov/helpers.js index 7d1449bac..08be011c2 100644 --- a/src/registrar/assets/src/js/getgov/helpers.js +++ b/src/registrar/assets/src/js/getgov/helpers.js @@ -96,3 +96,14 @@ export function submitForm(form_id) { console.error("Form '" + form_id + "' not found."); } } + +/** + * Helper function to strip HTML tags + * THIS IS NOT SUITABLE FOR SANITIZING DANGEROUS STRINGS + */ +export function unsafeStripHtmlTags(input) { + const tempDiv = document.createElement("div"); + // NOTE: THIS IS NOT SUITABLE FOR SANITIZING DANGEROUS STRINGS + tempDiv.innerHTML = input; + return tempDiv.textContent || tempDiv.innerText || ""; +} diff --git a/src/registrar/assets/src/js/getgov/portfolio-member-page.js b/src/registrar/assets/src/js/getgov/portfolio-member-page.js index 95723fc7e..96961e5dc 100644 --- a/src/registrar/assets/src/js/getgov/portfolio-member-page.js +++ b/src/registrar/assets/src/js/getgov/portfolio-member-page.js @@ -18,7 +18,7 @@ export function initPortfolioNewMemberPageToggle() { const unique_id = `${member_type}-${member_id}`; let cancelInvitationButton = member_type === "invitedmember" ? "Cancel invitation" : "Remove member"; - wrapperDeleteAction.innerHTML = generateKebabHTML('remove-member', unique_id, cancelInvitationButton, `More Options for ${member_name}`); + wrapperDeleteAction.innerHTML = generateKebabHTML('remove-member', unique_id, cancelInvitationButton, `More Options for ${member_name}`, "usa-icon--large"); // This easter egg is only for fixtures that dont have names as we are displaying their emails // All prod users will have emails linked to their account @@ -100,8 +100,8 @@ export function initAddNewMemberPageListeners() { const permissionSections = document.querySelectorAll(`#${permission_details_div_id} > h3`); permissionSections.forEach(section => { - // Find the
This member is assigned to ${num_domains} domain${num_domains > 1 ? 's' : ''}:
`; + domainsHTML += `This member is assigned to ${num_domains} domain${num_domains > 1 ? 's' : ''}:
`; domainsHTML += "This member is assigned to 0 domains.
`; + } - // If there are more than 6 domains, display a "View assigned domains" link - domainsHTML += ``; + // If there are more than 6 domains, display a "View assigned domains" link + domainsHTML += ``; - domainsHTML += "Member access: Admin
`; + } else { + permissionsHTML += `Member access: Basic
`; + } // Check domain-related permissions if (member_permissions.includes(UserPortfolioPermissionChoices.VIEW_ALL_DOMAINS)) { - permissionsHTML += `Domains: Can view all organization domains. Can manage domains they are assigned to and edit information about the domain (including DNS settings).
`; + permissionsHTML += `Domains: Viewer
`; } else if (member_permissions.includes(UserPortfolioPermissionChoices.VIEW_MANAGED_DOMAINS)) { - permissionsHTML += `Domains: Can manage domains they are assigned to and edit information about the domain (including DNS settings).
`; + permissionsHTML += `Domains: Viewer, limited
`; } // Check request-related permissions if (member_permissions.includes(UserPortfolioPermissionChoices.EDIT_REQUESTS)) { - permissionsHTML += `Domain requests: Can view all organization domain requests. Can create domain requests and modify their own requests.
`; + permissionsHTML += `Domain requests: Creator
`; } else if (member_permissions.includes(UserPortfolioPermissionChoices.VIEW_ALL_REQUESTS)) { - permissionsHTML += `Domain requests (view-only): Can view all organization domain requests. Can't create or modify any domain requests.
`; + permissionsHTML += `Domain requests: Viewer
`; + } else { + permissionsHTML += `Domain requests: No access
`; } // Check member-related permissions if (member_permissions.includes(UserPortfolioPermissionChoices.EDIT_MEMBERS)) { - permissionsHTML += `Members: Can manage members including inviting new members, removing current members, and assigning domains to members.
`; + permissionsHTML += `Members: Manager
`; } else if (member_permissions.includes(UserPortfolioPermissionChoices.VIEW_MEMBERS)) { - permissionsHTML += `Members (view-only): Can view all organizational members. Can't manage any members.
`; + permissionsHTML += `Members: Viewer
`; + } else { + permissionsHTML += `Members: No access
`; } // If no specific permissions are assigned, display a message indicating no additional permissions if (!permissionsHTML) { - permissionsHTML += `No additional permissions: There are no additional permissions for this member.
`; + permissionsHTML += `No additional permissions: There are no additional permissions for this member.
`; } // Add a permissions header and wrap the entire output in a container - permissionsHTML = `description beneath each option self.fields["domain_permissions"].descriptions = { - UserPortfolioPermissionChoices.VIEW_MANAGED_DOMAINS.value: "Can view only the domains they manage", - UserPortfolioPermissionChoices.VIEW_ALL_DOMAINS.value: "Can view all domains for the organization", + UserPortfolioPermissionChoices.VIEW_MANAGED_DOMAINS.value: get_domains_description_display( + UserPortfolioRoleChoices.ORGANIZATION_MEMBER, None + ), + UserPortfolioPermissionChoices.VIEW_ALL_DOMAINS.value: get_domains_description_display( + UserPortfolioRoleChoices.ORGANIZATION_MEMBER, [UserPortfolioPermissionChoices.VIEW_ALL_DOMAINS] + ), } self.fields["domain_request_permissions"].descriptions = { UserPortfolioPermissionChoices.EDIT_REQUESTS.value: ( - "Can view all domain requests for the organization and create requests" + get_domain_requests_description_display( + UserPortfolioRoleChoices.ORGANIZATION_MEMBER, [UserPortfolioPermissionChoices.EDIT_REQUESTS] + ) + ), + UserPortfolioPermissionChoices.VIEW_ALL_REQUESTS.value: ( + get_domain_requests_description_display( + UserPortfolioRoleChoices.ORGANIZATION_MEMBER, [UserPortfolioPermissionChoices.VIEW_ALL_REQUESTS] + ) ), - UserPortfolioPermissionChoices.VIEW_ALL_REQUESTS.value: "Can view all domain requests for the organization", - "no_access": "Cannot view or create domain requests", + "no_access": get_domain_requests_description_display(UserPortfolioRoleChoices.ORGANIZATION_MEMBER, None), } self.fields["member_permissions"].descriptions = { - UserPortfolioPermissionChoices.VIEW_MEMBERS.value: "Can view all members permissions", - "no_access": "Cannot view member permissions", + UserPortfolioPermissionChoices.VIEW_MEMBERS.value: get_members_description_display( + UserPortfolioRoleChoices.ORGANIZATION_MEMBER, [UserPortfolioPermissionChoices.VIEW_MEMBERS] + ), + "no_access": get_members_description_display(UserPortfolioRoleChoices.ORGANIZATION_MEMBER, None), } # Map model instance values to custom form fields @@ -218,6 +262,9 @@ def clean(self): cleaned_data = super().clean() role = cleaned_data.get("role") + # handle role + cleaned_data["roles"] = [role] if role else [] + # Get required fields for the selected role. Then validate all required fields for the role. required_fields = self.ROLE_REQUIRED_FIELDS.get(role, []) for field_name in required_fields: @@ -236,9 +283,6 @@ def clean(self): if cleaned_data.get("member_permissions") == "no_access": cleaned_data["member_permissions"] = None - # Handle roles - cleaned_data["roles"] = [role] - # Handle additional_permissions valid_fields = self.ROLE_REQUIRED_FIELDS.get(role, []) additional_permissions = {cleaned_data.get(field) for field in valid_fields if cleaned_data.get(field)} diff --git a/src/registrar/models/portfolio_invitation.py b/src/registrar/models/portfolio_invitation.py index 99febc92e..fafa99856 100644 --- a/src/registrar/models/portfolio_invitation.py +++ b/src/registrar/models/portfolio_invitation.py @@ -9,8 +9,11 @@ UserPortfolioPermissionChoices, UserPortfolioRoleChoices, cleanup_after_portfolio_member_deletion, + get_domain_requests_description_display, get_domain_requests_display, + get_domains_description_display, get_domains_display, + get_members_description_display, get_members_display, get_role_display, validate_portfolio_invitation, @@ -115,6 +118,16 @@ def domains_display(self): """ return get_domains_display(self.roles, self.additional_permissions) + @property + def domains_description_display(self): + """ + Returns a string description of the user's domain access level. + + Returns: + str: The display name of the user's domain permissions description. + """ + return get_domains_description_display(self.roles, self.additional_permissions) + @property def domain_requests_display(self): """ @@ -129,6 +142,16 @@ def domain_requests_display(self): """ return get_domain_requests_display(self.roles, self.additional_permissions) + @property + def domain_requests_description_display(self): + """ + Returns a string description of the user's access to domain requests. + + Returns: + str: The display name of the user's domain request permissions description. + """ + return get_domain_requests_description_display(self.roles, self.additional_permissions) + @property def members_display(self): """ @@ -143,6 +166,16 @@ def members_display(self): """ return get_members_display(self.roles, self.additional_permissions) + @property + def members_description_display(self): + """ + Returns a string description of the user's access to managing members. + + Returns: + str: The display name of the user's member management permissions description. + """ + return get_members_description_display(self.roles, self.additional_permissions) + @transition(field="status", source=PortfolioInvitationStatus.INVITED, target=PortfolioInvitationStatus.RETRIEVED) def retrieve(self): """When an invitation is retrieved, create the corresponding permission. diff --git a/src/registrar/models/user_portfolio_permission.py b/src/registrar/models/user_portfolio_permission.py index e077daa57..0a758ff6a 100644 --- a/src/registrar/models/user_portfolio_permission.py +++ b/src/registrar/models/user_portfolio_permission.py @@ -7,8 +7,11 @@ MemberPermissionDisplay, cleanup_after_portfolio_member_deletion, get_domain_requests_display, + get_domain_requests_description_display, get_domains_display, + get_domains_description_display, get_members_display, + get_members_description_display, get_role_display, validate_user_portfolio_permission, ) @@ -211,6 +214,16 @@ def domains_display(self): """ return get_domains_display(self.roles, self.additional_permissions) + @property + def domains_description_display(self): + """ + Returns a string description of the user's domain access level. + + Returns: + str: The display name of the user's domain permissions description. + """ + return get_domains_description_display(self.roles, self.additional_permissions) + @property def domain_requests_display(self): """ @@ -225,6 +238,16 @@ def domain_requests_display(self): """ return get_domain_requests_display(self.roles, self.additional_permissions) + @property + def domain_requests_description_display(self): + """ + Returns a string description of the user's access to domain requests. + + Returns: + str: The display name of the user's domain request permissions description. + """ + return get_domain_requests_description_display(self.roles, self.additional_permissions) + @property def members_display(self): """ @@ -239,6 +262,16 @@ def members_display(self): """ return get_members_display(self.roles, self.additional_permissions) + @property + def members_description_display(self): + """ + Returns a string description of the user's access to managing members. + + Returns: + str: The display name of the user's member management permissions description. + """ + return get_members_description_display(self.roles, self.additional_permissions) + def clean(self): """Extends clean method to perform additional validation, which can raise errors in django admin.""" super().clean() diff --git a/src/registrar/models/utility/portfolio_helper.py b/src/registrar/models/utility/portfolio_helper.py index 03733237e..e94733fb6 100644 --- a/src/registrar/models/utility/portfolio_helper.py +++ b/src/registrar/models/utility/portfolio_helper.py @@ -123,6 +123,25 @@ def get_domains_display(roles, permissions): 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. @@ -148,6 +167,27 @@ def get_domain_requests_display(roles, permissions): 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. @@ -173,6 +213,27 @@ def get_members_display(roles, permissions): 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 diff --git a/src/registrar/templates/django/forms/widgets/multiple_input.html b/src/registrar/templates/django/forms/widgets/multiple_input.html index af98e898b..8b8d7a620 100644 --- a/src/registrar/templates/django/forms/widgets/multiple_input.html +++ b/src/registrar/templates/django/forms/widgets/multiple_input.html @@ -21,7 +21,7 @@ {% if field and field.field and field.field.descriptions %} {% with description=field.field.descriptions|get_dict_value:option.value %} {% if description %} -
{{ description }}
+{{ description }}
{% endif %} {% endwith %} {% endif %} diff --git a/src/registrar/templates/domain_add_user.html b/src/registrar/templates/domain_add_user.html index abc549a82..8ef167f98 100644 --- a/src/registrar/templates/domain_add_user.html +++ b/src/registrar/templates/domain_add_user.html @@ -42,17 +42,18 @@ {% endblock breadcrumb %}+ Provide an email address for the domain manager you’d like to add. + They’ll need to access the registrar using a Login.gov account that’s associated with this email address. + Domain managers can be a member of only one .gov organization. +
+ {% else %}- You can add another user to help manage your domain. Users can only be a member of one .gov organization, - and they'll need to sign in with their Login.gov account. + Provide an email address for the domain manager you’d like to add. + They’ll need to access the registrar using a Login.gov account that’s associated with this email address.
-{% else %} -- You can add another user to help manage your domain. They will need to sign in to the .gov registrar with - their Login.gov account. -
-{% endif %} + {% endif %}