This commit is contained in:
David Kennedy 2024-08-20 15:29:28 -04:00
parent 432ee9c860
commit 3c89557976
No known key found for this signature in database
GPG key ID: 6528A5386E66B96B
12 changed files with 80 additions and 103 deletions

View file

@ -9,8 +9,6 @@ from django.http import HttpResponseRedirect
from django.shortcuts import redirect from django.shortcuts import redirect
from urllib.parse import parse_qs, urlencode from urllib.parse import parse_qs, urlencode
from waffle import flag_is_active
from djangooidc.oidc import Client from djangooidc.oidc import Client
from djangooidc import exceptions as o_e from djangooidc import exceptions as o_e
from registrar.models import User from registrar.models import User
@ -114,13 +112,6 @@ def login_callback(request):
user.set_user_verification_type() user.set_user_verification_type()
user.save() user.save()
if not flag_is_active(request, "multiple_portfolios"):
user.set_default_last_selected_portfolio()
user.save()
else:
user.last_selected_portfolio = None
user.save()
login(request, user) login(request, user)
logger.info("Successfully logged in user %s" % user) logger.info("Successfully logged in user %s" % user)

View file

@ -717,17 +717,12 @@ class MyUserAdmin(BaseUserAdmin, ImportExportModelAdmin):
"is_superuser", "is_superuser",
"groups", "groups",
"user_permissions", "user_permissions",
"last_selected_portfolio",
) )
}, },
), ),
("Important dates", {"fields": ("last_login", "date_joined")}), ("Important dates", {"fields": ("last_login", "date_joined")}),
) )
autocomplete_fields = [
"last_selected_portfolio",
]
readonly_fields = ("verification_type",) readonly_fields = ("verification_type",)
analyst_fieldsets = ( analyst_fieldsets = (
@ -747,7 +742,6 @@ class MyUserAdmin(BaseUserAdmin, ImportExportModelAdmin):
"fields": ( "fields": (
"is_active", "is_active",
"groups", "groups",
"last_selected_portfolio",
) )
}, },
), ),
@ -802,7 +796,6 @@ class MyUserAdmin(BaseUserAdmin, ImportExportModelAdmin):
"Important dates", "Important dates",
"last_login", "last_login",
"date_joined", "date_joined",
"last_selected_portfolio",
] ]
# TODO: delete after we merge organization feature # TODO: delete after we merge organization feature

View file

@ -61,27 +61,34 @@ 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"""
try: try:
if not request.user or not request.user.is_authenticated or not flag_is_active(request, "organization_feature"): if request.session["portfolio"] is not None:
return { return {
"has_base_portfolio_permission": False, "has_base_portfolio_permission": request.user.has_base_portfolio_permission(request.session["portfolio"]),
"has_domains_portfolio_permission": False, "has_domains_portfolio_permission": request.user.has_domains_portfolio_permission(request.session["portfolio"]),
"has_domain_requests_portfolio_permission": False, "has_domain_requests_portfolio_permission": request.user.has_domain_requests_portfolio_permission(request.session["portfolio"]),
"portfolio": None, "has_view_suborganization": request.user.has_view_suborganization(request.session["portfolio"]),
"has_organization_feature_flag": False, "has_edit_suborganization": request.user.has_edit_suborganization(request.session["portfolio"]),
"portfolio": request.session["portfolio"],
"has_organization_feature_flag": True,
} }
return { return {
"has_base_portfolio_permission": request.user.has_base_portfolio_permission(), "has_base_portfolio_permission": False,
"has_domains_portfolio_permission": request.user.has_domains_portfolio_permission(), "has_domains_portfolio_permission": False,
"has_domain_requests_portfolio_permission": request.user.has_domain_requests_portfolio_permission(), "has_domain_requests_portfolio_permission": False,
"portfolio": request.user.last_selected_portfolio, "has_view_suborganization": False,
"has_organization_feature_flag": True, "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 {
"has_base_portfolio_permission": False, "has_base_portfolio_permission": False,
"has_domains_portfolio_permission": False, "has_domains_portfolio_permission": False,
"has_domain_requests_portfolio_permission": False, "has_domain_requests_portfolio_permission": False,
"has_view_suborganization": False,
"has_edit_suborganization": False,
"portfolio": None, "portfolio": None,
"has_organization_feature_flag": False, "has_organization_feature_flag": False,
} }

View file

@ -25,17 +25,6 @@ class Migration(migrations.Migration):
model_name="user", model_name="user",
name="portfolio_roles", name="portfolio_roles",
), ),
migrations.AddField(
model_name="user",
name="last_selected_portfolio",
field=models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.SET_NULL,
related_name="portfolio_selected_by_users",
to="registrar.portfolio",
),
),
migrations.CreateModel( migrations.CreateModel(
name="UserPortfolioPermission", name="UserPortfolioPermission",
fields=[ fields=[

View file

@ -110,14 +110,6 @@ class User(AbstractUser):
related_name="users", related_name="users",
) )
last_selected_portfolio = models.ForeignKey(
"registrar.Portfolio",
null=True,
blank=True,
related_name="portfolio_selected_by_users",
on_delete=models.SET_NULL,
)
phone = PhoneNumberField( phone = PhoneNumberField(
null=True, null=True,
blank=True, blank=True,
@ -208,50 +200,48 @@ class User(AbstractUser):
def has_contact_info(self): def has_contact_info(self):
return bool(self.title or self.email or self.phone) return bool(self.title or self.email or self.phone)
def _has_portfolio_permission(self, portfolio_permission): def _has_portfolio_permission(self, portfolio, portfolio_permission):
"""The views should only call this function when testing for perms and not rely on roles.""" """The views should only call this function when testing for perms and not rely on roles."""
if not self.last_selected_portfolio: portfolio_perms = self.portfolio_permissions.filter(portfolio=portfolio, user=self).first()
return False
portfolio_perms = self.portfolio_permissions.filter(portfolio=self.last_selected_portfolio, user=self).first()
if not portfolio_perms: if not portfolio_perms:
return False return False
portfolio_permissions = portfolio_perms._get_portfolio_permissions() portfolio_permissions = portfolio_perms._get_portfolio_permissions()
return portfolio_permission in portfolio_permissions return portfolio_permission in portfolio_permissions
def has_base_portfolio_permission(self): def has_base_portfolio_permission(self, portfolio):
return self._has_portfolio_permission(UserPortfolioPermissionChoices.VIEW_PORTFOLIO) return self._has_portfolio_permission(portfolio, UserPortfolioPermissionChoices.VIEW_PORTFOLIO)
def has_edit_org_portfolio_permission(self): def has_edit_org_portfolio_permission(self, portfolio):
return self._has_portfolio_permission(UserPortfolioPermissionChoices.EDIT_PORTFOLIO) return self._has_portfolio_permission(portfolio, UserPortfolioPermissionChoices.EDIT_PORTFOLIO)
def has_domains_portfolio_permission(self): def has_domains_portfolio_permission(self, portfolio):
return self._has_portfolio_permission( return self._has_portfolio_permission(portfolio,
UserPortfolioPermissionChoices.VIEW_ALL_DOMAINS UserPortfolioPermissionChoices.VIEW_ALL_DOMAINS
) or self._has_portfolio_permission(UserPortfolioPermissionChoices.VIEW_MANAGED_DOMAINS) ) or self._has_portfolio_permission(portfolio, UserPortfolioPermissionChoices.VIEW_MANAGED_DOMAINS)
def has_domain_requests_portfolio_permission(self): def has_domain_requests_portfolio_permission(self, portfolio):
return self._has_portfolio_permission( return self._has_portfolio_permission(portfolio,
UserPortfolioPermissionChoices.VIEW_ALL_REQUESTS UserPortfolioPermissionChoices.VIEW_ALL_REQUESTS
) or self._has_portfolio_permission(UserPortfolioPermissionChoices.VIEW_CREATED_REQUESTS) ) or self._has_portfolio_permission(portfolio, UserPortfolioPermissionChoices.VIEW_CREATED_REQUESTS)
def has_view_all_domains_permission(self): 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(UserPortfolioPermissionChoices.VIEW_ALL_DOMAINS) return self._has_portfolio_permission(portfolio, UserPortfolioPermissionChoices.VIEW_ALL_DOMAINS)
# Field specific permission checks # Field specific permission checks
def has_view_suborganization(self): def has_view_suborganization(self, portfolio):
return self._has_portfolio_permission(UserPortfolioPermissionChoices.VIEW_SUBORGANIZATION) return self._has_portfolio_permission(portfolio, UserPortfolioPermissionChoices.VIEW_SUBORGANIZATION)
def has_edit_suborganization(self): def has_edit_suborganization(self, portfolio):
return self._has_portfolio_permission(UserPortfolioPermissionChoices.EDIT_SUBORGANIZATION) return self._has_portfolio_permission(portfolio, UserPortfolioPermissionChoices.EDIT_SUBORGANIZATION)
def set_default_last_selected_portfolio(self): def get_first_portfolio(self):
permission = self.portfolio_permissions.first() permission = self.portfolio_permissions.first()
if permission: if permission:
self.last_selected_portfolio = permission.portfolio return permission.portfolio
return None
@classmethod @classmethod
def needs_identity_verification(cls, email, uuid): def needs_identity_verification(cls, email, uuid):
@ -367,7 +357,7 @@ class User(AbstractUser):
email__iexact=self.email, status=PortfolioInvitation.PortfolioInvitationStatus.INVITED email__iexact=self.email, status=PortfolioInvitation.PortfolioInvitationStatus.INVITED
): ):
only_single_portfolio = ( only_single_portfolio = (
not flag_is_active(None, "multiple_portfolios") and self.last_selected_portfolio is None not flag_is_active(None, "multiple_portfolios") and self.get_first_portfolio() is None
) )
if only_single_portfolio or flag_is_active(None, "multiple_portfolios"): if only_single_portfolio or flag_is_active(None, "multiple_portfolios"):
try: try:
@ -396,12 +386,12 @@ class User(AbstractUser):
def is_org_user(self, request): def is_org_user(self, request):
has_organization_feature_flag = flag_is_active(request, "organization_feature") has_organization_feature_flag = flag_is_active(request, "organization_feature")
return has_organization_feature_flag and self.has_base_portfolio_permission() return has_organization_feature_flag and self.has_base_portfolio_permission(request.session["portfolio"])
def get_user_domain_ids(self, request): def get_user_domain_ids(self, request):
"""Returns either the domains ids associated with this user on UserDomainRole or Portfolio""" """Returns either the domains ids associated with this user on UserDomainRole or Portfolio"""
if self.is_org_user(request) and self.has_view_all_domains_permission(): if self.is_org_user(request) and self.has_view_all_domains_permission(request.session["portfolio"]):
return DomainInformation.objects.filter(portfolio=self.last_selected_portfolio).values_list( return DomainInformation.objects.filter(portfolio=request.session["portfolio"]).values_list(
"domain_id", flat=True "domain_id", flat=True
) )
else: else:

View file

@ -125,8 +125,9 @@ class CheckUserProfileMiddleware:
class CheckPortfolioMiddleware: class CheckPortfolioMiddleware:
""" """
Checks if the current user has a portfolio this middleware should serve two purposes:
If they do, redirect them to the portfolio homepage when they navigate to home. 1 - set the portfolio in session if appropriate # views will need the session portfolio
2 - if path is home and session portfolio is set, redirect based on permissions of user
""" """
def __init__(self, get_response): def __init__(self, get_response):
@ -140,20 +141,28 @@ class CheckPortfolioMiddleware:
def process_view(self, request, view_func, view_args, view_kwargs): def process_view(self, request, view_func, view_args, view_kwargs):
current_path = request.path current_path = request.path
if current_path == self.home and request.user.is_authenticated and request.user.is_org_user(request): if not request.user.is_authenticated:
return None
if request.user.has_base_portfolio_permission(): # set the portfolio in the session if it is not set
portfolio = request.user.last_selected_portfolio if not "portfolio" in request.session or request.session["portfolio"] is None:
# if user is a multiple portfolio
if flag_is_active(request, "multiple_portfolios"):
# NOTE: we will want to change later to have a workflow for selecting
# portfolio and another for switching portfolio; for now, select first
request.session["portfolio"] = request.user.get_first_portfolio()
elif flag_is_active(request, "organization_feature"):
request.session["portfolio"] = request.user.get_first_portfolio()
else:
request.session["portfolio"] = None
# Add the portfolio to the request object if request.session["portfolio"] is not None and current_path == self.home:
request.last_selected_portfolio = portfolio if request.user.has_base_portfolio_permission(request.session["portfolio"]):
if request.user.has_domains_portfolio_permission(request.session["portfolio"]):
if request.user.has_domains_portfolio_permission():
portfolio_redirect = reverse("domains") portfolio_redirect = reverse("domains")
else: else:
# View organization is the lowest access # View organization is the lowest access
portfolio_redirect = reverse("organization") portfolio_redirect = reverse("organization")
return HttpResponseRedirect(portfolio_redirect) return HttpResponseRedirect(portfolio_redirect)
return None return None

View file

@ -72,7 +72,7 @@
{% include "includes/summary_item.html" with title='DNSSEC' value='Not Enabled' edit_link=url editable=is_editable %} {% include "includes/summary_item.html" with title='DNSSEC' value='Not Enabled' edit_link=url editable=is_editable %}
{% endif %} {% endif %}
{% if portfolio and has_domains_portfolio_permission and request.user.has_view_suborganization %} {% if portfolio and has_domains_portfolio_permission and has_view_suborganization %}
{% url 'domain-suborganization' pk=domain.id as url %} {% url 'domain-suborganization' pk=domain.id as url %}
{% include "includes/summary_item.html" with title='Suborganization' value=domain.domain_info.sub_organization edit_link=url editable=is_editable|and:request.user.has_edit_suborganization %} {% include "includes/summary_item.html" with title='Suborganization' value=domain.domain_info.sub_organization edit_link=url editable=is_editable|and:request.user.has_edit_suborganization %}
{% else %} {% else %}

View file

@ -61,7 +61,7 @@
{% if portfolio %} {% if portfolio %}
{% comment %} Only show this menu option if the user has the perms to do so {% endcomment %} {% comment %} Only show this menu option if the user has the perms to do so {% endcomment %}
{% if has_domains_portfolio_permission and request.user.has_view_suborganization %} {% if has_domains_portfolio_permission and has_view_suborganization %}
{% with url_name="domain-suborganization" %} {% with url_name="domain-suborganization" %}
{% include "includes/domain_sidenav_item.html" with item_text="Suborganization" %} {% include "includes/domain_sidenav_item.html" with item_text="Suborganization" %}
{% endwith %} {% endwith %}

View file

@ -157,7 +157,7 @@
<th data-sortable="name" scope="col" role="columnheader">Domain name</th> <th data-sortable="name" scope="col" role="columnheader">Domain name</th>
<th data-sortable="expiration_date" scope="col" role="columnheader">Expires</th> <th data-sortable="expiration_date" scope="col" role="columnheader">Expires</th>
<th data-sortable="state_display" scope="col" role="columnheader">Status</th> <th data-sortable="state_display" scope="col" role="columnheader">Status</th>
{% if has_domains_portfolio_permission and request.user.has_view_suborganization %} {% if has_domains_portfolio_permission and has_view_suborganization %}
<th data-sortable="suborganization" scope="col" role="columnheader">Suborganization</th> <th data-sortable="suborganization" scope="col" role="columnheader">Suborganization</th>
{% endif %} {% endif %}
<th <th

View file

@ -177,7 +177,7 @@ class DomainView(DomainBaseView):
if self.request.user.has_domains_portfolio_permission(): if self.request.user.has_domains_portfolio_permission():
if Domain.objects.filter(id=pk).exists(): if Domain.objects.filter(id=pk).exists():
domain = Domain.objects.get(id=pk) domain = Domain.objects.get(id=pk)
if domain.domain_info.portfolio == self.request.user.last_selected_portfolio: if domain.domain_info.portfolio == self.request.session["portfolio"]:
return True return True
return False return False
@ -236,7 +236,7 @@ class DomainOrgNameAddressView(DomainFormBaseView):
# Org users shouldn't have access to this page # Org users shouldn't have access to this page
is_org_user = self.request.user.is_org_user(self.request) is_org_user = self.request.user.is_org_user(self.request)
if self.request.user.last_selected_portfolio and is_org_user: if self.request.session["portfolio"] and is_org_user:
return False return False
else: else:
return super().has_permission() return super().has_permission()
@ -255,7 +255,7 @@ class DomainSubOrganizationView(DomainFormBaseView):
# non-org users shouldn't have access to this page # non-org users shouldn't have access to this page
is_org_user = self.request.user.is_org_user(self.request) is_org_user = self.request.user.is_org_user(self.request)
if self.request.user.last_selected_portfolio and is_org_user: if self.request.session["portfolio"] and is_org_user:
return super().has_permission() return super().has_permission()
else: else:
return False return False
@ -335,7 +335,7 @@ class DomainSeniorOfficialView(DomainFormBaseView):
# Org users shouldn't have access to this page # Org users shouldn't have access to this page
is_org_user = self.request.user.is_org_user(self.request) is_org_user = self.request.user.is_org_user(self.request)
if self.request.user.last_selected_portfolio and is_org_user: if self.request.session["portfolio"] and is_org_user:
return False return False
else: else:
return super().has_permission() return super().has_permission()

View file

@ -51,15 +51,14 @@ class PortfolioOrganizationView(PortfolioBasePermissionView, FormMixin):
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
"""Add additional context data to the template.""" """Add additional context data to the template."""
context = super().get_context_data(**kwargs) context = super().get_context_data(**kwargs)
context["has_edit_org_portfolio_permission"] = self.request.user.has_edit_org_portfolio_permission() context["has_edit_org_portfolio_permission"] = self.request.user.has_edit_org_portfolio_permission(self.request.session["portfolio"])
return context return context
def get_object(self, queryset=None): def get_object(self, queryset=None):
"""Get the portfolio object based on the request user.""" """Get the portfolio object based on the session."""
portfolio = self.request.user.last_selected_portfolio if self.request.session["portfolio"] is None:
if portfolio is None:
raise Http404("No organization found for this user") raise Http404("No organization found for this user")
return portfolio return self.request.session["portfolio"]
def get_form_kwargs(self): def get_form_kwargs(self):
"""Include the instance in the form kwargs.""" """Include the instance in the form kwargs."""
@ -111,11 +110,10 @@ class PortfolioSeniorOfficialView(PortfolioBasePermissionView, FormMixin):
context_object_name = "portfolio" context_object_name = "portfolio"
def get_object(self, queryset=None): def get_object(self, queryset=None):
"""Get the portfolio object based on the request user.""" """Get the portfolio object based on the session."""
portfolio = self.request.user.last_selected_portfolio if self.request.session["portfolio"] is None:
if portfolio is None:
raise Http404("No organization found for this user") raise Http404("No organization found for this user")
return portfolio return self.request.session["portfolio"]
def get_form_kwargs(self): def get_form_kwargs(self):
"""Include the instance in the form kwargs.""" """Include the instance in the form kwargs."""

View file

@ -419,7 +419,7 @@ class PortfolioBasePermission(PermissionsLoginMixin):
if not self.request.user.is_authenticated: if not self.request.user.is_authenticated:
return False return False
return self.request.user.has_base_portfolio_permission() return self.request.user.has_base_portfolio_permission(self.request.session["portfolio"])
class PortfolioDomainsPermission(PortfolioBasePermission): class PortfolioDomainsPermission(PortfolioBasePermission):
@ -434,7 +434,7 @@ class PortfolioDomainsPermission(PortfolioBasePermission):
if not self.request.user.is_authenticated: if not self.request.user.is_authenticated:
return False return False
return self.request.user.has_domains_portfolio_permission() return self.request.user.has_domains_portfolio_permission(self.request.session["portfolio"])
class PortfolioDomainRequestsPermission(PortfolioBasePermission): class PortfolioDomainRequestsPermission(PortfolioBasePermission):
@ -449,4 +449,4 @@ class PortfolioDomainRequestsPermission(PortfolioBasePermission):
if not self.request.user.is_authenticated: if not self.request.user.is_authenticated:
return False return False
return self.request.user.has_domain_requests_portfolio_permission() return self.request.user.has_domain_requests_portfolio_permission(self.request.session["portfolio"])