From 6c4eb667bd202050a44c6f4ff2df2d6b63655c32 Mon Sep 17 00:00:00 2001 From: Alysia Broddrick Date: Mon, 6 May 2024 16:55:19 -0700 Subject: [PATCH 01/46] script add to rotate login cert, needs to get current creds --- ops/scripts/rotate_login_certs.sh | 39 +++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100755 ops/scripts/rotate_login_certs.sh diff --git a/ops/scripts/rotate_login_certs.sh b/ops/scripts/rotate_login_certs.sh new file mode 100755 index 000000000..29fed2a50 --- /dev/null +++ b/ops/scripts/rotate_login_certs.sh @@ -0,0 +1,39 @@ +# This script rotates the login.gov credentials, DJANGO_SECRET_KEY and DJANGO_SECRET_LOGIN_KEY that allow for identity sandbox to work on sandboxes and local. +# The echo prints in this script should serve for documentation for running manually. +# NOTE: This script was written for MacOS and to be run at the root directory. + +if [ -z "$1" ]; then + echo 'Please specify a new space to create (i.e. lmm)' >&2 + exit 1 +fi + +if [ ! $(command -v jq) ] || [ ! $(command -v cf) ]; then + echo "jq, and cf packages must be installed. Please install via your preferred manager." + exit 1 +fi + +cf target -o cisa-dotgov + +read -p "Are you logged in to the cisa-dotgov CF org above? (y/n) " -n 1 -r +echo +if [[ ! $REPLY =~ ^[Yy]$ ]] +then + cf login -a https://api.fr.cloud.gov --sso +fi +echo "targeting space" +cf target -o "cisa-dotgov" -s $1 + +echo "Creating new login.gov credentials for $1..." +django_key=$(python3 -c 'from django.core.management.utils import get_random_secret_key; print(get_random_secret_key())') +openssl req -nodes -x509 -days 365 -newkey rsa:2048 -keyout private-$1.pem -out public-$1.crt +login_key=$(base64 -i private-$1.pem) +jq -n --arg django_key "$django_key" --arg login_key "$login_key" '{"DJANGO_SECRET_KEY":$django_key,"DJANGO_SECRET_LOGIN_KEY":$login_key}' > credentials-$1.json +# cf uups getgov-credentials -p credentials-$1.json + +# echo "Now you will need to update some things for Login. Please sign-in to https://dashboard.int.identitysandbox.gov/." +# echo "Navigate to our application config: https://dashboard.int.identitysandbox.gov/service_providers/2640/edit?" +# echo "There are two things to update." +# echo "1. Remove the old cert associated with the user's email (under Public Certificates)" +# echo "2. You need to upload the public-$1.crt file generated as part of the previous command. See the "choose cert file" button under Public Certificates." + +# echo "Then, tell the developer to update their local .env file by retreiving their credentials from the sandbox" From b0435e4f2e1bb1cf2cb8e0d9ddf60b56aa8c02b1 Mon Sep 17 00:00:00 2001 From: Alysia Broddrick Date: Mon, 19 Aug 2024 09:32:43 -0700 Subject: [PATCH 02/46] updated script --- ops/scripts/rotate_login_certs.sh | 27 +++++++++++++++++++-------- 1 file changed, 19 insertions(+), 8 deletions(-) diff --git a/ops/scripts/rotate_login_certs.sh b/ops/scripts/rotate_login_certs.sh index 29fed2a50..a68c053e2 100755 --- a/ops/scripts/rotate_login_certs.sh +++ b/ops/scripts/rotate_login_certs.sh @@ -2,10 +2,17 @@ # The echo prints in this script should serve for documentation for running manually. # NOTE: This script was written for MacOS and to be run at the root directory. + if [ -z "$1" ]; then echo 'Please specify a new space to create (i.e. lmm)' >&2 exit 1 fi +echo "You need access to the login partner dashboard, otherwise you will not be able to complete the steps in this script (https://dashboard.int.identitysandbox.gov/service_providers/2640)" +read -p " Do you have access to the partner dashboard mentioned above? (y/n) " -n 1 -r +echo +if [[ ! $REPLY =~ ^[Yy]$ ]]; then + exit 1 +fi if [ ! $(command -v jq) ] || [ ! $(command -v cf) ]; then echo "jq, and cf packages must be installed. Please install via your preferred manager." @@ -27,13 +34,17 @@ echo "Creating new login.gov credentials for $1..." django_key=$(python3 -c 'from django.core.management.utils import get_random_secret_key; print(get_random_secret_key())') openssl req -nodes -x509 -days 365 -newkey rsa:2048 -keyout private-$1.pem -out public-$1.crt login_key=$(base64 -i private-$1.pem) -jq -n --arg django_key "$django_key" --arg login_key "$login_key" '{"DJANGO_SECRET_KEY":$django_key,"DJANGO_SECRET_LOGIN_KEY":$login_key}' > credentials-$1.json -# cf uups getgov-credentials -p credentials-$1.json -# echo "Now you will need to update some things for Login. Please sign-in to https://dashboard.int.identitysandbox.gov/." -# echo "Navigate to our application config: https://dashboard.int.identitysandbox.gov/service_providers/2640/edit?" -# echo "There are two things to update." -# echo "1. Remove the old cert associated with the user's email (under Public Certificates)" -# echo "2. You need to upload the public-$1.crt file generated as part of the previous command. See the "choose cert file" button under Public Certificates." +echo "Creating the final json" +cf env getgov-$1 | awk '/VCAP_SERVICES: /,/^$/' | sed s/VCAP_SERVICES:// | jq '."user-provided"[0].credentials' | jq --arg django_key "$django_key" --arg login_key "$login_key" '. + {"DJANGO_SECRET_KEY":$django_key, "DJANGO_SECRET_LOGIN_KEY":$login_key}' > credentials-$1.json -# echo "Then, tell the developer to update their local .env file by retreiving their credentials from the sandbox" +echo "Updating creds on the sandbox" +cf uups getgov-credentials -p credentials-$1.json +cf restage getgov-$1 --strategy rolling + +echo "Now you will need to update some things for Login. Please sign-in to https://dashboard.int.identitysandbox.gov/." +echo "Navigate to our application config: https://dashboard.int.identitysandbox.gov/service_providers/2640/edit?" +echo "There are two things to update." +echo "1. Remove the old cert associated with the user's email (under Public Certificates)" +echo "2. You need to upload the public-$1.crt file generated as part of the previous command. See the "choose cert file" button under Public Certificates." +echo "Then, tell the developer to update their local .env file by retreiving their credentials from the sandbox" From f4c6c084a6804c3fe6fa7ddcf3bb6a2e3985af42 Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Thu, 31 Oct 2024 13:54:27 -0600 Subject: [PATCH 03/46] kind of a solution Doesn't account for clicking the button after page refresh --- src/registrar/config/settings.py | 1 + src/registrar/registrar_middleware.py | 40 ++++++++++++++++++++++++++- src/registrar/views/domain_request.py | 17 +++++++----- src/registrar/views/index.py | 1 - src/registrar/views/portfolios.py | 2 -- src/registrar/views/utility/mixins.py | 2 +- 6 files changed, 51 insertions(+), 12 deletions(-) diff --git a/src/registrar/config/settings.py b/src/registrar/config/settings.py index da58eee86..f931dad5e 100644 --- a/src/registrar/config/settings.py +++ b/src/registrar/config/settings.py @@ -193,6 +193,7 @@ MIDDLEWARE = [ "waffle.middleware.WaffleMiddleware", "registrar.registrar_middleware.CheckUserProfileMiddleware", "registrar.registrar_middleware.CheckPortfolioMiddleware", + "registrar.registrar_middleware.NewRequestMiddleware" ] # application object used by Django's built-in servers (e.g. `runserver`) diff --git a/src/registrar/registrar_middleware.py b/src/registrar/registrar_middleware.py index 2ccea9321..d8340421a 100644 --- a/src/registrar/registrar_middleware.py +++ b/src/registrar/registrar_middleware.py @@ -8,7 +8,8 @@ from django.urls import reverse from django.http import HttpResponseRedirect from registrar.models import User from waffle.decorators import flag_is_active - +from django.urls import resolve, Resolver404 +from registrar.config.urls import DOMAIN_REQUEST_NAMESPACE from registrar.models.utility.generic_helper import replace_url_queryparams logger = logging.getLogger(__name__) @@ -169,3 +170,40 @@ class CheckPortfolioMiddleware: request.session["portfolio"] = request.user.get_first_portfolio() else: request.session["portfolio"] = request.user.get_first_portfolio() + + +class NewRequestMiddleware: + def __init__(self, get_response): + self.get_response = get_response + + def __call__(self, request): + response = self.get_response(request) + return response + + def process_view(self, request, view_func, view_args, view_kwargs): + if not request.user.is_authenticated: + return None + + if request.session.get("new_request") is None: + request.session["new_request"] = True + + resolved = resolve(request.path) + if request.session.get("new_request") is False: + try: + resolved = resolve(request.path) + # Check if we're in the domain-request namespace. + # If not, then a new request is not being made. + if resolved.namespace != DOMAIN_REQUEST_NAMESPACE: + request.session["new_request"] = True + # URL doesn't match any known pattern. + # This shouldn't happen (caught before this), but redundancy is good. + except Resolver404: + # If you somehow see this log, something must have went very, *very* wrong. + # All I can offer in consolidation is this ASCII cat to tend to these hard times: + # ⠀ /l、 + # (゚、 。 7 + # ⠀ l、゙ ~ヽ + #   じしf_, )ノ + logger.error("[CRITICAL] NewRequestMiddleware => Could not resolve the request path.") + + return None diff --git a/src/registrar/views/domain_request.py b/src/registrar/views/domain_request.py index 7bbe78775..d74897266 100644 --- a/src/registrar/views/domain_request.py +++ b/src/registrar/views/domain_request.py @@ -308,6 +308,13 @@ class DomainRequestWizard(DomainRequestWizardPermissionView, TemplateView): if current_url == self.EDIT_URL_NAME and "id" in kwargs: del self.storage self.storage["domain_request_id"] = kwargs["id"] + elif self.request.session.get("new_request") is True and current_url != self.NEW_URL_NAME and current_url != "": + print(f"what is the url: {current_url} vs type: {type(current_url)}") + # Add some popup here that indicates a new request was started... + logger.info(f"DomainRequestWizard => user {request.user} was redirected to home (because, etc...)") + del self.storage + return HttpResponseRedirect(reverse("home")) + # if accessing this class directly, redirect to either to an acknowledgement # page or to the first step in the processes (if an edit rather than a new request); @@ -493,10 +500,8 @@ class DomainRequestWizard(DomainRequestWizardPermissionView, TemplateView): return request_step_list(self, self.get_step_enum()) def goto(self, step): - if step == "generic_org_type" or step == "portfolio_requesting_entity": - # We need to avoid creating a new domain request if the user - # clicks the back button - self.request.session["new_request"] = False + # We need to avoid creating a new domain request if the user clicks the back button + self.request.session["new_request"] = False self.steps.current = step return redirect(reverse(f"{self.URL_NAMESPACE}:{step}")) @@ -524,9 +529,6 @@ class DomainRequestWizard(DomainRequestWizardPermissionView, TemplateView): # which button did the user press? button: str = request.POST.get("submit_button", "") - if "new_request" not in request.session: - request.session["new_request"] = True - # if user has acknowledged the intro message if button == "intro_acknowledge": # Split into a function: C901 'DomainRequestWizard.post' is too complex (11) @@ -564,6 +566,7 @@ class DomainRequestWizard(DomainRequestWizardPermissionView, TemplateView): def handle_intro_acknowledge(self, request): """If we are starting a new request, clear storage and redirect to the first step""" + print(f"path info is: {request.path_info}") if request.path_info == self.NEW_URL_NAME: if self.request.session["new_request"] is True: del self.storage diff --git a/src/registrar/views/index.py b/src/registrar/views/index.py index 53900a4a7..7019c8db3 100644 --- a/src/registrar/views/index.py +++ b/src/registrar/views/index.py @@ -7,7 +7,6 @@ def index(request): if request and request.user and request.user.is_authenticated: # This controls the creation of a new domain request in the wizard - request.session["new_request"] = True context["user_domain_count"] = request.user.get_user_domain_ids(request).count() return render(request, "home.html", context) diff --git a/src/registrar/views/portfolios.py b/src/registrar/views/portfolios.py index 6fb976d5c..42158737a 100644 --- a/src/registrar/views/portfolios.py +++ b/src/registrar/views/portfolios.py @@ -46,8 +46,6 @@ class PortfolioDomainRequestsView(PortfolioDomainRequestsPermissionView, View): template_name = "portfolio_requests.html" def get(self, request): - if self.request.user.is_authenticated: - request.session["new_request"] = True return render(request, "portfolio_requests.html") diff --git a/src/registrar/views/utility/mixins.py b/src/registrar/views/utility/mixins.py index c1cf97d82..31913691c 100644 --- a/src/registrar/views/utility/mixins.py +++ b/src/registrar/views/utility/mixins.py @@ -401,7 +401,7 @@ class DomainRequestWizardPermission(PermissionsLoginMixin): id = self.kwargs.get("id") if hasattr(self, "kwargs") else None if not id: domain_request_wizard = self.request.session.get("wizard_domain_request") - if domain_request_wizard: + if domain_request_wizard and self.request.session.get("new_request") is False: id = domain_request_wizard.get("domain_request_id") # If no id is provided, we can assume that the user is starting a new request. From e19923f86fd30a263b945b869cf1eaa86a28a1e2 Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Thu, 31 Oct 2024 13:54:43 -0600 Subject: [PATCH 04/46] Revert "kind of a solution" This reverts commit f4c6c084a6804c3fe6fa7ddcf3bb6a2e3985af42. --- src/registrar/config/settings.py | 1 - src/registrar/registrar_middleware.py | 40 +-------------------------- src/registrar/views/domain_request.py | 17 +++++------- src/registrar/views/index.py | 1 + src/registrar/views/portfolios.py | 2 ++ src/registrar/views/utility/mixins.py | 2 +- 6 files changed, 12 insertions(+), 51 deletions(-) diff --git a/src/registrar/config/settings.py b/src/registrar/config/settings.py index f931dad5e..da58eee86 100644 --- a/src/registrar/config/settings.py +++ b/src/registrar/config/settings.py @@ -193,7 +193,6 @@ MIDDLEWARE = [ "waffle.middleware.WaffleMiddleware", "registrar.registrar_middleware.CheckUserProfileMiddleware", "registrar.registrar_middleware.CheckPortfolioMiddleware", - "registrar.registrar_middleware.NewRequestMiddleware" ] # application object used by Django's built-in servers (e.g. `runserver`) diff --git a/src/registrar/registrar_middleware.py b/src/registrar/registrar_middleware.py index d8340421a..2ccea9321 100644 --- a/src/registrar/registrar_middleware.py +++ b/src/registrar/registrar_middleware.py @@ -8,8 +8,7 @@ from django.urls import reverse from django.http import HttpResponseRedirect from registrar.models import User from waffle.decorators import flag_is_active -from django.urls import resolve, Resolver404 -from registrar.config.urls import DOMAIN_REQUEST_NAMESPACE + from registrar.models.utility.generic_helper import replace_url_queryparams logger = logging.getLogger(__name__) @@ -170,40 +169,3 @@ class CheckPortfolioMiddleware: request.session["portfolio"] = request.user.get_first_portfolio() else: request.session["portfolio"] = request.user.get_first_portfolio() - - -class NewRequestMiddleware: - def __init__(self, get_response): - self.get_response = get_response - - def __call__(self, request): - response = self.get_response(request) - return response - - def process_view(self, request, view_func, view_args, view_kwargs): - if not request.user.is_authenticated: - return None - - if request.session.get("new_request") is None: - request.session["new_request"] = True - - resolved = resolve(request.path) - if request.session.get("new_request") is False: - try: - resolved = resolve(request.path) - # Check if we're in the domain-request namespace. - # If not, then a new request is not being made. - if resolved.namespace != DOMAIN_REQUEST_NAMESPACE: - request.session["new_request"] = True - # URL doesn't match any known pattern. - # This shouldn't happen (caught before this), but redundancy is good. - except Resolver404: - # If you somehow see this log, something must have went very, *very* wrong. - # All I can offer in consolidation is this ASCII cat to tend to these hard times: - # ⠀ /l、 - # (゚、 。 7 - # ⠀ l、゙ ~ヽ - #   じしf_, )ノ - logger.error("[CRITICAL] NewRequestMiddleware => Could not resolve the request path.") - - return None diff --git a/src/registrar/views/domain_request.py b/src/registrar/views/domain_request.py index d74897266..7bbe78775 100644 --- a/src/registrar/views/domain_request.py +++ b/src/registrar/views/domain_request.py @@ -308,13 +308,6 @@ class DomainRequestWizard(DomainRequestWizardPermissionView, TemplateView): if current_url == self.EDIT_URL_NAME and "id" in kwargs: del self.storage self.storage["domain_request_id"] = kwargs["id"] - elif self.request.session.get("new_request") is True and current_url != self.NEW_URL_NAME and current_url != "": - print(f"what is the url: {current_url} vs type: {type(current_url)}") - # Add some popup here that indicates a new request was started... - logger.info(f"DomainRequestWizard => user {request.user} was redirected to home (because, etc...)") - del self.storage - return HttpResponseRedirect(reverse("home")) - # if accessing this class directly, redirect to either to an acknowledgement # page or to the first step in the processes (if an edit rather than a new request); @@ -500,8 +493,10 @@ class DomainRequestWizard(DomainRequestWizardPermissionView, TemplateView): return request_step_list(self, self.get_step_enum()) def goto(self, step): - # We need to avoid creating a new domain request if the user clicks the back button - self.request.session["new_request"] = False + if step == "generic_org_type" or step == "portfolio_requesting_entity": + # We need to avoid creating a new domain request if the user + # clicks the back button + self.request.session["new_request"] = False self.steps.current = step return redirect(reverse(f"{self.URL_NAMESPACE}:{step}")) @@ -529,6 +524,9 @@ class DomainRequestWizard(DomainRequestWizardPermissionView, TemplateView): # which button did the user press? button: str = request.POST.get("submit_button", "") + if "new_request" not in request.session: + request.session["new_request"] = True + # if user has acknowledged the intro message if button == "intro_acknowledge": # Split into a function: C901 'DomainRequestWizard.post' is too complex (11) @@ -566,7 +564,6 @@ class DomainRequestWizard(DomainRequestWizardPermissionView, TemplateView): def handle_intro_acknowledge(self, request): """If we are starting a new request, clear storage and redirect to the first step""" - print(f"path info is: {request.path_info}") if request.path_info == self.NEW_URL_NAME: if self.request.session["new_request"] is True: del self.storage diff --git a/src/registrar/views/index.py b/src/registrar/views/index.py index 7019c8db3..53900a4a7 100644 --- a/src/registrar/views/index.py +++ b/src/registrar/views/index.py @@ -7,6 +7,7 @@ def index(request): if request and request.user and request.user.is_authenticated: # This controls the creation of a new domain request in the wizard + request.session["new_request"] = True context["user_domain_count"] = request.user.get_user_domain_ids(request).count() return render(request, "home.html", context) diff --git a/src/registrar/views/portfolios.py b/src/registrar/views/portfolios.py index 42158737a..6fb976d5c 100644 --- a/src/registrar/views/portfolios.py +++ b/src/registrar/views/portfolios.py @@ -46,6 +46,8 @@ class PortfolioDomainRequestsView(PortfolioDomainRequestsPermissionView, View): template_name = "portfolio_requests.html" def get(self, request): + if self.request.user.is_authenticated: + request.session["new_request"] = True return render(request, "portfolio_requests.html") diff --git a/src/registrar/views/utility/mixins.py b/src/registrar/views/utility/mixins.py index 31913691c..c1cf97d82 100644 --- a/src/registrar/views/utility/mixins.py +++ b/src/registrar/views/utility/mixins.py @@ -401,7 +401,7 @@ class DomainRequestWizardPermission(PermissionsLoginMixin): id = self.kwargs.get("id") if hasattr(self, "kwargs") else None if not id: domain_request_wizard = self.request.session.get("wizard_domain_request") - if domain_request_wizard and self.request.session.get("new_request") is False: + if domain_request_wizard: id = domain_request_wizard.get("domain_request_id") # If no id is provided, we can assume that the user is starting a new request. From aa930eb77c1742694c3506008d7936af403fcdca Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Thu, 31 Oct 2024 15:10:12 -0600 Subject: [PATCH 05/46] use id instead of session for domain requests --- src/registrar/config/urls.py | 9 +++-- src/registrar/registrar_middleware.py | 2 +- .../templates/domain_request_form.html | 2 +- .../templates/domain_request_intro.html | 2 +- .../templates/domain_request_sidebar.html | 2 +- src/registrar/templates/home.html | 7 +--- .../templates/includes/header_extended.html | 2 +- .../portfolio_request_review_steps.html | 2 +- .../includes/request_review_steps.html | 2 +- .../templates/portfolio_requests.html | 8 +--- src/registrar/tests/test_views.py | 4 +- src/registrar/tests/test_views_request.py | 38 +++++++++---------- src/registrar/views/domain_request.py | 20 ++++------ src/registrar/views/index.py | 1 - src/registrar/views/portfolios.py | 2 - src/registrar/views/user_profile.py | 2 +- 16 files changed, 46 insertions(+), 59 deletions(-) diff --git a/src/registrar/config/urls.py b/src/registrar/config/urls.py index f61e31e54..d1551c9da 100644 --- a/src/registrar/config/urls.py +++ b/src/registrar/config/urls.py @@ -39,10 +39,11 @@ from registrar.views.utility import always_404 from api.views import available, rdap, get_current_federal, get_current_full DOMAIN_REQUEST_NAMESPACE = views.DomainRequestWizard.URL_NAMESPACE -domain_request_urls = [ - path("", views.DomainRequestWizard.as_view(), name=""), +domain_request_start_and_finished_urls = [ + path("start/", views.DomainRequestWizard.as_view(), name="start"), path("finished/", views.Finished.as_view(), name="finished"), ] +domain_request_urls = [] # dynamically generate the other domain_request_urls for step, view in [ @@ -253,7 +254,9 @@ urlpatterns = [ ), path("health", views.health, name="health"), path("openid/", include("djangooidc.urls")), - path("request/", include((domain_request_urls, DOMAIN_REQUEST_NAMESPACE))), + path("request/start/", views.DomainRequestWizard.as_view(), name="start"), + #path("request/", include((domain_request_start_and_finished_urls, DOMAIN_REQUEST_NAMESPACE))), + path("request//", include((domain_request_urls, DOMAIN_REQUEST_NAMESPACE))), path("api/v1/available/", available, name="available"), path("api/v1/rdap/", rdap, name="rdap"), path("api/v1/get-report/current-federal", get_current_federal, name="get-current-federal"), diff --git a/src/registrar/registrar_middleware.py b/src/registrar/registrar_middleware.py index 2ccea9321..bb6871803 100644 --- a/src/registrar/registrar_middleware.py +++ b/src/registrar/registrar_middleware.py @@ -100,7 +100,7 @@ class CheckUserProfileMiddleware: # In some cases, we don't want to redirect to home. This handles that. # Can easily be generalized if need be, but for now lets keep this easy to read. - custom_redirect = "domain-request:" if request.path == "/request/" else None + custom_redirect = "domain-request:start" if request.path == "/request/" else None # Don't redirect on excluded pages (such as the setup page itself) if not any(request.path.startswith(page) for page in self._get_excluded_pages(profile_page)): diff --git a/src/registrar/templates/domain_request_form.html b/src/registrar/templates/domain_request_form.html index 9228e51db..fe1d816e6 100644 --- a/src/registrar/templates/domain_request_form.html +++ b/src/registrar/templates/domain_request_form.html @@ -11,7 +11,7 @@
{% if steps.prev %} - + Previous step diff --git a/src/registrar/templates/domain_request_intro.html b/src/registrar/templates/domain_request_intro.html index 6b5223991..dd5b7ec6e 100644 --- a/src/registrar/templates/domain_request_intro.html +++ b/src/registrar/templates/domain_request_intro.html @@ -21,7 +21,7 @@

If you have all the information you need, completing your domain request might take around 15 minutes.

How we’ll reach you

-

While reviewing your domain request, we may need to reach out with questions. We’ll also email you when we complete our review. If the contact information below is not correct, visit your profile to make updates.

+

While reviewing your domain request, we may need to reach out with questions. We’ll also email you when we complete our review. If the contact information below is not correct, visit your profile to make updates.

{% include "includes/profile_information.html" with user=user%} diff --git a/src/registrar/templates/domain_request_sidebar.html b/src/registrar/templates/domain_request_sidebar.html index f9ca5397d..efdfe391e 100644 --- a/src/registrar/templates/domain_request_sidebar.html +++ b/src/registrar/templates/domain_request_sidebar.html @@ -15,7 +15,7 @@ {% endif %} {% endif %} - Manage your domains - {% comment %} - IMPORTANT: - If this button is added on any other page, make sure to update the - relevant view to reset request.session["new_request"] = True - {% endcomment %}

- Start a new domain request diff --git a/src/registrar/templates/includes/header_extended.html b/src/registrar/templates/includes/header_extended.html index 23b7d1be3..b9846277a 100644 --- a/src/registrar/templates/includes/header_extended.html +++ b/src/registrar/templates/includes/header_extended.html @@ -72,7 +72,7 @@ >

  • - Start a new domain request
  • diff --git a/src/registrar/templates/includes/portfolio_request_review_steps.html b/src/registrar/templates/includes/portfolio_request_review_steps.html index 9d3c5bdeb..b8d86b420 100644 --- a/src/registrar/templates/includes/portfolio_request_review_steps.html +++ b/src/registrar/templates/includes/portfolio_request_review_steps.html @@ -4,7 +4,7 @@ {% for step in steps %}
    {% if is_editable %} - {% namespaced_url 'domain-request' step as domain_request_url %} + {% namespaced_url 'domain-request' step pk=domain_request_id as domain_request_url %} {% endif %} {% if step == Step.REQUESTING_ENTITY %} diff --git a/src/registrar/templates/includes/request_review_steps.html b/src/registrar/templates/includes/request_review_steps.html index db1743b34..e0f76b27e 100644 --- a/src/registrar/templates/includes/request_review_steps.html +++ b/src/registrar/templates/includes/request_review_steps.html @@ -4,7 +4,7 @@ {% for step in steps %}
    {% if is_editable %} - {% namespaced_url 'domain-request' step as domain_request_url %} + {% namespaced_url 'domain-request' step pk=domain_request_id as domain_request_url %} {% endif %} {% if step == Step.ORGANIZATION_TYPE %} diff --git a/src/registrar/templates/portfolio_requests.html b/src/registrar/templates/portfolio_requests.html index d21bbcc4e..0eea5f6bd 100644 --- a/src/registrar/templates/portfolio_requests.html +++ b/src/registrar/templates/portfolio_requests.html @@ -22,13 +22,9 @@

    Domain requests can only be modified by the person who created the request.

    - {% comment %} - IMPORTANT: - If this button is added on any other page, make sure to update the - relevant view to reset request.session["new_request"] = True - {% endcomment %} +

    - Start a new domain request diff --git a/src/registrar/tests/test_views.py b/src/registrar/tests/test_views.py index 85ff5edd6..c1e859648 100644 --- a/src/registrar/tests/test_views.py +++ b/src/registrar/tests/test_views.py @@ -718,7 +718,7 @@ class FinishUserProfileTests(TestWithUser, WebTest): self.app.set_user(incomplete_regular_user.username) with override_flag("", active=True): # This will redirect the user to the setup page - finish_setup_page = self.app.get(reverse("domain-request:")).follow() + finish_setup_page = self.app.get(reverse("domain-request:start")).follow() self._set_session_cookie() # Assert that we're on the right page @@ -927,7 +927,7 @@ class UserProfileTests(TestWithUser, WebTest): def test_user_profile_back_button_when_coming_from_domain_request(self): """tests user profile, and when they are redirected from the domain request page""" - response = self.client.get("/user-profile?redirect=domain-request:") + response = self.client.get("/user-profile?redirect=domain-request:start") self.assertContains(response, "Your profile") self.assertContains(response, "Go back to your domain request") self.assertNotContains(response, "Back to manage your domains") diff --git a/src/registrar/tests/test_views_request.py b/src/registrar/tests/test_views_request.py index 17e6bcbe6..9b676e850 100644 --- a/src/registrar/tests/test_views_request.py +++ b/src/registrar/tests/test_views_request.py @@ -54,7 +54,7 @@ class DomainRequestTests(TestWithUser, WebTest): @less_console_noise_decorator def test_domain_request_form_intro_acknowledgement(self): """Tests that user is presented with intro acknowledgement page""" - intro_page = self.app.get(reverse("domain-request:")) + intro_page = self.app.get(reverse("domain-request:start")) self.assertContains(intro_page, "You’re about to start your .gov domain request") @less_console_noise_decorator @@ -110,7 +110,7 @@ class DomainRequestTests(TestWithUser, WebTest): @less_console_noise_decorator def test_domain_request_form_empty_submit(self): """Tests empty submit on the first page after the acknowledgement page""" - intro_page = self.app.get(reverse("domain-request:")) + intro_page = self.app.get(reverse("domain-request:start")) # django-webtest does not handle cookie-based sessions well because it keeps # resetting the session key on each new request, thus destroying the concept # of a "session". We are going to do it manually, saving the session ID here @@ -141,7 +141,7 @@ class DomainRequestTests(TestWithUser, WebTest): domain_request.save() # now, attempt to create another one - intro_page = self.app.get(reverse("domain-request:")) + intro_page = self.app.get(reverse("domain-request:start")) session_id = self.app.cookies[settings.SESSION_COOKIE_NAME] intro_form = intro_page.forms[0] self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) @@ -167,7 +167,7 @@ class DomainRequestTests(TestWithUser, WebTest): session_id = self.app.cookies[settings.SESSION_COOKIE_NAME] self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) - intro_page = self.app.get(reverse("domain-request:")) + intro_page = self.app.get(reverse("domain-request:start")) # Select the form intro_form = intro_page.forms[0] @@ -225,7 +225,7 @@ class DomainRequestTests(TestWithUser, WebTest): SKIPPED_PAGES = 3 num_pages = len(self.TITLES) - SKIPPED_PAGES - intro_page = self.app.get(reverse("domain-request:")) + intro_page = self.app.get(reverse("domain-request:start")) # django-webtest does not handle cookie-based sessions well because it keeps # resetting the session key on each new request, thus destroying the concept # of a "session". We are going to do it manually, saving the session ID here @@ -549,7 +549,7 @@ class DomainRequestTests(TestWithUser, WebTest): num_pages_tested = 0 # skipping elections, type_of_work, tribal_government - intro_page = self.app.get(reverse("domain-request:")) + intro_page = self.app.get(reverse("domain-request:start")) # django-webtest does not handle cookie-based sessions well because it keeps # resetting the session key on each new request, thus destroying the concept # of a "session". We are going to do it manually, saving the session ID here @@ -873,7 +873,7 @@ class DomainRequestTests(TestWithUser, WebTest): @less_console_noise_decorator def test_domain_request_form_conditional_federal(self): """Federal branch question is shown for federal organizations.""" - intro_page = self.app.get(reverse("domain-request:")) + intro_page = self.app.get(reverse("domain-request:start")) # django-webtest does not handle cookie-based sessions well because it keeps # resetting the session key on each new request, thus destroying the concept # of a "session". We are going to do it manually, saving the session ID here @@ -929,7 +929,7 @@ class DomainRequestTests(TestWithUser, WebTest): @less_console_noise_decorator def test_domain_request_form_conditional_elections(self): """Election question is shown for other organizations.""" - intro_page = self.app.get(reverse("domain-request:")) + intro_page = self.app.get(reverse("domain-request:start")) # django-webtest does not handle cookie-based sessions well because it keeps # resetting the session key on each new request, thus destroying the concept # of a "session". We are going to do it manually, saving the session ID here @@ -984,7 +984,7 @@ class DomainRequestTests(TestWithUser, WebTest): @less_console_noise_decorator def test_domain_request_form_section_skipping(self): """Can skip forward and back in sections""" - intro_page = self.app.get(reverse("domain-request:")) + intro_page = self.app.get(reverse("domain-request:start")) # django-webtest does not handle cookie-based sessions well because it keeps # resetting the session key on each new request, thus destroying the concept # of a "session". We are going to do it manually, saving the session ID here @@ -1029,7 +1029,7 @@ class DomainRequestTests(TestWithUser, WebTest): @less_console_noise_decorator def test_domain_request_form_nonfederal(self): """Non-federal organizations don't have to provide their federal agency.""" - intro_page = self.app.get(reverse("domain-request:")) + intro_page = self.app.get(reverse("domain-request:start")) # django-webtest does not handle cookie-based sessions well because it keeps # resetting the session key on each new request, thus destroying the concept # of a "session". We are going to do it manually, saving the session ID here @@ -1074,7 +1074,7 @@ class DomainRequestTests(TestWithUser, WebTest): @less_console_noise_decorator def test_domain_request_about_your_organization_special(self): """Special districts have to answer an additional question.""" - intro_page = self.app.get(reverse("domain-request:")) + intro_page = self.app.get(reverse("domain-request:start")) # django-webtest does not handle cookie-based sessions well because it keeps # resetting the session key on each new request, thus destroying the concept # of a "session". We are going to do it manually, saving the session ID here @@ -1104,7 +1104,7 @@ class DomainRequestTests(TestWithUser, WebTest): def test_federal_agency_dropdown_excludes_expected_values(self): """The Federal Agency dropdown on a domain request form should not include options for gov Administration and Non-Federal Agency""" - intro_page = self.app.get(reverse("domain-request:")) + intro_page = self.app.get(reverse("domain-request:start")) # django-webtest does not handle cookie-based sessions well because it keeps # resetting the session key on each new request, thus destroying the concept # of a "session". We are going to do it manually, saving the session ID here @@ -2303,7 +2303,7 @@ class DomainRequestTests(TestWithUser, WebTest): @less_console_noise_decorator def test_domain_request_about_your_organiztion_interstate(self): """Special districts have to answer an additional question.""" - intro_page = self.app.get(reverse("domain-request:")) + intro_page = self.app.get(reverse("domain-request:start")) # django-webtest does not handle cookie-based sessions well because it keeps # resetting the session key on each new request, thus destroying the concept # of a "session". We are going to do it manually, saving the session ID here @@ -2332,7 +2332,7 @@ class DomainRequestTests(TestWithUser, WebTest): @less_console_noise_decorator def test_domain_request_tribal_government(self): """Tribal organizations have to answer an additional question.""" - intro_page = self.app.get(reverse("domain-request:")) + intro_page = self.app.get(reverse("domain-request:start")) # django-webtest does not handle cookie-based sessions well because it keeps # resetting the session key on each new request, thus destroying the concept # of a "session". We are going to do it manually, saving the session ID here @@ -2363,7 +2363,7 @@ class DomainRequestTests(TestWithUser, WebTest): @less_console_noise_decorator def test_domain_request_so_dynamic_text(self): - intro_page = self.app.get(reverse("domain-request:")) + intro_page = self.app.get(reverse("domain-request:start")) # django-webtest does not handle cookie-based sessions well because it keeps # resetting the session key on each new request, thus destroying the concept # of a "session". We are going to do it manually, saving the session ID here @@ -2447,7 +2447,7 @@ class DomainRequestTests(TestWithUser, WebTest): @less_console_noise_decorator def test_domain_request_dotgov_domain_dynamic_text(self): - intro_page = self.app.get(reverse("domain-request:")) + intro_page = self.app.get(reverse("domain-request:start")) # django-webtest does not handle cookie-based sessions well because it keeps # resetting the session key on each new request, thus destroying the concept # of a "session". We are going to do it manually, saving the session ID here @@ -2712,7 +2712,7 @@ class DomainRequestTests(TestWithUser, WebTest): Make sure the long name is displaying in the domain request form, org step """ - intro_page = self.app.get(reverse("domain-request:")) + intro_page = self.app.get(reverse("domain-request:start")) # django-webtest does not handle cookie-based sessions well because it keeps # resetting the session key on each new request, thus destroying the concept # of a "session". We are going to do it manually, saving the session ID here @@ -2751,7 +2751,7 @@ class DomainRequestTests(TestWithUser, WebTest): user=self.user, portfolio=portfolio, roles=[UserPortfolioRoleChoices.ORGANIZATION_MEMBER] ) # This user should be forbidden from creating new domain requests - intro_page = self.app.get(reverse("domain-request:"), expect_errors=True) + intro_page = self.app.get(reverse("domain-request:start"), expect_errors=True) self.assertEqual(intro_page.status_code, 403) # This user should also be forbidden from editing existing ones @@ -2773,7 +2773,7 @@ class DomainRequestTests(TestWithUser, WebTest): ) # This user should be allowed to create new domain requests - intro_page = self.app.get(reverse("domain-request:")) + intro_page = self.app.get(reverse("domain-request:start")) self.assertEqual(intro_page.status_code, 200) # This user should also be allowed to edit existing ones diff --git a/src/registrar/views/domain_request.py b/src/registrar/views/domain_request.py index 7bbe78775..70fefab11 100644 --- a/src/registrar/views/domain_request.py +++ b/src/registrar/views/domain_request.py @@ -53,7 +53,7 @@ class DomainRequestWizard(DomainRequestWizardPermissionView, TemplateView): URL_NAMESPACE = "domain-request" # name for accessing /domain-request//edit EDIT_URL_NAME = "edit-domain-request" - NEW_URL_NAME = "/request/" + NEW_URL_NAME = "/request/start/" # region: Titles # We need to pass our human-readable step titles as context to the templates. @@ -315,6 +315,7 @@ class DomainRequestWizard(DomainRequestWizardPermissionView, TemplateView): # send users "to the domain request wizard" without needing to know which view # is first in the list of steps. if self.__class__ == DomainRequestWizard: + print(F"what is this? {request.path_info}") if request.path_info == self.NEW_URL_NAME: # Clear context so the prop getter won't create a request here. # Creating a request will be handled in the post method for the @@ -485,6 +486,7 @@ class DomainRequestWizard(DomainRequestWizardPermissionView, TemplateView): # Hides the requests and domains buttons in the navbar context_stuff["hide_requests"] = self.is_portfolio context_stuff["hide_domains"] = self.is_portfolio + context_stuff["domain_request_id"] = self.domain_request.id return context_stuff @@ -493,12 +495,12 @@ class DomainRequestWizard(DomainRequestWizardPermissionView, TemplateView): return request_step_list(self, self.get_step_enum()) def goto(self, step): - if step == "generic_org_type" or step == "portfolio_requesting_entity": - # We need to avoid creating a new domain request if the user - # clicks the back button - self.request.session["new_request"] = False self.steps.current = step - return redirect(reverse(f"{self.URL_NAMESPACE}:{step}")) + self.domain_request + # Get or create the domain request + domain_request = self.domain_request + test = self.storage.get("domain_request_id") + return redirect(reverse(f"{self.URL_NAMESPACE}:{step}", kwargs={"pk": domain_request.pk})) def goto_next_step(self): """Redirects to the next step.""" @@ -524,9 +526,6 @@ class DomainRequestWizard(DomainRequestWizardPermissionView, TemplateView): # which button did the user press? button: str = request.POST.get("submit_button", "") - if "new_request" not in request.session: - request.session["new_request"] = True - # if user has acknowledged the intro message if button == "intro_acknowledge": # Split into a function: C901 'DomainRequestWizard.post' is too complex (11) @@ -564,9 +563,6 @@ class DomainRequestWizard(DomainRequestWizardPermissionView, TemplateView): def handle_intro_acknowledge(self, request): """If we are starting a new request, clear storage and redirect to the first step""" - if request.path_info == self.NEW_URL_NAME: - if self.request.session["new_request"] is True: - del self.storage return self.goto(self.steps.first) def save(self, forms: list): diff --git a/src/registrar/views/index.py b/src/registrar/views/index.py index 53900a4a7..7019c8db3 100644 --- a/src/registrar/views/index.py +++ b/src/registrar/views/index.py @@ -7,7 +7,6 @@ def index(request): if request and request.user and request.user.is_authenticated: # This controls the creation of a new domain request in the wizard - request.session["new_request"] = True context["user_domain_count"] = request.user.get_user_domain_ids(request).count() return render(request, "home.html", context) diff --git a/src/registrar/views/portfolios.py b/src/registrar/views/portfolios.py index 6fb976d5c..42158737a 100644 --- a/src/registrar/views/portfolios.py +++ b/src/registrar/views/portfolios.py @@ -46,8 +46,6 @@ class PortfolioDomainRequestsView(PortfolioDomainRequestsPermissionView, View): template_name = "portfolio_requests.html" def get(self, request): - if self.request.user.is_authenticated: - request.session["new_request"] = True return render(request, "portfolio_requests.html") diff --git a/src/registrar/views/user_profile.py b/src/registrar/views/user_profile.py index 4d3b44366..2012d12ab 100644 --- a/src/registrar/views/user_profile.py +++ b/src/registrar/views/user_profile.py @@ -53,7 +53,7 @@ class UserProfileView(UserProfilePermissionView, FormMixin): context = super().get_context_data(**kwargs) # Set the profile_back_button_text based on the redirect parameter - if kwargs.get("redirect") == "domain-request:": + if kwargs.get("redirect") == "domain-request:start": context["profile_back_button_text"] = "Go back to your domain request" else: context["profile_back_button_text"] = "Go to manage your domains" From 37894e7ede4544b35fcdaa67e6364d598c85a9b1 Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Thu, 31 Oct 2024 15:20:50 -0600 Subject: [PATCH 06/46] fix session issue --- src/registrar/config/urls.py | 10 +++------- src/registrar/templates/domain_request_form.html | 2 +- src/registrar/templates/domain_request_sidebar.html | 2 +- .../includes/portfolio_request_review_steps.html | 2 +- .../templates/includes/request_review_steps.html | 2 +- src/registrar/views/domain_request.py | 8 +++----- 6 files changed, 10 insertions(+), 16 deletions(-) diff --git a/src/registrar/config/urls.py b/src/registrar/config/urls.py index d1551c9da..8ee89799f 100644 --- a/src/registrar/config/urls.py +++ b/src/registrar/config/urls.py @@ -39,13 +39,9 @@ from registrar.views.utility import always_404 from api.views import available, rdap, get_current_federal, get_current_full DOMAIN_REQUEST_NAMESPACE = views.DomainRequestWizard.URL_NAMESPACE -domain_request_start_and_finished_urls = [ - path("start/", views.DomainRequestWizard.as_view(), name="start"), - path("finished/", views.Finished.as_view(), name="finished"), -] -domain_request_urls = [] # dynamically generate the other domain_request_urls +domain_request_urls = [] for step, view in [ # add/remove steps here (Step.ORGANIZATION_TYPE, views.OrganizationType), @@ -255,8 +251,8 @@ urlpatterns = [ path("health", views.health, name="health"), path("openid/", include("djangooidc.urls")), path("request/start/", views.DomainRequestWizard.as_view(), name="start"), - #path("request/", include((domain_request_start_and_finished_urls, DOMAIN_REQUEST_NAMESPACE))), - path("request//", include((domain_request_urls, DOMAIN_REQUEST_NAMESPACE))), + path("request/finished/", views.Finished.as_view(), name="finished"), + path("request//", include((domain_request_urls, DOMAIN_REQUEST_NAMESPACE))), path("api/v1/available/", available, name="available"), path("api/v1/rdap/", rdap, name="rdap"), path("api/v1/get-report/current-federal", get_current_federal, name="get-current-federal"), diff --git a/src/registrar/templates/domain_request_form.html b/src/registrar/templates/domain_request_form.html index fe1d816e6..49c61c524 100644 --- a/src/registrar/templates/domain_request_form.html +++ b/src/registrar/templates/domain_request_form.html @@ -11,7 +11,7 @@

    {% if steps.prev %} - + Previous step diff --git a/src/registrar/templates/domain_request_sidebar.html b/src/registrar/templates/domain_request_sidebar.html index efdfe391e..1af54bb24 100644 --- a/src/registrar/templates/domain_request_sidebar.html +++ b/src/registrar/templates/domain_request_sidebar.html @@ -15,7 +15,7 @@ {% endif %} {% endif %} - {% if is_editable %} - {% namespaced_url 'domain-request' step pk=domain_request_id as domain_request_url %} + {% namespaced_url 'domain-request' step id=domain_request_id as domain_request_url %} {% endif %} {% if step == Step.REQUESTING_ENTITY %} diff --git a/src/registrar/templates/includes/request_review_steps.html b/src/registrar/templates/includes/request_review_steps.html index e0f76b27e..73b71d536 100644 --- a/src/registrar/templates/includes/request_review_steps.html +++ b/src/registrar/templates/includes/request_review_steps.html @@ -4,7 +4,7 @@ {% for step in steps %}
    {% if is_editable %} - {% namespaced_url 'domain-request' step pk=domain_request_id as domain_request_url %} + {% namespaced_url 'domain-request' step id=domain_request_id as domain_request_url %} {% endif %} {% if step == Step.ORGANIZATION_TYPE %} diff --git a/src/registrar/views/domain_request.py b/src/registrar/views/domain_request.py index 70fefab11..6039a570e 100644 --- a/src/registrar/views/domain_request.py +++ b/src/registrar/views/domain_request.py @@ -308,6 +308,8 @@ class DomainRequestWizard(DomainRequestWizardPermissionView, TemplateView): if current_url == self.EDIT_URL_NAME and "id" in kwargs: del self.storage self.storage["domain_request_id"] = kwargs["id"] + elif "id" not in kwargs: + del self.storage # if accessing this class directly, redirect to either to an acknowledgement # page or to the first step in the processes (if an edit rather than a new request); @@ -496,11 +498,7 @@ class DomainRequestWizard(DomainRequestWizardPermissionView, TemplateView): def goto(self, step): self.steps.current = step - self.domain_request - # Get or create the domain request - domain_request = self.domain_request - test = self.storage.get("domain_request_id") - return redirect(reverse(f"{self.URL_NAMESPACE}:{step}", kwargs={"pk": domain_request.pk})) + return redirect(reverse(f"{self.URL_NAMESPACE}:{step}", kwargs={"id": self.domain_request.id})) def goto_next_step(self): """Redirects to the next step.""" From 20a5774213afddf7780d5d3357022f16984b4d94 Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Thu, 31 Oct 2024 15:42:39 -0600 Subject: [PATCH 07/46] Update domain_request.py --- src/registrar/views/domain_request.py | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/src/registrar/views/domain_request.py b/src/registrar/views/domain_request.py index 6039a570e..57ca35628 100644 --- a/src/registrar/views/domain_request.py +++ b/src/registrar/views/domain_request.py @@ -158,6 +158,7 @@ class DomainRequestWizard(DomainRequestWizardPermissionView, TemplateView): # Configure titles, wizard_conditions, unlocking_steps, and steps self.configure_step_options() self._domain_request = None # for caching + self.domain_request_id = None def configure_step_options(self): """Changes which steps are available to the user based on self.is_portfolio. @@ -182,7 +183,8 @@ class DomainRequestWizard(DomainRequestWizardPermissionView, TemplateView): def has_pk(self): """Does this wizard know about a DomainRequest database record?""" - return "domain_request_id" in self.storage + + return self.kwargs.get("id") is not None def get_step_enum(self): """Determines which step enum we should use for the wizard""" @@ -214,11 +216,10 @@ class DomainRequestWizard(DomainRequestWizardPermissionView, TemplateView): raise ValueError("Invalid value for User") if self.has_pk(): - id = self.storage["domain_request_id"] try: self._domain_request = DomainRequest.objects.get( creator=creator, - pk=id, + pk=self.kwargs.get('id'), ) return self._domain_request except DomainRequest.DoesNotExist: @@ -239,7 +240,7 @@ class DomainRequestWizard(DomainRequestWizardPermissionView, TemplateView): else: self._domain_request = DomainRequest.objects.create(creator=self.request.user) - self.storage["domain_request_id"] = self._domain_request.id + self.kwargs["id"] = self._domain_request.id return self._domain_request @property @@ -295,6 +296,7 @@ class DomainRequestWizard(DomainRequestWizardPermissionView, TemplateView): def get(self, request, *args, **kwargs): """This method handles GET requests.""" + self.kwargs = kwargs if not self.is_portfolio and self.request.user.is_org_user(request): self.is_portfolio = True # Configure titles, wizard_conditions, unlocking_steps, and steps @@ -307,9 +309,6 @@ class DomainRequestWizard(DomainRequestWizardPermissionView, TemplateView): # and remove any prior wizard data from their session if current_url == self.EDIT_URL_NAME and "id" in kwargs: del self.storage - self.storage["domain_request_id"] = kwargs["id"] - elif "id" not in kwargs: - del self.storage # if accessing this class directly, redirect to either to an acknowledgement # page or to the first step in the processes (if an edit rather than a new request); @@ -317,7 +316,6 @@ class DomainRequestWizard(DomainRequestWizardPermissionView, TemplateView): # send users "to the domain request wizard" without needing to know which view # is first in the list of steps. if self.__class__ == DomainRequestWizard: - print(F"what is this? {request.path_info}") if request.path_info == self.NEW_URL_NAME: # Clear context so the prop getter won't create a request here. # Creating a request will be handled in the post method for the From 18e25b0aca1120e70e5647db986a0a3ff5cbb761 Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Fri, 1 Nov 2024 13:25:24 -0600 Subject: [PATCH 08/46] cleanup urls a bit --- src/registrar/config/urls.py | 11 ++++++----- src/registrar/templates/home.html | 2 +- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/src/registrar/config/urls.py b/src/registrar/config/urls.py index 06c722923..2d517a26e 100644 --- a/src/registrar/config/urls.py +++ b/src/registrar/config/urls.py @@ -41,7 +41,10 @@ from api.views import available, rdap, get_current_federal, get_current_full DOMAIN_REQUEST_NAMESPACE = views.DomainRequestWizard.URL_NAMESPACE # dynamically generate the other domain_request_urls -domain_request_urls = [] +domain_request_urls = [ + path("start/", views.DomainRequestWizard.as_view(), name="start"), + path("finished/", views.Finished.as_view(), name="finished"), +] for step, view in [ # add/remove steps here (Step.ORGANIZATION_TYPE, views.OrganizationType), @@ -62,7 +65,7 @@ for step, view in [ (PortfolioDomainRequestStep.REQUESTING_ENTITY, views.RequestingEntity), (PortfolioDomainRequestStep.ADDITIONAL_DETAILS, views.PortfolioAdditionalDetails), ]: - domain_request_urls.append(path(f"{step}/", view.as_view(), name=step)) + domain_request_urls.append(path(f"/{step}/", view.as_view(), name=step)) urlpatterns = [ @@ -255,9 +258,7 @@ urlpatterns = [ ), path("health", views.health, name="health"), path("openid/", include("djangooidc.urls")), - path("request/start/", views.DomainRequestWizard.as_view(), name="start"), - path("request/finished/", views.Finished.as_view(), name="finished"), - path("request//", include((domain_request_urls, DOMAIN_REQUEST_NAMESPACE))), + path("request/", include((domain_request_urls, DOMAIN_REQUEST_NAMESPACE))), path("api/v1/available/", available, name="available"), path("api/v1/rdap/", rdap, name="rdap"), path("api/v1/get-report/current-federal", get_current_federal, name="get-current-federal"), diff --git a/src/registrar/templates/home.html b/src/registrar/templates/home.html index 0fa077daa..b00c57b5c 100644 --- a/src/registrar/templates/home.html +++ b/src/registrar/templates/home.html @@ -18,7 +18,7 @@

    Manage your domains

    - Start a new domain request From 4ae764bf0b43ee3ab9d169545dec845a65c48127 Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Fri, 1 Nov 2024 15:05:07 -0600 Subject: [PATCH 09/46] (draft) get a domain id per session --- src/registrar/assets/js/get-gov.js | 27 +++++++++++++++++++ .../templates/domain_request_form.html | 1 + 2 files changed, 28 insertions(+) diff --git a/src/registrar/assets/js/get-gov.js b/src/registrar/assets/js/get-gov.js index adcc21d2a..15d6b1712 100644 --- a/src/registrar/assets/js/get-gov.js +++ b/src/registrar/assets/js/get-gov.js @@ -2820,3 +2820,30 @@ document.addEventListener('DOMContentLoaded', function() { // Add event listener to the suborg dropdown to show/hide the suborg details section select.addEventListener("change", () => toggleSuborganization()); })(); + + +(function handleBackButtonStuff() { + function handleStuff() { + const domainRequestId = document.getElementById("wizard-domain-request-id")?.value; + if (domainRequestId) { + sessionStorage.setItem("domainRequestId", domainRequestId); + console.log("Domain request ID stored:", domainRequestId); + } + } + + // Handle back button navigation and initial page load + function handleStartPage(event) { + if (window.location.pathname === "/request/start/") { + if (event && !event.persisted) return; + const domainRequestId = sessionStorage.getItem("domainRequestId"); + console.log("Domain request ID retrieved:", domainRequestId); + } + } + + // Listen for back/forward navigation + window.addEventListener('pageshow', handleStartPage); + + // Handle initial page load + handleStuff(); + handleStartPage(); +})(); diff --git a/src/registrar/templates/domain_request_form.html b/src/registrar/templates/domain_request_form.html index 49c61c524..db99bdf10 100644 --- a/src/registrar/templates/domain_request_form.html +++ b/src/registrar/templates/domain_request_form.html @@ -10,6 +10,7 @@

    + {% if steps.prev %}
    - {% if steps.prev %} + {% if steps.current == steps.first %} + {% if portfolio %} + + Back to domain requests + + {% else %} + + Back to home + + {% endif %} + {% elif steps.prev %} Back to domain requests - + {% url 'domain-requests' as url_2 %} {% else %} - - Back to home - + {% url 'home' as url_2 %} {% endif %} + {% elif steps.prev %} Previous step - {% comment %} - TODO: uncomment in #2596 - {% else %} - {% if portfolio %} - {% url 'domain-requests' as url_2 %} - - {% endif %} {% endcomment %} {% endif %} {% block form_messages %} From 5fa622ada136bfa6202bb30c153faf33aaa0dda7 Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Thu, 7 Nov 2024 12:41:57 -0700 Subject: [PATCH 30/46] Unit test --- src/registrar/tests/test_views_request.py | 63 +++++++++++++++++++++++ 1 file changed, 63 insertions(+) diff --git a/src/registrar/tests/test_views_request.py b/src/registrar/tests/test_views_request.py index e253d5ae6..13f7d12cc 100644 --- a/src/registrar/tests/test_views_request.py +++ b/src/registrar/tests/test_views_request.py @@ -2959,6 +2959,69 @@ class TestDomainRequestWizard(TestWithUser, WebTest): DomainRequest.objects.all().delete() DomainInformation.objects.all().delete() + @less_console_noise_decorator + def test_breadcrumb_navigation(self): + """ + Tests the breadcrumb navigation behavior in domain request wizard. + Ensures that: + - Breadcrumb shows correct text based on portfolio flag + - Links point to correct destinations + - Back button appears on appropriate steps + - Back button is not present on first step + """ + # Create initial domain request + domain_request = completed_domain_request( + status=DomainRequest.DomainRequestStatus.STARTED, + user=self.user, + ) + + # Test without portfolio flag + start_page = self.app.get(f"/domain-request/{domain_request.id}/edit/").follow() + session_id = self.app.cookies[settings.SESSION_COOKIE_NAME] + self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) + + # Check initial breadcrumb state. + # Ensure that the request name is shown if it exists, otherwise just show new domain request. + self.assertContains(start_page, '
      ') + self.assertContains(start_page, "city.gov") + self.assertContains(start_page, 'href="/"') + self.assertContains(start_page, "Manage your domains") + self.assertNotContains(start_page, "Previous step") + + # Move to next step + form = start_page.forms[0] + next_page = form.submit().follow() + self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) + + # Verify that the back button appears + self.assertContains(next_page, "Previous step") + self.assertContains(next_page, "#arrow_back") + + # Test with portfolio flag + with override_flag("organization_feature", active=True), override_flag("organization_requests", active=True): + portfolio = Portfolio.objects.create( + creator=self.user, + organization_name="test portfolio", + ) + permission = UserPortfolioPermission.objects.create( + user=self.user, + portfolio=portfolio, + roles=[UserPortfolioRoleChoices.ORGANIZATION_ADMIN], + ) + + # Check portfolio-specific breadcrumb + portfolio_page = self.app.get(f"/domain-request/{domain_request.id}/edit/").follow() + self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) + + self.assertContains(portfolio_page, "Domain requests") + + # Clean up portfolio + permission.delete() + portfolio.delete() + + # Clean up + domain_request.delete() + @less_console_noise_decorator def test_unlocked_steps_empty_domain_request(self): """Test when all fields in the domain request are empty.""" From 14f861b81edfe20b9c8c568289d0056dfc652686 Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Thu, 7 Nov 2024 12:44:14 -0700 Subject: [PATCH 31/46] Update test_views_request.py --- src/registrar/tests/test_views_request.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/registrar/tests/test_views_request.py b/src/registrar/tests/test_views_request.py index 13f7d12cc..a973e590f 100644 --- a/src/registrar/tests/test_views_request.py +++ b/src/registrar/tests/test_views_request.py @@ -3063,7 +3063,7 @@ class TestDomainRequestWizard(TestWithUser, WebTest): # 10 unlocked steps, one active step, the review step will have link_usa but not check_circle self.assertContains(detail_page, "#check_circle", count=9) # Type of organization - self.assertContains(detail_page, "usa-current", count=1) + self.assertContains(detail_page, "usa-current", count=2) self.assertContains(detail_page, "link_usa-checked", count=10) else: @@ -3125,7 +3125,7 @@ class TestDomainRequestWizard(TestWithUser, WebTest): # which unlocks if domain exists), one active step, the review step is locked self.assertContains(detail_page, "#check_circle", count=4) # Type of organization - self.assertContains(detail_page, "usa-current", count=1) + self.assertContains(detail_page, "usa-current", count=2) self.assertContains(detail_page, "link_usa-checked", count=4) else: @@ -3199,7 +3199,7 @@ class TestDomainRequestWizard(TestWithUser, WebTest): self.assertContains(detail_page, "#lock", 1) # The current option should be selected - self.assertContains(detail_page, "usa-current", count=1) + self.assertContains(detail_page, "usa-current", count=2) # We default to the requesting entity page expected_url = reverse("domain-request:portfolio_requesting_entity", kwargs={"id": domain_request.id}) From f5cc5a63495412a9fcc6677d45e825636c85d161 Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Thu, 7 Nov 2024 12:56:10 -0700 Subject: [PATCH 32/46] Update test_views_request.py --- src/registrar/tests/test_views_request.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/registrar/tests/test_views_request.py b/src/registrar/tests/test_views_request.py index a973e590f..a73fac5a8 100644 --- a/src/registrar/tests/test_views_request.py +++ b/src/registrar/tests/test_views_request.py @@ -3209,7 +3209,7 @@ class TestDomainRequestWizard(TestWithUser, WebTest): # We shouldn't show the "domains" and "domain requests" buttons # on this page. self.assertNotContains(detail_page, "Domains") - self.assertNotContains(detail_page, "Domain requests") + self.assertNotContains(detail_page, "Domain requests") else: self.fail(f"Expected a redirect, but got a different response: {response}") From dd574bc6a9d8e16f9fd9d3f3df9e0e3dc974d151 Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Fri, 8 Nov 2024 09:57:41 -0700 Subject: [PATCH 33/46] PR suggestions --- src/registrar/views/domain_request.py | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/src/registrar/views/domain_request.py b/src/registrar/views/domain_request.py index cde9ff417..6519a5bbc 100644 --- a/src/registrar/views/domain_request.py +++ b/src/registrar/views/domain_request.py @@ -183,9 +183,7 @@ class DomainRequestWizard(DomainRequestWizardPermissionView, TemplateView): def has_pk(self): """Does this wizard know about a DomainRequest database record?""" - if self.kwargs.get("id") is not None: - return True - return False + return bool(self.kwargs.get("id") is not None) def get_step_enum(self): """Determines which step enum we should use for the wizard""" @@ -450,7 +448,7 @@ class DomainRequestWizard(DomainRequestWizardPermissionView, TemplateView): if self.domain_request.requested_domain is not None: requested_domain_name = self.domain_request.requested_domain.name - context_stuff = {} + context = {} # Note: we will want to consolidate the non_org_steps_complete check into the same check that # org_steps_complete is using at some point. @@ -458,7 +456,7 @@ class DomainRequestWizard(DomainRequestWizardPermissionView, TemplateView): org_steps_complete = len(self.db_check_for_unlocking_steps()) == len(self.steps) if (not self.is_portfolio and non_org_steps_complete) or (self.is_portfolio and org_steps_complete): modal_button = '" - context_stuff = { + context = { "not_form": False, "form_titles": self.titles, "steps": self.steps, @@ -475,7 +473,7 @@ class DomainRequestWizard(DomainRequestWizardPermissionView, TemplateView): } else: # form is not complete modal_button = '' - context_stuff = { + context = { "not_form": True, "form_titles": self.titles, "steps": self.steps, @@ -491,11 +489,11 @@ class DomainRequestWizard(DomainRequestWizardPermissionView, TemplateView): } # Hides the requests and domains buttons in the navbar - context_stuff["hide_requests"] = self.is_portfolio - context_stuff["hide_domains"] = self.is_portfolio - context_stuff["domain_request_id"] = self.domain_request.id + context["hide_requests"] = self.is_portfolio + context["hide_domains"] = self.is_portfolio + context["domain_request_id"] = self.domain_request.id - return context_stuff + return context def get_step_list(self) -> list: """Dynamically generated list of steps in the form wizard.""" From a1fd5140ff4baba6fa53acde22a2f1f89e159f0b Mon Sep 17 00:00:00 2001 From: Matthew Spence Date: Tue, 12 Nov 2024 14:34:29 -0600 Subject: [PATCH 34/46] add monitor to github actions --- .github/workflows/clone-staging.yaml | 1 + .github/workflows/createcachetable.yaml | 1 + .github/workflows/daily-csv-upload.yaml | 1 + .github/workflows/deploy-development.yaml | 2 ++ .github/workflows/deploy-manual.yaml | 2 ++ .github/workflows/deploy-sandbox.yaml | 2 ++ .github/workflows/deploy-stable.yaml | 1 + .github/workflows/deploy-staging.yaml | 1 + .github/workflows/issue-label-notifier.yaml | 1 + .github/workflows/migrate.yaml | 1 + .github/workflows/reset-db.yaml | 1 + .github/workflows/security-check.yaml | 1 + .github/workflows/test.yaml | 4 ++++ 13 files changed, 19 insertions(+) diff --git a/.github/workflows/clone-staging.yaml b/.github/workflows/clone-staging.yaml index 790246328..ef259c3b6 100644 --- a/.github/workflows/clone-staging.yaml +++ b/.github/workflows/clone-staging.yaml @@ -19,6 +19,7 @@ jobs: CF_USERNAME: ${{ secrets.CF_MS_USERNAME }} CF_PASSWORD: ${{ secrets.CF_MS_PASSWORD }} steps: + - uses: GitHubSecurityLab/actions-permissions/monitor@v1 - name: Clone Database run: | # install cf cli and other tools diff --git a/.github/workflows/createcachetable.yaml b/.github/workflows/createcachetable.yaml index 207ecf70e..768bf050d 100644 --- a/.github/workflows/createcachetable.yaml +++ b/.github/workflows/createcachetable.yaml @@ -37,6 +37,7 @@ jobs: CF_USERNAME: CF_${{ github.event.inputs.environment }}_USERNAME CF_PASSWORD: CF_${{ github.event.inputs.environment }}_PASSWORD steps: + - uses: GitHubSecurityLab/actions-permissions/monitor@v1 - name: Create cache table for ${{ github.event.inputs.environment }} uses: cloud-gov/cg-cli-tools@main with: diff --git a/.github/workflows/daily-csv-upload.yaml b/.github/workflows/daily-csv-upload.yaml index 9cacfc3bf..41fb00971 100644 --- a/.github/workflows/daily-csv-upload.yaml +++ b/.github/workflows/daily-csv-upload.yaml @@ -13,6 +13,7 @@ jobs: CF_USERNAME: CF_${{ secrets.CF_REPORT_ENV }}_USERNAME CF_PASSWORD: CF_${{ secrets.CF_REPORT_ENV }}_PASSWORD steps: + - uses: GitHubSecurityLab/actions-permissions/monitor@v1 - name: Generate current-federal.csv uses: cloud-gov/cg-cli-tools@main with: diff --git a/.github/workflows/deploy-development.yaml b/.github/workflows/deploy-development.yaml index fa447ed76..9b4ce00e1 100644 --- a/.github/workflows/deploy-development.yaml +++ b/.github/workflows/deploy-development.yaml @@ -17,6 +17,8 @@ jobs: deploy-development: runs-on: ubuntu-latest steps: + - uses: GitHubSecurityLab/actions-permissions/monitor@v1 + - uses: actions/checkout@v3 - name: Compile USWDS assets diff --git a/.github/workflows/deploy-manual.yaml b/.github/workflows/deploy-manual.yaml index a85cc7565..7033bc129 100644 --- a/.github/workflows/deploy-manual.yaml +++ b/.github/workflows/deploy-manual.yaml @@ -44,6 +44,7 @@ jobs: variables: runs-on: ubuntu-latest steps: + - uses: GitHubSecurityLab/actions-permissions/monitor@v1 - name: Setting global variables uses: actions/github-script@v6 id: var @@ -53,6 +54,7 @@ jobs: deploy: runs-on: ubuntu-latest steps: + - uses: GitHubSecurityLab/actions-permissions/monitor@v1 - uses: actions/checkout@v3 - name: Compile USWDS assets working-directory: ./src diff --git a/.github/workflows/deploy-sandbox.yaml b/.github/workflows/deploy-sandbox.yaml index e9eb06627..52d0d1830 100644 --- a/.github/workflows/deploy-sandbox.yaml +++ b/.github/workflows/deploy-sandbox.yaml @@ -35,6 +35,7 @@ jobs: environment: ${{ steps.var.outputs.environment}} runs-on: "ubuntu-latest" steps: + - uses: GitHubSecurityLab/actions-permissions/monitor@v1 - name: Setting global variables uses: actions/github-script@v6 id: var @@ -45,6 +46,7 @@ jobs: runs-on: ubuntu-latest needs: [variables] steps: + - uses: GitHubSecurityLab/actions-permissions/monitor@v1 - uses: actions/checkout@v3 - name: Compile USWDS assets working-directory: ./src diff --git a/.github/workflows/deploy-stable.yaml b/.github/workflows/deploy-stable.yaml index a1b947ca5..7a2e4a940 100644 --- a/.github/workflows/deploy-stable.yaml +++ b/.github/workflows/deploy-stable.yaml @@ -18,6 +18,7 @@ jobs: if: ${{ github.ref_type == 'tag' }} runs-on: ubuntu-latest steps: + - uses: GitHubSecurityLab/actions-permissions/monitor@v1 - uses: actions/checkout@v3 - name: Compile USWDS assets diff --git a/.github/workflows/deploy-staging.yaml b/.github/workflows/deploy-staging.yaml index 3cf5ad5a1..81e87b2df 100644 --- a/.github/workflows/deploy-staging.yaml +++ b/.github/workflows/deploy-staging.yaml @@ -18,6 +18,7 @@ jobs: if: ${{ github.ref_type == 'tag' }} runs-on: ubuntu-latest steps: + - uses: GitHubSecurityLab/actions-permissions/monitor@v1 - uses: actions/checkout@v3 - name: Compile USWDS assets diff --git a/.github/workflows/issue-label-notifier.yaml b/.github/workflows/issue-label-notifier.yaml index c4f10d48f..ee6aaa844 100644 --- a/.github/workflows/issue-label-notifier.yaml +++ b/.github/workflows/issue-label-notifier.yaml @@ -10,6 +10,7 @@ jobs: notify: runs-on: ubuntu-latest steps: + - uses: GitHubSecurityLab/actions-permissions/monitor@v1 - uses: jenschelkopf/issue-label-notification-action@1.3 with: recipients: | diff --git a/.github/workflows/migrate.yaml b/.github/workflows/migrate.yaml index 1853b3c4f..e18791cad 100644 --- a/.github/workflows/migrate.yaml +++ b/.github/workflows/migrate.yaml @@ -45,6 +45,7 @@ jobs: CF_USERNAME: CF_${{ github.event.inputs.environment }}_USERNAME CF_PASSWORD: CF_${{ github.event.inputs.environment }}_PASSWORD steps: + - uses: GitHubSecurityLab/actions-permissions/monitor@v1 - name: Run Django migrations for ${{ github.event.inputs.environment }} uses: cloud-gov/cg-cli-tools@main with: diff --git a/.github/workflows/reset-db.yaml b/.github/workflows/reset-db.yaml index 111555b3c..8e060beaa 100644 --- a/.github/workflows/reset-db.yaml +++ b/.github/workflows/reset-db.yaml @@ -45,6 +45,7 @@ jobs: CF_USERNAME: CF_${{ github.event.inputs.environment }}_USERNAME CF_PASSWORD: CF_${{ github.event.inputs.environment }}_PASSWORD steps: + - uses: GitHubSecurityLab/actions-permissions/monitor@v1 - name: Delete existing data for ${{ github.event.inputs.environment }} uses: cloud-gov/cg-cli-tools@main with: diff --git a/.github/workflows/security-check.yaml b/.github/workflows/security-check.yaml index aea700613..2a0623143 100644 --- a/.github/workflows/security-check.yaml +++ b/.github/workflows/security-check.yaml @@ -38,6 +38,7 @@ jobs: REGISTRY_HOSTNAME: localhost steps: + - uses: GitHubSecurityLab/actions-permissions/monitor@v1 - name: Check out uses: actions/checkout@v3 - name: Scan Django settings for security issues diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 642e9dc30..0ccf94885 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -21,6 +21,7 @@ jobs: python-linting: runs-on: ubuntu-latest steps: + - uses: GitHubSecurityLab/actions-permissions/monitor@v1 - uses: actions/checkout@v3 - name: Linting @@ -32,6 +33,7 @@ jobs: python-test: runs-on: ubuntu-latest steps: + - uses: GitHubSecurityLab/actions-permissions/monitor@v1 - uses: actions/checkout@v3 - name: Unit tests @@ -41,6 +43,7 @@ jobs: django-migrations-complete: runs-on: ubuntu-latest steps: + - uses: GitHubSecurityLab/actions-permissions/monitor@v1 - uses: actions/checkout@v3 - name: Check for complete migrations @@ -52,6 +55,7 @@ jobs: pa11y-scan: runs-on: ubuntu-20.04 steps: + - uses: GitHubSecurityLab/actions-permissions/monitor@v1 - uses: actions/checkout@v3 - name: Disable Login From e95eb6eee441de77033eb90c4306ee06bf2eb013 Mon Sep 17 00:00:00 2001 From: David Kennedy Date: Tue, 12 Nov 2024 17:18:12 -0500 Subject: [PATCH 35/46] add city and state to suborg --- ...on_city_suborganization_state_territory.py | 90 +++++++++++++++++++ src/registrar/models/suborganization.py | 15 ++++ 2 files changed, 105 insertions(+) create mode 100644 src/registrar/migrations/0137_suborganization_city_suborganization_state_territory.py diff --git a/src/registrar/migrations/0137_suborganization_city_suborganization_state_territory.py b/src/registrar/migrations/0137_suborganization_city_suborganization_state_territory.py new file mode 100644 index 000000000..85e8cb60f --- /dev/null +++ b/src/registrar/migrations/0137_suborganization_city_suborganization_state_territory.py @@ -0,0 +1,90 @@ +# Generated by Django 4.2.10 on 2024-11-12 22:02 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("registrar", "0136_domainrequest_requested_suborganization_and_more"), + ] + + operations = [ + migrations.AddField( + model_name="suborganization", + name="city", + field=models.CharField(blank=True, null=True), + ), + migrations.AddField( + model_name="suborganization", + name="state_territory", + field=models.CharField( + blank=True, + choices=[ + ("AL", "Alabama (AL)"), + ("AK", "Alaska (AK)"), + ("AS", "American Samoa (AS)"), + ("AZ", "Arizona (AZ)"), + ("AR", "Arkansas (AR)"), + ("CA", "California (CA)"), + ("CO", "Colorado (CO)"), + ("CT", "Connecticut (CT)"), + ("DE", "Delaware (DE)"), + ("DC", "District of Columbia (DC)"), + ("FL", "Florida (FL)"), + ("GA", "Georgia (GA)"), + ("GU", "Guam (GU)"), + ("HI", "Hawaii (HI)"), + ("ID", "Idaho (ID)"), + ("IL", "Illinois (IL)"), + ("IN", "Indiana (IN)"), + ("IA", "Iowa (IA)"), + ("KS", "Kansas (KS)"), + ("KY", "Kentucky (KY)"), + ("LA", "Louisiana (LA)"), + ("ME", "Maine (ME)"), + ("MD", "Maryland (MD)"), + ("MA", "Massachusetts (MA)"), + ("MI", "Michigan (MI)"), + ("MN", "Minnesota (MN)"), + ("MS", "Mississippi (MS)"), + ("MO", "Missouri (MO)"), + ("MT", "Montana (MT)"), + ("NE", "Nebraska (NE)"), + ("NV", "Nevada (NV)"), + ("NH", "New Hampshire (NH)"), + ("NJ", "New Jersey (NJ)"), + ("NM", "New Mexico (NM)"), + ("NY", "New York (NY)"), + ("NC", "North Carolina (NC)"), + ("ND", "North Dakota (ND)"), + ("MP", "Northern Mariana Islands (MP)"), + ("OH", "Ohio (OH)"), + ("OK", "Oklahoma (OK)"), + ("OR", "Oregon (OR)"), + ("PA", "Pennsylvania (PA)"), + ("PR", "Puerto Rico (PR)"), + ("RI", "Rhode Island (RI)"), + ("SC", "South Carolina (SC)"), + ("SD", "South Dakota (SD)"), + ("TN", "Tennessee (TN)"), + ("TX", "Texas (TX)"), + ("UM", "United States Minor Outlying Islands (UM)"), + ("UT", "Utah (UT)"), + ("VT", "Vermont (VT)"), + ("VI", "Virgin Islands (VI)"), + ("VA", "Virginia (VA)"), + ("WA", "Washington (WA)"), + ("WV", "West Virginia (WV)"), + ("WI", "Wisconsin (WI)"), + ("WY", "Wyoming (WY)"), + ("AA", "Armed Forces Americas (AA)"), + ("AE", "Armed Forces Africa, Canada, Europe, Middle East (AE)"), + ("AP", "Armed Forces Pacific (AP)"), + ], + max_length=2, + null=True, + verbose_name="state, territory, or military post", + ), + ), + ] diff --git a/src/registrar/models/suborganization.py b/src/registrar/models/suborganization.py index 6ad80fdc0..087490244 100644 --- a/src/registrar/models/suborganization.py +++ b/src/registrar/models/suborganization.py @@ -1,4 +1,6 @@ from django.db import models + +from registrar.models.domain_request import DomainRequest from .utility.time_stamped_model import TimeStampedModel @@ -19,5 +21,18 @@ class Suborganization(TimeStampedModel): related_name="portfolio_suborganizations", ) + city = models.CharField( + null=True, + blank=True, + ) + + state_territory = models.CharField( + max_length=2, + choices=DomainRequest.StateTerritoryChoices.choices, + null=True, + blank=True, + verbose_name="state, territory, or military post", + ) + def __str__(self) -> str: return f"{self.name}" From 8e5bf185734b3f4a3c5ed8e96eeb3270caed7adf Mon Sep 17 00:00:00 2001 From: Alysia Broddrick Date: Tue, 12 Nov 2024 15:47:04 -0800 Subject: [PATCH 36/46] updated with PR feedback --- ops/scripts/rotate_login_certs.sh | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/ops/scripts/rotate_login_certs.sh b/ops/scripts/rotate_login_certs.sh index a68c053e2..abefd8781 100755 --- a/ops/scripts/rotate_login_certs.sh +++ b/ops/scripts/rotate_login_certs.sh @@ -1,5 +1,6 @@ # This script rotates the login.gov credentials, DJANGO_SECRET_KEY and DJANGO_SECRET_LOGIN_KEY that allow for identity sandbox to work on sandboxes and local. # The echo prints in this script should serve for documentation for running manually. +# Run this script once a year for each environment # NOTE: This script was written for MacOS and to be run at the root directory. @@ -27,12 +28,12 @@ if [[ ! $REPLY =~ ^[Yy]$ ]] then cf login -a https://api.fr.cloud.gov --sso fi -echo "targeting space" -cf target -o "cisa-dotgov" -s $1 +echo "Targeting space" +cf target -o cisa-dotgov -s $1 echo "Creating new login.gov credentials for $1..." django_key=$(python3 -c 'from django.core.management.utils import get_random_secret_key; print(get_random_secret_key())') -openssl req -nodes -x509 -days 365 -newkey rsa:2048 -keyout private-$1.pem -out public-$1.crt +openssl req -noenc -x509 -days 365 -newkey rsa:2048 -keyout private-$1.pem -out public-$1.crt login_key=$(base64 -i private-$1.pem) echo "Creating the final json" From df4fa55df36c9dbc289e281f8bfc907f990faa29 Mon Sep 17 00:00:00 2001 From: Alysia Broddrick Date: Wed, 13 Nov 2024 08:34:24 -0800 Subject: [PATCH 37/46] fixed typo --- ops/scripts/rotate_login_certs.sh | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ops/scripts/rotate_login_certs.sh b/ops/scripts/rotate_login_certs.sh index abefd8781..41976d750 100755 --- a/ops/scripts/rotate_login_certs.sh +++ b/ops/scripts/rotate_login_certs.sh @@ -5,10 +5,10 @@ if [ -z "$1" ]; then - echo 'Please specify a new space to create (i.e. lmm)' >&2 + echo 'Please specify a space to update (i.e. lmm)' >&2 exit 1 fi -echo "You need access to the login partner dashboard, otherwise you will not be able to complete the steps in this script (https://dashboard.int.identitysandbox.gov/service_providers/2640)" +echo "You need access to the Login partner dashboard, otherwise you will not be able to complete the steps in this script (https://dashboard.int.identitysandbox.gov/service_providers/2640)" read -p " Do you have access to the partner dashboard mentioned above? (y/n) " -n 1 -r echo if [[ ! $REPLY =~ ^[Yy]$ ]]; then @@ -48,4 +48,4 @@ echo "Navigate to our application config: https://dashboard.int.identitysandbox. echo "There are two things to update." echo "1. Remove the old cert associated with the user's email (under Public Certificates)" echo "2. You need to upload the public-$1.crt file generated as part of the previous command. See the "choose cert file" button under Public Certificates." -echo "Then, tell the developer to update their local .env file by retreiving their credentials from the sandbox" +echo "Then, tell the developer to update their local .env file by retrieving their credentials from the sandbox" From d4c3e81fa155fc58f94ae2c04f8c923a64fd6dcf Mon Sep 17 00:00:00 2001 From: Matt-Spence Date: Wed, 13 Nov 2024 11:15:32 -0600 Subject: [PATCH 38/46] Update test.yaml --- .github/workflows/test.yaml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 0ccf94885..7c7576cae 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -55,7 +55,6 @@ jobs: pa11y-scan: runs-on: ubuntu-20.04 steps: - - uses: GitHubSecurityLab/actions-permissions/monitor@v1 - uses: actions/checkout@v3 - name: Disable Login From 2f65cd83b671b8d0a219b8115451e21642d58e2e Mon Sep 17 00:00:00 2001 From: Matt-Spence Date: Wed, 13 Nov 2024 11:17:43 -0600 Subject: [PATCH 39/46] Update security-check.yaml --- .github/workflows/security-check.yaml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/security-check.yaml b/.github/workflows/security-check.yaml index 2a0623143..eda6a4f34 100644 --- a/.github/workflows/security-check.yaml +++ b/.github/workflows/security-check.yaml @@ -38,7 +38,6 @@ jobs: REGISTRY_HOSTNAME: localhost steps: - - uses: GitHubSecurityLab/actions-permissions/monitor@v1 - name: Check out uses: actions/checkout@v3 - name: Scan Django settings for security issues @@ -55,6 +54,7 @@ jobs: runs-on: ubuntu-latest steps: + - uses: GitHubSecurityLab/actions-permissions/monitor@v1 - name: Check out uses: actions/checkout@v3 - name: MockUserLogin should not be in settings.MIDDLEWARE @@ -66,6 +66,7 @@ jobs: runs-on: ubuntu-latest steps: + - uses: GitHubSecurityLab/actions-permissions/monitor@v1 - name: Check out uses: actions/checkout@v3 From 5e9d1fa048719b71def9c3f097d567af48410e6c Mon Sep 17 00:00:00 2001 From: Matt-Spence Date: Wed, 13 Nov 2024 11:24:26 -0600 Subject: [PATCH 40/46] Update security-check.yaml --- .github/workflows/security-check.yaml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/security-check.yaml b/.github/workflows/security-check.yaml index eda6a4f34..ae58b37c6 100644 --- a/.github/workflows/security-check.yaml +++ b/.github/workflows/security-check.yaml @@ -66,7 +66,6 @@ jobs: runs-on: ubuntu-latest steps: - - uses: GitHubSecurityLab/actions-permissions/monitor@v1 - name: Check out uses: actions/checkout@v3 From 5f329774bad1cebdf636108a4faf15e84606e6b1 Mon Sep 17 00:00:00 2001 From: Alysia Broddrick Date: Wed, 13 Nov 2024 17:10:35 -0800 Subject: [PATCH 41/46] added new login cert rotation documentation --- .../runbooks/rotate_application_secrets.md | 54 +++++++++++++++++-- ops/scripts/rotate_login_certs.sh | 2 +- 2 files changed, 52 insertions(+), 4 deletions(-) diff --git a/docs/operations/runbooks/rotate_application_secrets.md b/docs/operations/runbooks/rotate_application_secrets.md index 1094b4ff7..1d36f6a74 100644 --- a/docs/operations/runbooks/rotate_application_secrets.md +++ b/docs/operations/runbooks/rotate_application_secrets.md @@ -3,7 +3,7 @@ Secrets are read from the running environment. -Secrets were originally created with: +Secrets are originally created with: ```sh cf cups getgov-credentials -p credentials-.json @@ -38,6 +38,49 @@ cf restage getgov-stable --strategy rolling Non-secret environment variables can be declared in `manifest-.json` directly. +## Rotating login.gov credentials +The DJANGO_SECRET_KEY and DJANGO_SECRET_LOGIN_KEY are reset once a year for each sandbox, see their sections below for more information on them and how to manually generate these keys. To save time, complete the following steps to rotate these credentials using a script in non-production environments: + +### Step 1 login + +To run the script make sure you are logged on the cf cli and make sure you have access to the [Login Partner Dashboard](https://dashboard.int.identitysandbox.gov/service_providers/2640). + +### Step 2 Run the script + +Run the following where "ENV" refers to whichever sandbox you want to reset credentials on. Note, the below assumes you are in the root directory of our app. + +```bash +ops/scripts/rotate_login_certs.sh ENV +``` + +### Step 3 Respond to the terminal prompts + +Respond to the prompts from the script and, when it asks for the cert information, the below is an example of what you should enter. Note for "Common Name" you should put the name of the sandbox and for "Email Address" it should be the address of who owns that sandbox (such as the developer's email, if it's a develop sandbox, or whoever ran this action otherwise) + +```bash +Country Name (2 letter code) [AU]:US +State or Province Name (full name) [Some-State]:DC +Locality Name (eg, city) []:DC +Organization Name (eg, company) [Internet Widgits Pty Ltd]:DHS +Organizational Unit Name (eg, section) []:CISA +Common Name (e.g. server FQDN or YOUR name) []:ENV +Email Address []: example@something.com +``` + +Note when this script is done it will have generated a .pem and a .crt file, as well as updated the cert info on the sandbox + +### Step 4 Delete the old cert + +Navigate to to the Login Partner Dashboard linked above and delete the old cert + +### Step 5 add the new cert + +In whichever directory you ran the script there should now be a .crt file named "public-ENV.crt", where ENV is the space name you used on Step 2. Upload this cert in the Login Partner Dashboard in the same section where you deleted the old one. + +### Production only + +This script should not be run in production. Instead, you will need to manually create the keys and then refrain from updating the sandbox. Once the cert is created you will upload it to the Login Partner Dashboard for our production system, and then open a ticket with them to update our existing Login.gov integration. Once they respond back saying it has been applied, you can then update the sandbox. + ## DJANGO_SECRET_KEY This is a standard Django secret key. See Django documentation for tips on generating a new one. @@ -46,6 +89,7 @@ This is a standard Django secret key. See Django documentation for tips on gener This is the base64 encoded private key used in the OpenID Connect authentication flow with Login.gov. It is used to sign a token during user login; the signature is examined by Login.gov before their API grants access to user data. +### Manually creating creating the Login Key Generate a new key using this command (or whatever is most recently [recommended by Login.gov](https://developers.login.gov/testing/#creating-a-public-certificate)): ```bash @@ -60,6 +104,8 @@ base64 private.pem You also need to upload the `public.crt` key if recently created to the login.gov identity sandbox: https://dashboard.int.identitysandbox.gov/ + + ## AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY To access the AWS Simple Email Service, we need credentials from the CISA AWS @@ -76,6 +122,8 @@ These are the client certificate and its private key used to identify the regist The private key is protected by a passphrase for safer transport and storage. +Note this must be reset once a year. + These were generated with the following steps: ### Step 1: Generate an unencrypted private key with a named curve @@ -90,7 +138,7 @@ openssl ecparam -name prime256v1 -genkey -out client_unencrypted.key openssl pkcs8 -topk8 -v2 aes-256-cbc -in client_unencrypted.key -out client.key ``` -### Generate the certificate +### Step 3: Generate the certificate ```bash openssl req -new -x509 -days 365 -key client.key -out client.crt -subj "/C=US/ST=DC/L=Washington/O=GSA/OU=18F/CN=GOV Prototype Registrar" @@ -112,7 +160,7 @@ base64 -i client.key base64 -i client.crt ``` -You'll need to give the new certificate to the registry vendor _before_ rotating it in production. Once it has been accepted by the vendor, make sure to update the kdbx file on Google Drive. +You'll need to give the new certificate to the registry vendor _before_ rotating it in production. Once it has been accepted by the vendor, make sure to update [the KBDX](https://docs.google.com/document/d/1_BbJmjYZNYLNh4jJPPnUEG9tFCzJrOc0nMrZrnSKKyw) file on Google Drive. ## REGISTRY_HOSTNAME diff --git a/ops/scripts/rotate_login_certs.sh b/ops/scripts/rotate_login_certs.sh index 41976d750..31363fe36 100755 --- a/ops/scripts/rotate_login_certs.sh +++ b/ops/scripts/rotate_login_certs.sh @@ -43,7 +43,7 @@ echo "Updating creds on the sandbox" cf uups getgov-credentials -p credentials-$1.json cf restage getgov-$1 --strategy rolling -echo "Now you will need to update some things for Login. Please sign-in to https://dashboard.int.identitysandbox.gov/." +echo "\n\n\nNow you will need to update some things for Login. Please sign-in to https://dashboard.int.identitysandbox.gov/." echo "Navigate to our application config: https://dashboard.int.identitysandbox.gov/service_providers/2640/edit?" echo "There are two things to update." echo "1. Remove the old cert associated with the user's email (under Public Certificates)" From 88477991563150f5e2378182a8521afd0718822d Mon Sep 17 00:00:00 2001 From: Matt-Spence Date: Thu, 14 Nov 2024 13:09:38 -0600 Subject: [PATCH 42/46] Update deploy-development.yaml --- .github/workflows/deploy-development.yaml | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/.github/workflows/deploy-development.yaml b/.github/workflows/deploy-development.yaml index 9b4ce00e1..c3c7cf7df 100644 --- a/.github/workflows/deploy-development.yaml +++ b/.github/workflows/deploy-development.yaml @@ -16,9 +16,7 @@ on: jobs: deploy-development: runs-on: ubuntu-latest - steps: - - uses: GitHubSecurityLab/actions-permissions/monitor@v1 - + steps: - uses: actions/checkout@v3 - name: Compile USWDS assets @@ -48,4 +46,4 @@ jobs: cf_password: ${{ secrets.CF_DEVELOPMENT_PASSWORD }} cf_org: cisa-dotgov cf_space: development - cf_command: "run-task getgov-development --command 'python manage.py migrate' --name migrate" \ No newline at end of file + cf_command: "run-task getgov-development --command 'python manage.py migrate' --name migrate" From 7594e469e45a961c385f63fdeeb5bcdce0b7ec19 Mon Sep 17 00:00:00 2001 From: Matt-Spence Date: Thu, 14 Nov 2024 13:10:19 -0600 Subject: [PATCH 43/46] Update deploy-manual.yaml --- .github/workflows/deploy-manual.yaml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/deploy-manual.yaml b/.github/workflows/deploy-manual.yaml index 7033bc129..f3045c110 100644 --- a/.github/workflows/deploy-manual.yaml +++ b/.github/workflows/deploy-manual.yaml @@ -54,7 +54,6 @@ jobs: deploy: runs-on: ubuntu-latest steps: - - uses: GitHubSecurityLab/actions-permissions/monitor@v1 - uses: actions/checkout@v3 - name: Compile USWDS assets working-directory: ./src From 9dbe9a4cb453050bddf3c9762c865e6018381696 Mon Sep 17 00:00:00 2001 From: Matt-Spence Date: Thu, 14 Nov 2024 13:10:35 -0600 Subject: [PATCH 44/46] Update deploy-sandbox.yaml --- .github/workflows/deploy-sandbox.yaml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/deploy-sandbox.yaml b/.github/workflows/deploy-sandbox.yaml index 52d0d1830..d751616c6 100644 --- a/.github/workflows/deploy-sandbox.yaml +++ b/.github/workflows/deploy-sandbox.yaml @@ -46,7 +46,6 @@ jobs: runs-on: ubuntu-latest needs: [variables] steps: - - uses: GitHubSecurityLab/actions-permissions/monitor@v1 - uses: actions/checkout@v3 - name: Compile USWDS assets working-directory: ./src @@ -85,4 +84,4 @@ jobs: owner: context.repo.owner, repo: context.repo.repo, body: '🥳 Successfully deployed to developer sandbox **[${{ env.ENVIRONMENT }}](https://getgov-${{ env.ENVIRONMENT }}.app.cloud.gov/)**.' - }) \ No newline at end of file + }) From e3a13bccf16335715afbe5bc2d6bc803b51703e0 Mon Sep 17 00:00:00 2001 From: Matt-Spence Date: Thu, 14 Nov 2024 13:10:52 -0600 Subject: [PATCH 45/46] Update deploy-stable.yaml --- .github/workflows/deploy-stable.yaml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/deploy-stable.yaml b/.github/workflows/deploy-stable.yaml index 7a2e4a940..78dd9e9f5 100644 --- a/.github/workflows/deploy-stable.yaml +++ b/.github/workflows/deploy-stable.yaml @@ -18,7 +18,6 @@ jobs: if: ${{ github.ref_type == 'tag' }} runs-on: ubuntu-latest steps: - - uses: GitHubSecurityLab/actions-permissions/monitor@v1 - uses: actions/checkout@v3 - name: Compile USWDS assets @@ -45,4 +44,4 @@ jobs: cf_password: ${{ secrets.CF_STABLE_PASSWORD }} cf_org: cisa-dotgov cf_space: stable - cf_command: "run-task getgov-stable --command 'python manage.py migrate' --name migrate" \ No newline at end of file + cf_command: "run-task getgov-stable --command 'python manage.py migrate' --name migrate" From ab0617a452ea27864a1e1d658d79f87e0608ab43 Mon Sep 17 00:00:00 2001 From: Matt-Spence Date: Thu, 14 Nov 2024 13:11:06 -0600 Subject: [PATCH 46/46] Update deploy-staging.yaml --- .github/workflows/deploy-staging.yaml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/deploy-staging.yaml b/.github/workflows/deploy-staging.yaml index 81e87b2df..b7a1c4b50 100644 --- a/.github/workflows/deploy-staging.yaml +++ b/.github/workflows/deploy-staging.yaml @@ -18,7 +18,6 @@ jobs: if: ${{ github.ref_type == 'tag' }} runs-on: ubuntu-latest steps: - - uses: GitHubSecurityLab/actions-permissions/monitor@v1 - uses: actions/checkout@v3 - name: Compile USWDS assets @@ -45,4 +44,4 @@ jobs: cf_password: ${{ secrets.CF_STAGING_PASSWORD }} cf_org: cisa-dotgov cf_space: staging - cf_command: "run-task getgov-staging --command 'python manage.py migrate' --name migrate" \ No newline at end of file + cf_command: "run-task getgov-staging --command 'python manage.py migrate' --name migrate"