mirror of
https://github.com/cisagov/manage.get.gov.git
synced 2025-05-20 03:19:24 +02:00
merged
This commit is contained in:
commit
504fdedf0c
15 changed files with 365 additions and 37 deletions
|
@ -60,6 +60,17 @@ def add_has_profile_feature_flag_to_context(request):
|
||||||
|
|
||||||
def portfolio_permissions(request):
|
def portfolio_permissions(request):
|
||||||
"""Make portfolio permissions for the request user available in global context"""
|
"""Make portfolio permissions for the request user available in global context"""
|
||||||
|
context = {
|
||||||
|
"has_base_portfolio_permission": False,
|
||||||
|
"has_domains_portfolio_permission": False,
|
||||||
|
"has_domain_requests_portfolio_permission": False,
|
||||||
|
"has_view_members_portfolio_permission": False,
|
||||||
|
"has_edit_members_portfolio_permission": False,
|
||||||
|
"has_view_suborganization": False,
|
||||||
|
"has_edit_suborganization": False,
|
||||||
|
"portfolio": None,
|
||||||
|
"has_organization_feature_flag": False,
|
||||||
|
}
|
||||||
try:
|
try:
|
||||||
portfolio = request.session.get("portfolio")
|
portfolio = request.session.get("portfolio")
|
||||||
if portfolio:
|
if portfolio:
|
||||||
|
@ -69,32 +80,18 @@ def portfolio_permissions(request):
|
||||||
"has_domain_requests_portfolio_permission": request.user.has_domain_requests_portfolio_permission(
|
"has_domain_requests_portfolio_permission": request.user.has_domain_requests_portfolio_permission(
|
||||||
portfolio
|
portfolio
|
||||||
),
|
),
|
||||||
|
"has_view_members_portfolio_permission": request.user.has_view_members_portfolio_permission(portfolio),
|
||||||
|
"has_edit_members_portfolio_permission": request.user.has_edit_members_portfolio_permission(portfolio),
|
||||||
"has_view_suborganization": request.user.has_view_suborganization(portfolio),
|
"has_view_suborganization": request.user.has_view_suborganization(portfolio),
|
||||||
"has_edit_suborganization": request.user.has_edit_suborganization(portfolio),
|
"has_edit_suborganization": request.user.has_edit_suborganization(portfolio),
|
||||||
"portfolio": portfolio,
|
"portfolio": portfolio,
|
||||||
"has_organization_feature_flag": True,
|
"has_organization_feature_flag": True,
|
||||||
}
|
}
|
||||||
return {
|
return context
|
||||||
"has_base_portfolio_permission": False,
|
|
||||||
"has_domains_portfolio_permission": False,
|
|
||||||
"has_domain_requests_portfolio_permission": False,
|
|
||||||
"has_view_suborganization": False,
|
|
||||||
"has_edit_suborganization": False,
|
|
||||||
"portfolio": None,
|
|
||||||
"has_organization_feature_flag": False,
|
|
||||||
}
|
|
||||||
|
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
# Handles cases where request.user might not exist
|
# Handles cases where request.user might not exist
|
||||||
return {
|
return context
|
||||||
"has_base_portfolio_permission": False,
|
|
||||||
"has_domains_portfolio_permission": False,
|
|
||||||
"has_domain_requests_portfolio_permission": False,
|
|
||||||
"has_view_suborganization": False,
|
|
||||||
"has_edit_suborganization": False,
|
|
||||||
"portfolio": None,
|
|
||||||
"has_organization_feature_flag": False,
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
def is_widescreen_mode(request):
|
def is_widescreen_mode(request):
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
import logging
|
import logging
|
||||||
from django.core.management import BaseCommand
|
from django.core.management import BaseCommand
|
||||||
from django.db.models.manager import BaseManager
|
|
||||||
from registrar.management.commands.utility.terminal_helper import PopulateScriptTemplate, TerminalColors
|
from registrar.management.commands.utility.terminal_helper import PopulateScriptTemplate, TerminalColors
|
||||||
from registrar.models import Domain, TransitionDomain
|
from registrar.models import Domain, TransitionDomain
|
||||||
|
|
||||||
|
@ -27,7 +26,7 @@ class Command(BaseCommand, PopulateScriptTemplate):
|
||||||
|
|
||||||
# check if a transition domain object for this domain name exists,
|
# check if a transition domain object for this domain name exists,
|
||||||
# or if so whether its first_ready value matches its created_at date
|
# or if so whether its first_ready value matches its created_at date
|
||||||
def custom_filter(self, records: BaseManager[Domain]) -> BaseManager[Domain]:
|
def custom_filter(self, records):
|
||||||
to_include_pks = []
|
to_include_pks = []
|
||||||
for record in records:
|
for record in records:
|
||||||
if (
|
if (
|
||||||
|
|
|
@ -78,7 +78,7 @@ class PopulateScriptTemplate(ABC):
|
||||||
run_summary_header = None
|
run_summary_header = None
|
||||||
|
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
def update_record(self, record: Model):
|
def update_record(self, record):
|
||||||
"""Defines how we update each field.
|
"""Defines how we update each field.
|
||||||
|
|
||||||
raises:
|
raises:
|
||||||
|
@ -86,7 +86,7 @@ class PopulateScriptTemplate(ABC):
|
||||||
"""
|
"""
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
def mass_update_records(self, object_class: Model, filter_conditions, fields_to_update, debug=True, verbose=False):
|
def mass_update_records(self, object_class, filter_conditions, fields_to_update, debug=True, verbose=False):
|
||||||
"""Loops through each valid "object_class" object - specified by filter_conditions - and
|
"""Loops through each valid "object_class" object - specified by filter_conditions - and
|
||||||
updates fields defined by fields_to_update using update_record.
|
updates fields defined by fields_to_update using update_record.
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,66 @@
|
||||||
|
# Generated by Django 4.2.10 on 2024-09-04 21:29
|
||||||
|
|
||||||
|
import django.contrib.postgres.fields
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
("registrar", "0122_create_groups_v16"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="portfolioinvitation",
|
||||||
|
name="portfolio_additional_permissions",
|
||||||
|
field=django.contrib.postgres.fields.ArrayField(
|
||||||
|
base_field=models.CharField(
|
||||||
|
choices=[
|
||||||
|
("view_all_domains", "View all domains and domain reports"),
|
||||||
|
("view_managed_domains", "View managed domains"),
|
||||||
|
("view_members", "View members"),
|
||||||
|
("edit_members", "Create and edit members"),
|
||||||
|
("view_all_requests", "View all requests"),
|
||||||
|
("view_created_requests", "View created requests"),
|
||||||
|
("edit_requests", "Create and edit requests"),
|
||||||
|
("view_portfolio", "View organization"),
|
||||||
|
("edit_portfolio", "Edit organization"),
|
||||||
|
("view_suborganization", "View suborganization"),
|
||||||
|
("edit_suborganization", "Edit suborganization"),
|
||||||
|
],
|
||||||
|
max_length=50,
|
||||||
|
),
|
||||||
|
blank=True,
|
||||||
|
help_text="Select one or more additional permissions.",
|
||||||
|
null=True,
|
||||||
|
size=None,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="userportfoliopermission",
|
||||||
|
name="additional_permissions",
|
||||||
|
field=django.contrib.postgres.fields.ArrayField(
|
||||||
|
base_field=models.CharField(
|
||||||
|
choices=[
|
||||||
|
("view_all_domains", "View all domains and domain reports"),
|
||||||
|
("view_managed_domains", "View managed domains"),
|
||||||
|
("view_members", "View members"),
|
||||||
|
("edit_members", "Create and edit members"),
|
||||||
|
("view_all_requests", "View all requests"),
|
||||||
|
("view_created_requests", "View created requests"),
|
||||||
|
("edit_requests", "Create and edit requests"),
|
||||||
|
("view_portfolio", "View organization"),
|
||||||
|
("edit_portfolio", "Edit organization"),
|
||||||
|
("view_suborganization", "View suborganization"),
|
||||||
|
("edit_suborganization", "Edit suborganization"),
|
||||||
|
],
|
||||||
|
max_length=50,
|
||||||
|
),
|
||||||
|
blank=True,
|
||||||
|
help_text="Select one or more additional permissions.",
|
||||||
|
null=True,
|
||||||
|
size=None,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
]
|
|
@ -198,10 +198,40 @@ class User(AbstractUser):
|
||||||
) or self._has_portfolio_permission(portfolio, UserPortfolioPermissionChoices.VIEW_MANAGED_DOMAINS)
|
) or self._has_portfolio_permission(portfolio, UserPortfolioPermissionChoices.VIEW_MANAGED_DOMAINS)
|
||||||
|
|
||||||
def has_domain_requests_portfolio_permission(self, portfolio):
|
def has_domain_requests_portfolio_permission(self, portfolio):
|
||||||
|
# BEGIN
|
||||||
|
# Note code below is to add organization_request feature
|
||||||
|
request = HttpRequest()
|
||||||
|
request.user = self
|
||||||
|
has_organization_requests_flag = flag_is_active(request, "organization_requests")
|
||||||
|
if not has_organization_requests_flag:
|
||||||
|
return False
|
||||||
|
# END
|
||||||
return self._has_portfolio_permission(
|
return self._has_portfolio_permission(
|
||||||
portfolio, UserPortfolioPermissionChoices.VIEW_ALL_REQUESTS
|
portfolio, UserPortfolioPermissionChoices.VIEW_ALL_REQUESTS
|
||||||
) or self._has_portfolio_permission(portfolio, UserPortfolioPermissionChoices.VIEW_CREATED_REQUESTS)
|
) or self._has_portfolio_permission(portfolio, UserPortfolioPermissionChoices.VIEW_CREATED_REQUESTS)
|
||||||
|
|
||||||
|
def has_view_members_portfolio_permission(self, portfolio):
|
||||||
|
# BEGIN
|
||||||
|
# Note code below is to add organization_request feature
|
||||||
|
request = HttpRequest()
|
||||||
|
request.user = self
|
||||||
|
has_organization_members_flag = flag_is_active(request, "organization_members")
|
||||||
|
if not has_organization_members_flag:
|
||||||
|
return False
|
||||||
|
# END
|
||||||
|
return self._has_portfolio_permission(portfolio, UserPortfolioPermissionChoices.VIEW_MEMBERS)
|
||||||
|
|
||||||
|
def has_edit_members_portfolio_permission(self, portfolio):
|
||||||
|
# BEGIN
|
||||||
|
# Note code below is to add organization_request feature
|
||||||
|
request = HttpRequest()
|
||||||
|
request.user = self
|
||||||
|
has_organization_members_flag = flag_is_active(request, "organization_members")
|
||||||
|
if not has_organization_members_flag:
|
||||||
|
return False
|
||||||
|
# END
|
||||||
|
return self._has_portfolio_permission(portfolio, UserPortfolioPermissionChoices.EDIT_MEMBERS)
|
||||||
|
|
||||||
def has_view_all_domains_permission(self, portfolio):
|
def has_view_all_domains_permission(self, portfolio):
|
||||||
"""Determines if the current user can view all available domains in a given portfolio"""
|
"""Determines if the current user can view all available domains in a given portfolio"""
|
||||||
return self._has_portfolio_permission(portfolio, UserPortfolioPermissionChoices.VIEW_ALL_DOMAINS)
|
return self._has_portfolio_permission(portfolio, UserPortfolioPermissionChoices.VIEW_ALL_DOMAINS)
|
||||||
|
|
|
@ -16,8 +16,8 @@ class UserPortfolioPermission(TimeStampedModel):
|
||||||
PORTFOLIO_ROLE_PERMISSIONS = {
|
PORTFOLIO_ROLE_PERMISSIONS = {
|
||||||
UserPortfolioRoleChoices.ORGANIZATION_ADMIN: [
|
UserPortfolioRoleChoices.ORGANIZATION_ADMIN: [
|
||||||
UserPortfolioPermissionChoices.VIEW_ALL_DOMAINS,
|
UserPortfolioPermissionChoices.VIEW_ALL_DOMAINS,
|
||||||
UserPortfolioPermissionChoices.VIEW_MEMBER,
|
UserPortfolioPermissionChoices.VIEW_MEMBERS,
|
||||||
UserPortfolioPermissionChoices.EDIT_MEMBER,
|
UserPortfolioPermissionChoices.EDIT_MEMBERS,
|
||||||
UserPortfolioPermissionChoices.VIEW_ALL_REQUESTS,
|
UserPortfolioPermissionChoices.VIEW_ALL_REQUESTS,
|
||||||
UserPortfolioPermissionChoices.EDIT_REQUESTS,
|
UserPortfolioPermissionChoices.EDIT_REQUESTS,
|
||||||
UserPortfolioPermissionChoices.VIEW_PORTFOLIO,
|
UserPortfolioPermissionChoices.VIEW_PORTFOLIO,
|
||||||
|
@ -28,7 +28,7 @@ class UserPortfolioPermission(TimeStampedModel):
|
||||||
],
|
],
|
||||||
UserPortfolioRoleChoices.ORGANIZATION_ADMIN_READ_ONLY: [
|
UserPortfolioRoleChoices.ORGANIZATION_ADMIN_READ_ONLY: [
|
||||||
UserPortfolioPermissionChoices.VIEW_ALL_DOMAINS,
|
UserPortfolioPermissionChoices.VIEW_ALL_DOMAINS,
|
||||||
UserPortfolioPermissionChoices.VIEW_MEMBER,
|
UserPortfolioPermissionChoices.VIEW_MEMBERS,
|
||||||
UserPortfolioPermissionChoices.VIEW_ALL_REQUESTS,
|
UserPortfolioPermissionChoices.VIEW_ALL_REQUESTS,
|
||||||
UserPortfolioPermissionChoices.VIEW_PORTFOLIO,
|
UserPortfolioPermissionChoices.VIEW_PORTFOLIO,
|
||||||
# Domain: field specific permissions
|
# Domain: field specific permissions
|
||||||
|
|
|
@ -17,8 +17,8 @@ class UserPortfolioPermissionChoices(models.TextChoices):
|
||||||
VIEW_ALL_DOMAINS = "view_all_domains", "View all domains and domain reports"
|
VIEW_ALL_DOMAINS = "view_all_domains", "View all domains and domain reports"
|
||||||
VIEW_MANAGED_DOMAINS = "view_managed_domains", "View managed domains"
|
VIEW_MANAGED_DOMAINS = "view_managed_domains", "View managed domains"
|
||||||
|
|
||||||
VIEW_MEMBER = "view_member", "View members"
|
VIEW_MEMBERS = "view_members", "View members"
|
||||||
EDIT_MEMBER = "edit_member", "Create and edit members"
|
EDIT_MEMBERS = "edit_members", "Create and edit members"
|
||||||
|
|
||||||
VIEW_ALL_REQUESTS = "view_all_requests", "View all requests"
|
VIEW_ALL_REQUESTS = "view_all_requests", "View all requests"
|
||||||
VIEW_CREATED_REQUESTS = "view_created_requests", "View created requests"
|
VIEW_CREATED_REQUESTS = "view_created_requests", "View created requests"
|
||||||
|
|
|
@ -46,11 +46,11 @@
|
||||||
Domains
|
Domains
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
<li class="usa-nav__primary-item">
|
<!-- <li class="usa-nav__primary-item">
|
||||||
<a href="#" class="usa-nav-link">
|
<a href="#" class="usa-nav-link">
|
||||||
Domain groups
|
Domain groups
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li> -->
|
||||||
|
|
||||||
{% if has_domain_requests_portfolio_permission %}
|
{% if has_domain_requests_portfolio_permission %}
|
||||||
<li class="usa-nav__primary-item">
|
<li class="usa-nav__primary-item">
|
||||||
|
@ -60,11 +60,13 @@
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
{% if has_view_members_portfolio_permission %}
|
||||||
<li class="usa-nav__primary-item">
|
<li class="usa-nav__primary-item">
|
||||||
<a href="#" class="usa-nav-link">
|
<a href="#" class="usa-nav-link">
|
||||||
Members
|
Members
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
|
{% endif %}
|
||||||
<li class="usa-nav__primary-item">
|
<li class="usa-nav__primary-item">
|
||||||
{% url 'organization' as url %}
|
{% url 'organization' as url %}
|
||||||
<!-- Move the padding from the a to the span so that the descenders do not get cut off -->
|
<!-- Move the padding from the a to the span so that the descenders do not get cut off -->
|
||||||
|
|
|
@ -1534,6 +1534,7 @@ class TestUser(TestCase):
|
||||||
self.assertFalse(self.user.has_contact_info())
|
self.assertFalse(self.user.has_contact_info())
|
||||||
|
|
||||||
@less_console_noise_decorator
|
@less_console_noise_decorator
|
||||||
|
@override_flag("organization_requests", active=True)
|
||||||
def test_has_portfolio_permission(self):
|
def test_has_portfolio_permission(self):
|
||||||
"""
|
"""
|
||||||
0. Returns False when user does not have a permission
|
0. Returns False when user does not have a permission
|
||||||
|
@ -1555,7 +1556,10 @@ class TestUser(TestCase):
|
||||||
portfolio_permission, _ = UserPortfolioPermission.objects.get_or_create(
|
portfolio_permission, _ = UserPortfolioPermission.objects.get_or_create(
|
||||||
portfolio=portfolio,
|
portfolio=portfolio,
|
||||||
user=self.user,
|
user=self.user,
|
||||||
additional_permissions=[UserPortfolioPermissionChoices.VIEW_ALL_DOMAINS],
|
additional_permissions=[
|
||||||
|
UserPortfolioPermissionChoices.VIEW_PORTFOLIO,
|
||||||
|
UserPortfolioPermissionChoices.VIEW_ALL_DOMAINS,
|
||||||
|
],
|
||||||
)
|
)
|
||||||
|
|
||||||
user_can_view_all_domains = self.user.has_domains_portfolio_permission(portfolio)
|
user_can_view_all_domains = self.user.has_domains_portfolio_permission(portfolio)
|
||||||
|
|
|
@ -1,9 +1,13 @@
|
||||||
from registrar.models import UserDomainRole, Domain, DomainInformation, Portfolio
|
from registrar.models import UserDomainRole, Domain, DomainInformation, Portfolio
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
|
|
||||||
|
from registrar.models.user_portfolio_permission import UserPortfolioPermission
|
||||||
|
from registrar.models.utility.portfolio_helper import UserPortfolioPermissionChoices, UserPortfolioRoleChoices
|
||||||
from .test_views import TestWithUser
|
from .test_views import TestWithUser
|
||||||
from django_webtest import WebTest # type: ignore
|
from django_webtest import WebTest # type: ignore
|
||||||
from django.utils.dateparse import parse_date
|
from django.utils.dateparse import parse_date
|
||||||
from api.tests.common import less_console_noise_decorator
|
from api.tests.common import less_console_noise_decorator
|
||||||
|
from waffle.testutils import override_flag
|
||||||
|
|
||||||
|
|
||||||
class GetDomainsJsonTest(TestWithUser, WebTest):
|
class GetDomainsJsonTest(TestWithUser, WebTest):
|
||||||
|
@ -31,6 +35,7 @@ class GetDomainsJsonTest(TestWithUser, WebTest):
|
||||||
|
|
||||||
def tearDown(self):
|
def tearDown(self):
|
||||||
UserDomainRole.objects.all().delete()
|
UserDomainRole.objects.all().delete()
|
||||||
|
UserPortfolioPermission.objects.all().delete()
|
||||||
DomainInformation.objects.all().delete()
|
DomainInformation.objects.all().delete()
|
||||||
Portfolio.objects.all().delete()
|
Portfolio.objects.all().delete()
|
||||||
super().tearDown()
|
super().tearDown()
|
||||||
|
@ -115,8 +120,104 @@ class GetDomainsJsonTest(TestWithUser, WebTest):
|
||||||
self.assertEqual(svg_icon_expected, svg_icons[i])
|
self.assertEqual(svg_icon_expected, svg_icons[i])
|
||||||
|
|
||||||
@less_console_noise_decorator
|
@less_console_noise_decorator
|
||||||
def test_get_domains_json_with_portfolio(self):
|
@override_flag("organization_feature", active=True)
|
||||||
"""Test that an authenticated user gets the list of 2 domains for portfolio."""
|
def test_get_domains_json_with_portfolio_view_managed_domains(self):
|
||||||
|
"""Test that an authenticated user gets the list of 1 domain for portfolio. The 1 domain
|
||||||
|
is the domain that they manage within the portfolio."""
|
||||||
|
|
||||||
|
UserPortfolioPermission.objects.get_or_create(
|
||||||
|
user=self.user,
|
||||||
|
portfolio=self.portfolio,
|
||||||
|
roles=[UserPortfolioRoleChoices.ORGANIZATION_MEMBER],
|
||||||
|
additional_permissions=[UserPortfolioPermissionChoices.VIEW_MANAGED_DOMAINS],
|
||||||
|
)
|
||||||
|
|
||||||
|
response = self.app.get(reverse("get_domains_json"), {"portfolio": self.portfolio.id})
|
||||||
|
self.assertEqual(response.status_code, 200)
|
||||||
|
data = response.json
|
||||||
|
|
||||||
|
# Check pagination info
|
||||||
|
self.assertEqual(data["page"], 1)
|
||||||
|
self.assertFalse(data["has_next"])
|
||||||
|
self.assertFalse(data["has_previous"])
|
||||||
|
self.assertEqual(data["num_pages"], 1)
|
||||||
|
|
||||||
|
# Check the number of domains
|
||||||
|
self.assertEqual(len(data["domains"]), 1)
|
||||||
|
|
||||||
|
# Expected domains
|
||||||
|
expected_domains = [self.domain3]
|
||||||
|
|
||||||
|
# Extract fields from response
|
||||||
|
domain_ids = [domain["id"] for domain in data["domains"]]
|
||||||
|
names = [domain["name"] for domain in data["domains"]]
|
||||||
|
expiration_dates = [domain["expiration_date"] for domain in data["domains"]]
|
||||||
|
states = [domain["state"] for domain in data["domains"]]
|
||||||
|
state_displays = [domain["state_display"] for domain in data["domains"]]
|
||||||
|
get_state_help_texts = [domain["get_state_help_text"] for domain in data["domains"]]
|
||||||
|
action_urls = [domain["action_url"] for domain in data["domains"]]
|
||||||
|
action_labels = [domain["action_label"] for domain in data["domains"]]
|
||||||
|
svg_icons = [domain["svg_icon"] for domain in data["domains"]]
|
||||||
|
|
||||||
|
# Check fields for each domain
|
||||||
|
for i, expected_domain in enumerate(expected_domains):
|
||||||
|
self.assertEqual(expected_domain.id, domain_ids[i])
|
||||||
|
self.assertEqual(expected_domain.name, names[i])
|
||||||
|
self.assertEqual(expected_domain.expiration_date, expiration_dates[i])
|
||||||
|
self.assertEqual(expected_domain.state, states[i])
|
||||||
|
|
||||||
|
# Parsing the expiration date from string to date
|
||||||
|
parsed_expiration_date = parse_date(expiration_dates[i])
|
||||||
|
expected_domain.expiration_date = parsed_expiration_date
|
||||||
|
|
||||||
|
# Check state_display and get_state_help_text
|
||||||
|
self.assertEqual(expected_domain.state_display(), state_displays[i])
|
||||||
|
self.assertEqual(expected_domain.get_state_help_text(), get_state_help_texts[i])
|
||||||
|
|
||||||
|
self.assertEqual(reverse("domain", kwargs={"pk": expected_domain.id}), action_urls[i])
|
||||||
|
|
||||||
|
# Check action_label
|
||||||
|
user_domain_role_exists = UserDomainRole.objects.filter(
|
||||||
|
domain_id=expected_domains[i].id, user=self.user
|
||||||
|
).exists()
|
||||||
|
action_label_expected = (
|
||||||
|
"View"
|
||||||
|
if not user_domain_role_exists
|
||||||
|
or expected_domains[i].state
|
||||||
|
in [
|
||||||
|
Domain.State.DELETED,
|
||||||
|
Domain.State.ON_HOLD,
|
||||||
|
]
|
||||||
|
else "Manage"
|
||||||
|
)
|
||||||
|
self.assertEqual(action_label_expected, action_labels[i])
|
||||||
|
|
||||||
|
# Check svg_icon
|
||||||
|
svg_icon_expected = (
|
||||||
|
"visibility"
|
||||||
|
if not user_domain_role_exists
|
||||||
|
or expected_domains[i].state
|
||||||
|
in [
|
||||||
|
Domain.State.DELETED,
|
||||||
|
Domain.State.ON_HOLD,
|
||||||
|
]
|
||||||
|
else "settings"
|
||||||
|
)
|
||||||
|
self.assertEqual(svg_icon_expected, svg_icons[i])
|
||||||
|
|
||||||
|
@less_console_noise_decorator
|
||||||
|
@override_flag("organization_feature", active=True)
|
||||||
|
def test_get_domains_json_with_portfolio_view_all_domains(self):
|
||||||
|
"""Test that an authenticated user gets the list of 2 domains for portfolio. One is a domain which
|
||||||
|
they manage within the portfolio. The other is a domain which they don't manage within the
|
||||||
|
portfolio."""
|
||||||
|
|
||||||
|
UserPortfolioPermission.objects.get_or_create(
|
||||||
|
user=self.user,
|
||||||
|
portfolio=self.portfolio,
|
||||||
|
roles=[UserPortfolioRoleChoices.ORGANIZATION_MEMBER],
|
||||||
|
additional_permissions=[UserPortfolioPermissionChoices.VIEW_ALL_DOMAINS],
|
||||||
|
)
|
||||||
|
|
||||||
response = self.app.get(reverse("get_domains_json"), {"portfolio": self.portfolio.id})
|
response = self.app.get(reverse("get_domains_json"), {"portfolio": self.portfolio.id})
|
||||||
self.assertEqual(response.status_code, 200)
|
self.assertEqual(response.status_code, 200)
|
||||||
|
|
|
@ -230,6 +230,7 @@ class TestPortfolio(WebTest):
|
||||||
self.assertContains(response, 'for="id_city"')
|
self.assertContains(response, 'for="id_city"')
|
||||||
|
|
||||||
@less_console_noise_decorator
|
@less_console_noise_decorator
|
||||||
|
@override_flag("organization_requests", active=True)
|
||||||
def test_accessible_pages_when_user_does_not_have_permission(self):
|
def test_accessible_pages_when_user_does_not_have_permission(self):
|
||||||
"""Tests which pages are accessible when user does not have portfolio permissions"""
|
"""Tests which pages are accessible when user does not have portfolio permissions"""
|
||||||
self.app.set_user(self.user.username)
|
self.app.set_user(self.user.username)
|
||||||
|
@ -280,6 +281,7 @@ class TestPortfolio(WebTest):
|
||||||
self.assertEquals(domain_request_page.status_code, 403)
|
self.assertEquals(domain_request_page.status_code, 403)
|
||||||
|
|
||||||
@less_console_noise_decorator
|
@less_console_noise_decorator
|
||||||
|
@override_flag("organization_requests", active=True)
|
||||||
def test_accessible_pages_when_user_does_not_have_role(self):
|
def test_accessible_pages_when_user_does_not_have_role(self):
|
||||||
"""Test that admin / memmber roles are associated with the right access"""
|
"""Test that admin / memmber roles are associated with the right access"""
|
||||||
self.app.set_user(self.user.username)
|
self.app.set_user(self.user.username)
|
||||||
|
@ -554,3 +556,99 @@ class TestPortfolio(WebTest):
|
||||||
|
|
||||||
# Test for widescreen modifier
|
# Test for widescreen modifier
|
||||||
self.assertContains(response, "--widescreen")
|
self.assertContains(response, "--widescreen")
|
||||||
|
|
||||||
|
@less_console_noise_decorator
|
||||||
|
@override_flag("organization_feature", active=True)
|
||||||
|
@override_flag("organization_requests", active=False)
|
||||||
|
def test_organization_requests_waffle_flag_off_hides_nav_link_and_restricts_permission(self):
|
||||||
|
"""Setting the organization_requests waffle off hides the nav link and restricts access to the requests page"""
|
||||||
|
self.app.set_user(self.user.username)
|
||||||
|
|
||||||
|
UserPortfolioPermission.objects.get_or_create(
|
||||||
|
user=self.user,
|
||||||
|
portfolio=self.portfolio,
|
||||||
|
additional_permissions=[
|
||||||
|
UserPortfolioPermissionChoices.VIEW_PORTFOLIO,
|
||||||
|
UserPortfolioPermissionChoices.VIEW_CREATED_REQUESTS,
|
||||||
|
UserPortfolioPermissionChoices.VIEW_ALL_REQUESTS,
|
||||||
|
UserPortfolioPermissionChoices.EDIT_REQUESTS,
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
home = self.app.get(reverse("home")).follow()
|
||||||
|
|
||||||
|
self.assertContains(home, "Hotel California")
|
||||||
|
self.assertNotContains(home, "Domain requests")
|
||||||
|
|
||||||
|
domain_requests = self.app.get(reverse("domain-requests"), expect_errors=True)
|
||||||
|
self.assertEqual(domain_requests.status_code, 403)
|
||||||
|
|
||||||
|
@less_console_noise_decorator
|
||||||
|
@override_flag("organization_feature", active=True)
|
||||||
|
@override_flag("organization_requests", active=True)
|
||||||
|
def test_organization_requests_waffle_flag_on_shows_nav_link_and_allows_permission(self):
|
||||||
|
"""Setting the organization_requests waffle on shows the nav link and allows access to the requests page"""
|
||||||
|
self.app.set_user(self.user.username)
|
||||||
|
|
||||||
|
UserPortfolioPermission.objects.get_or_create(
|
||||||
|
user=self.user,
|
||||||
|
portfolio=self.portfolio,
|
||||||
|
additional_permissions=[
|
||||||
|
UserPortfolioPermissionChoices.VIEW_PORTFOLIO,
|
||||||
|
UserPortfolioPermissionChoices.VIEW_CREATED_REQUESTS,
|
||||||
|
UserPortfolioPermissionChoices.VIEW_ALL_REQUESTS,
|
||||||
|
UserPortfolioPermissionChoices.EDIT_REQUESTS,
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
home = self.app.get(reverse("home")).follow()
|
||||||
|
|
||||||
|
self.assertContains(home, "Hotel California")
|
||||||
|
self.assertContains(home, "Domain requests")
|
||||||
|
|
||||||
|
domain_requests = self.app.get(reverse("domain-requests"))
|
||||||
|
self.assertEqual(domain_requests.status_code, 200)
|
||||||
|
|
||||||
|
@less_console_noise_decorator
|
||||||
|
@override_flag("organization_feature", active=True)
|
||||||
|
@override_flag("organization_members", active=False)
|
||||||
|
def test_organization_members_waffle_flag_off_hides_nav_link(self):
|
||||||
|
"""Setting the organization_members waffle off hides the nav link"""
|
||||||
|
self.app.set_user(self.user.username)
|
||||||
|
|
||||||
|
UserPortfolioPermission.objects.get_or_create(
|
||||||
|
user=self.user,
|
||||||
|
portfolio=self.portfolio,
|
||||||
|
additional_permissions=[
|
||||||
|
UserPortfolioPermissionChoices.VIEW_PORTFOLIO,
|
||||||
|
UserPortfolioPermissionChoices.VIEW_CREATED_REQUESTS,
|
||||||
|
UserPortfolioPermissionChoices.VIEW_ALL_REQUESTS,
|
||||||
|
UserPortfolioPermissionChoices.EDIT_REQUESTS,
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
home = self.app.get(reverse("home")).follow()
|
||||||
|
|
||||||
|
self.assertContains(home, "Hotel California")
|
||||||
|
self.assertNotContains(home, "Members")
|
||||||
|
|
||||||
|
@less_console_noise_decorator
|
||||||
|
@override_flag("organization_feature", active=True)
|
||||||
|
@override_flag("organization_members", active=True)
|
||||||
|
def test_organization_members_waffle_flag_on_shows_nav_link(self):
|
||||||
|
"""Setting the organization_members waffle on shows the nav link"""
|
||||||
|
self.app.set_user(self.user.username)
|
||||||
|
|
||||||
|
UserPortfolioPermission.objects.get_or_create(
|
||||||
|
user=self.user,
|
||||||
|
portfolio=self.portfolio,
|
||||||
|
additional_permissions=[
|
||||||
|
UserPortfolioPermissionChoices.VIEW_PORTFOLIO,
|
||||||
|
UserPortfolioPermissionChoices.VIEW_MEMBERS,
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
home = self.app.get(reverse("home")).follow()
|
||||||
|
|
||||||
|
self.assertContains(home, "Hotel California")
|
||||||
|
self.assertContains(home, "Members")
|
||||||
|
|
|
@ -50,9 +50,13 @@ def get_domain_ids_from_request(request):
|
||||||
"""
|
"""
|
||||||
portfolio = request.GET.get("portfolio")
|
portfolio = request.GET.get("portfolio")
|
||||||
if portfolio:
|
if portfolio:
|
||||||
|
if request.user.is_org_user(request) and request.user.has_view_all_domains_permission(portfolio):
|
||||||
domain_infos = DomainInformation.objects.filter(portfolio=portfolio)
|
domain_infos = DomainInformation.objects.filter(portfolio=portfolio)
|
||||||
return domain_infos.values_list("domain_id", flat=True)
|
return domain_infos.values_list("domain_id", flat=True)
|
||||||
else:
|
else:
|
||||||
|
domain_info_ids = DomainInformation.objects.filter(portfolio=portfolio).values_list("domain_id", flat=True)
|
||||||
|
user_domain_roles = UserDomainRole.objects.filter(user=request.user).values_list("domain_id", flat=True)
|
||||||
|
return domain_info_ids.intersection(user_domain_roles)
|
||||||
user_domain_roles = UserDomainRole.objects.filter(user=request.user)
|
user_domain_roles = UserDomainRole.objects.filter(user=request.user)
|
||||||
return user_domain_roles.values_list("domain_id", flat=True)
|
return user_domain_roles.values_list("domain_id", flat=True)
|
||||||
|
|
||||||
|
|
|
@ -7,5 +7,6 @@ from .permission_views import (
|
||||||
DomainRequestPermissionWithdrawView,
|
DomainRequestPermissionWithdrawView,
|
||||||
DomainInvitationPermissionDeleteView,
|
DomainInvitationPermissionDeleteView,
|
||||||
DomainRequestWizardPermissionView,
|
DomainRequestWizardPermissionView,
|
||||||
|
PortfolioMembersPermission,
|
||||||
)
|
)
|
||||||
from .api_views import get_senior_official_from_federal_agency_json
|
from .api_views import get_senior_official_from_federal_agency_json
|
||||||
|
|
|
@ -454,3 +454,20 @@ class PortfolioDomainRequestsPermission(PortfolioBasePermission):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
return super().has_permission()
|
return super().has_permission()
|
||||||
|
|
||||||
|
|
||||||
|
class PortfolioMembersPermission(PortfolioBasePermission):
|
||||||
|
"""Permission mixin that allows access to portfolio members pages if user
|
||||||
|
has access, otherwise 403"""
|
||||||
|
|
||||||
|
def has_permission(self):
|
||||||
|
"""Check if this user has access to members for this portfolio.
|
||||||
|
|
||||||
|
The user is in self.request.user and the portfolio can be looked
|
||||||
|
up from the portfolio's primary key in self.kwargs["pk"]"""
|
||||||
|
|
||||||
|
portfolio = self.request.session.get("portfolio")
|
||||||
|
if not self.request.user.has_view_members(portfolio):
|
||||||
|
return False
|
||||||
|
|
||||||
|
return super().has_permission()
|
||||||
|
|
|
@ -18,6 +18,7 @@ from .mixins import (
|
||||||
UserDeleteDomainRolePermission,
|
UserDeleteDomainRolePermission,
|
||||||
UserProfilePermission,
|
UserProfilePermission,
|
||||||
PortfolioBasePermission,
|
PortfolioBasePermission,
|
||||||
|
PortfolioMembersPermission,
|
||||||
)
|
)
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
|
@ -229,3 +230,11 @@ class PortfolioDomainRequestsPermissionView(PortfolioDomainRequestsPermission, P
|
||||||
This abstract view cannot be instantiated. Actual views must specify
|
This abstract view cannot be instantiated. Actual views must specify
|
||||||
`template_name`.
|
`template_name`.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
class PortfolioMembersPermissionView(PortfolioMembersPermission, PortfolioBasePermissionView, abc.ABC):
|
||||||
|
"""Abstract base view for portfolio domain request views that enforces permissions.
|
||||||
|
|
||||||
|
This abstract view cannot be instantiated. Actual views must specify
|
||||||
|
`template_name`.
|
||||||
|
"""
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue