diff --git a/.github/ISSUE_TEMPLATE/designer-onboarding.md b/.github/ISSUE_TEMPLATE/designer-onboarding.md new file mode 100644 index 000000000..461850b60 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/designer-onboarding.md @@ -0,0 +1,62 @@ +--- +name: Designer Onboarding +about: Onboarding steps for designers. +title: 'Designer Onboarding: GH_HANDLE' +labels: design, onboarding +assignees: katherineosos + +--- + +# Designer Onboarding + +- Onboardee: _GH handle of person being onboarded_ +- Onboarder: _GH handle of onboard buddy_ + +Welcome to the .gov team! We're excited to have you here. Please follow the steps below to get everything set up. An onboarding buddy will help grant you access to all the tools and platforms we use. If you haven't been assigned an onboarding buddy, let us know in the #dotgov-disco channel. + + +## Onboardee + +### Steps for the onboardee +- [ ] Read the [.gov onboarding doc](https://docs.google.com/document/d/1ukbpW4LSqkb_CCt8LWfpehP03qqfyYfvK3Fl21NaEq8/edit?usp=sharing) thoroughly. +- [ ] Accept an invitation to our Slack workspace and fill out your profile. + - [ ] For our Slack profile names, we usually follow the naming convention of `Firstname Lastname (Org, State, pronouns)`. + Example: Katherine Osos (Truss, MN, she/her) + - [ ] Make sure you have been added to the necessary [channels](https://docs.google.com/document/d/1ukbpW4LSqkb_CCt8LWfpehP03qqfyYfvK3Fl21NaEq8/edit#heading=h.li3lqcygw8ax) and familiarize yourself with them. +- [ ] FOR FEDERAL EMPLOYEES: Create a [Google workspace enterprise account](https://docs.google.com/document/d/1ukbpW4LSqkb_CCt8LWfpehP03qqfyYfvK3Fl21NaEq8/edit?pli=1#heading=h.xowzg9w0qlis) for CISA. +- [ ] Get access to our [Project Folder](https://drive.google.com/drive/folders/1qkoFQBlzXA7axi9CZ_OBhlJqRcqlNfpW?usp=drive_link) on Google Drive. + - [ ] Explore the folders and docs. Designers interface with the Product Design, Content, and Research folders most often. +- [ ] Make sure you have been invited to our [team meetings](https://docs.google.com/document/d/1ukbpW4LSqkb_CCt8LWfpehP03qqfyYfvK3Fl21NaEq8/edit#heading=h.emgtp2hgvabr) on Google Meet. +- [ ] Review our [design tools](https://docs.google.com/document/d/1ukbpW4LSqkb_CCt8LWfpehP03qqfyYfvK3Fl21NaEq8/edit?pli=1#heading=h.aprurp3z4gmv). +- [ ] Accept invitation to our [Figma workspace](https://www.figma.com/files/1287135731043703282/team/1299882813146449644). +- [ ] Follow the steps in [preparing for your sandbox](https://docs.google.com/document/d/1ukbpW4LSqkb_CCt8LWfpehP03qqfyYfvK3Fl21NaEq8/edit?pli=1#heading=h.au66hq5e0l8s) section on the onboarding doc. +- [ ] Schedule coffee chats with Design Lead, other designers, scrum master, and product manager ([team directory](https://docs.google.com/document/d/1ukbpW4LSqkb_CCt8LWfpehP03qqfyYfvK3Fl21NaEq8/edit?pli=1#heading=h.1vq6r8e52e9f)). +- [ ] Look over [recommended reading](https://docs.google.com/document/d/1ukbpW4LSqkb_CCt8LWfpehP03qqfyYfvK3Fl21NaEq8/edit?pli=1#heading=h.7ox9ee7v5q5n) and [relevant links](https://docs.google.com/document/d/1ukbpW4LSqkb_CCt8LWfpehP03qqfyYfvK3Fl21NaEq8/edit?pli=1#heading=h.d9pac1gc751t). +- [ ] Fill out your own Personal Operating Manual (POM) on the [team norming board](https://miro.com/app/board/uXjVMxMu1SA=/). OPTIONAL: Present it on the next team coffee meeting. +- [ ] FOR FEDERAL EMPLOYEES: Check in with your manager on the CISA onboarding process and getting your PIV card. +- [ ] FOR CONTRACTORS: Check with your manager on your EOD clearance process. +- [ ] OPTIONAL: Request access to our [analytics tools](https://docs.google.com/document/d/1ukbpW4LSqkb_CCt8LWfpehP03qqfyYfvK3Fl21NaEq8/edit?pli=1#heading=h.9q334hs4lbks). +- [ ] OPTIONAL: If you would like to manage content updates by running code locally rather than through GitHub, follow the steps in the [dev onboarding ticket](https://github.com/cisagov/getgov/issues/new?assignees=loganmeetsworld&labels=dev%2C+onboarding&template=developer-onboarding.md&title=Developer+Onboarding%3A+GH_HANDLE). + - [ ] If you would like to run code locally on a CISA laptop, reference the "[Setting up a developer environment on CISA laptops](https://docs.google.com/document/d/1ukbpW4LSqkb_CCt8LWfpehP03qqfyYfvK3Fl21NaEq8/edit#heading=h.2ctyba51d1zp)" section of the onboarding doc. + + +### Access +By following the steps, you should have access / been added to the following: +- [ ] The [.gov team](https://github.com/orgs/cisagov/teams/gov) under cisagov on GitHub +- [ ] [Slack](https://dhscisa.enterprise.slack.com), and have been added to the necessary channels +- [ ] [Google Drive Project folder](https://drive.google.com/drive/folders/1qkoFQBlzXA7axi9CZ_OBhlJqRcqlNfpW?usp=drive_link) +- [ ] [.gov team on Figma](https://www.figma.com/files/1287135731043703282/team/1299882813146449644) (as an editor if you have a license) +- [ ] [Team meetings](https://docs.google.com/document/d/1ukbpW4LSqkb_CCt8LWfpehP03qqfyYfvK3Fl21NaEq8/edit#heading=h.h62kzew057p1) + + +## Onboarder + +### Steps for the onboarder +- [ ] Make sure onboardee is given an invitation to our Slack workspace +- [ ] Add onboardee to dotgov Slack channels +- [ ] Add onboardee to Project Folder as a "Contributor" (or if you do not have permissions, confirm with Cameron) + - If onboardee if a federal employee, add them as a "Content Manager" instead +- [ ] Coordinate with Cameron to grant onboardee a Figma license +- [ ] Add onboardee as an editor to the Figma team workspace, If they do not have a license (yet), you can add them as a viewer. +- [ ] Add onboardee to our team meetings +- [ ] If applicable, invite onboardee to Google Analytics, Google Search Console, and Search.gov console diff --git a/README.md b/README.md index f457e7e69..4fa110464 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ The .gov domain helps U.S.-based government organizations gain public trust by b ## Onboarding -For new members of the @cisagov/dotgov team looking to contribute to the registrar, please open an [onboarding ticket](https://github.com/cisagov/getgov/issues/new?assignees=loganmeetsworld&labels=dev%2C+onboarding&template=developer-onboarding.md&title=Developer+Onboarding%3A+GH_HANDLE). +For new members of the @cisagov/dotgov team looking to contribute to the registrar, please open a [developer onboarding ticket](https://github.com/cisagov/getgov/issues/new?assignees=loganmeetsworld&labels=dev%2C+onboarding&template=developer-onboarding.md&title=Developer+Onboarding%3A+GH_HANDLE) or a [designer onboarding ticket](https://github.com/cisagov/getgov/issues/new?assignees=loganmeetsworld&labels=design%2C+onboarding&template=designer-onboarding.md&title=Designer+Onboarding%3A+GH_HANDLE). ## Code diff --git a/docs/developer/README.md b/docs/developer/README.md index f894955e5..9421d5856 100644 --- a/docs/developer/README.md +++ b/docs/developer/README.md @@ -73,14 +73,23 @@ cp ./.env-example .env Get the secrets from Cloud.gov by running `cf env getgov-YOURSANDBOX`. More information is available in [rotate_application_secrets.md](../operations/runbooks/rotate_application_secrets.md). -## Adding user to /admin +## Getting access to /admin on all development sandboxes (also referred to as "adding to fixtures") -The endpoint /admin can be used to view and manage site content, including but not limited to user information and the list of current applications in the database. To be able to view and use /admin locally: +The endpoint /admin can be used to view and manage site content, including but not limited to user information and the list of current applications in the database. However, access to this is limited to analysts and full-access users with regular domain requestors and domain managers not being able to see this page. + +While on production (the sandbox referred to as `stable`), an existing analyst or full-access user typically grants access /admin as part of onboarding ([see these instructions](../django-admin/roles.md)), doing this for all development sandboxes is very time consuming. Instead, to get access to /admin on all development sandboxes and when developing code locally, refer to the following sections depending on what level of user access you desire. + + +### Adding full-access user to /admin + + To get access to /admin on every non-production sandbox and to use /admin in local development, do the following: 1. Login via login.gov 2. Go to the home page and make sure you can see the part where you can submit a domain request -3. Go to /admin and it will tell you that UUID is not authorized, copy that UUID for use in 4 -4. in src/registrar/fixtures_users.py add to the `ADMINS` list in that file by adding your UUID as your username along with your first and last name. See below: +3. Go to /admin and it will tell you that your UUID is not authorized (it shows a very long string, this is your UUID). Copy that UUID for use in 4. +4. (Designers) Message in #getgov-dev that you need access to admin as a `superuser` and send them this UUID along with your desired email address. Please see the "Adding an Analyst to /admin" section below to complete similiar steps if you also desire an `analyst` user account. Engineers will handle the remaining steps for designers, stop here. + +(Engineers) In src/registrar/fixtures_users.py add to the `ADMINS` list in that file by adding your UUID as your username along with your first and last name. See below: ``` ADMINS = [ @@ -93,16 +102,18 @@ The endpoint /admin can be used to view and manage site content, including but n ] ``` -5. In the browser, navigate to /admin. To verify that all is working correctly, under "domain requests" you should see fake domains with various fake statuses. -6. Add an optional email key/value pair +5. (Engineers) In the browser, navigate to /admin. To verify that all is working correctly, under "domain requests" you should see fake domains with various fake statuses. +6. (Engineers) Add an optional email key/value pair -### Adding an Analyst to /admin +### Adding an analyst-level user to /admin Analysts are a variant of the admin role with limited permissions. The process for adding an Analyst is much the same as adding an admin: 1. Login via login.gov (if you already exist as an admin, you will need to create a separate login.gov account for this: i.e. first.last+1@email.com) 2. Go to the home page and make sure you can see the part where you can submit a domain request 3. Go to /admin and it will tell you that UUID is not authorized, copy that UUID for use in 4 (this will be a different UUID than the one obtained from creating an admin) -4. in src/registrar/fixtures_users.py add to the `STAFF` list in that file by adding your UUID as your username along with your first and last name. See below: +4. (Designers) Message in #getgov-dev that you need access to admin as a `superuser` and send them this UUID along with your desired email address. Engineers will handle the remaining steps for designers, stop here. + +5. (Engineers) In src/registrar/fixtures_users.py add to the `STAFF` list in that file by adding your UUID as your username along with your first and last name. See below: ``` STAFF = [ @@ -115,10 +126,11 @@ Analysts are a variant of the admin role with limited permissions. The process f ] ``` -5. In the browser, navigate to /admin. To verify that all is working correctly, verify that you can only see a sub-section of the modules and some are set to view-only. -6. Add an optional email key/value pair +5. (Engineers) In the browser, navigate to /admin. To verify that all is working correctly, verify that you can only see a sub-section of the modules and some are set to view-only. +6. (Engineers) Add an optional email key/value pair Do note that if you wish to have both an analyst and admin account, append `-Analyst` to your first and last name, or use a completely different first/last name to avoid confusion. Example: `Bob-Analyst` + ## Adding to CODEOWNERS (optional) The CODEOWNERS file sets the tagged individuals as default reviewers on any Pull Request that changes files that they are marked as owners of. diff --git a/docs/django-admin/roles.md b/docs/django-admin/roles.md index c527bbfa5..4e74a857f 100644 --- a/docs/django-admin/roles.md +++ b/docs/django-admin/roles.md @@ -9,7 +9,7 @@ our `user_group` model and run in a migration. For more details, refer to the [user group model](../../src/registrar/models/user_group.py). -## Adding a user as analyst or granting full access via django-admin +## Adding a user as analyst or granting full access via django-admin (/admin) If a new team member has joined, then they will need to be granted analyst (`cisa_analysts_group`) or full access (`full_access_group`) permissions in order to view the admin pages. These admin pages are the ones found at manage.get.gov/admin. To do this, do the following: @@ -21,7 +21,7 @@ To do this, do the following: 5. (Optional) If the user needs access to django admin (such as an analyst), then you will also need to make sure "Staff Status" is checked. This can be found in the same `User Permissions` section right below the checkbox for `Active`. 6. Click `Save` to apply all changes. -## Removing a user group permission via django-admin +## Removing a user group permission via django-admin (/admin) If an employee was given the wrong permissions or has had a change in roles that subsequently requires a permission change, then their permissions should be updated in django-admin. Much like in the previous section you can accomplish this by doing the following: diff --git a/src/registrar/admin.py b/src/registrar/admin.py index b06111e5b..05bfc06b6 100644 --- a/src/registrar/admin.py +++ b/src/registrar/admin.py @@ -779,6 +779,46 @@ class WebsiteAdmin(ListHeaderAdmin): ] search_help_text = "Search by website." + def get_model_perms(self, request): + """ + Return empty perms dict thus hiding the model from admin index. + """ + superuser_perm = request.user.has_perm("registrar.full_access_permission") + analyst_perm = request.user.has_perm("registrar.analyst_access_permission") + if analyst_perm and not superuser_perm: + return {} + return super().get_model_perms(request) + + def has_change_permission(self, request, obj=None): + """ + Allow analysts to access the change form directly via URL. + """ + superuser_perm = request.user.has_perm("registrar.full_access_permission") + analyst_perm = request.user.has_perm("registrar.analyst_access_permission") + if analyst_perm and not superuser_perm: + return True + return super().has_change_permission(request, obj) + + def response_change(self, request, obj): + """ + Override to redirect users back to the previous page after saving. + """ + superuser_perm = request.user.has_perm("registrar.full_access_permission") + analyst_perm = request.user.has_perm("registrar.analyst_access_permission") + return_path = request.GET.get("return_path") + + # First, call the super method to perform the standard operations and capture the response + response = super().response_change(request, obj) + + # Don't redirect to the website page on save if the user is an analyst. + # Rather, just redirect back to the originating page. + if (analyst_perm and not superuser_perm) and return_path: + # Redirect to the return path if it exists + return HttpResponseRedirect(return_path) + + # If no redirection is needed, return the original response + return response + class UserDomainRoleAdmin(ListHeaderAdmin): """Custom user domain role admin class.""" @@ -1466,7 +1506,10 @@ class DomainInformationInline(admin.StackedInline): def has_change_permission(self, request, obj=None): """Custom has_change_permission override so that we can specify that analysts can edit this through this inline, but not through the model normally""" - if request.user.has_perm("registrar.analyst_access_permission"): + + superuser_perm = request.user.has_perm("registrar.full_access_permission") + analyst_perm = request.user.has_perm("registrar.analyst_access_permission") + if analyst_perm and not superuser_perm: return True return super().has_change_permission(request, obj) @@ -1627,12 +1670,8 @@ class DomainAdmin(ListHeaderAdmin): # No expiration date was found. Return none. extra_context["extended_expiration_date"] = None return super().changeform_view(request, object_id, form_url, extra_context) - - if curr_exp_date < date.today(): - extra_context["extended_expiration_date"] = date.today() + relativedelta(years=years_to_extend_by) - else: - new_date = domain.registry_expiration_date + relativedelta(years=years_to_extend_by) - extra_context["extended_expiration_date"] = new_date + new_date = curr_exp_date + relativedelta(years=years_to_extend_by) + extra_context["extended_expiration_date"] = new_date else: extra_context["extended_expiration_date"] = None @@ -1896,6 +1935,46 @@ class DraftDomainAdmin(ListHeaderAdmin): # in autocomplete_fields for user ordering = ["name"] + def get_model_perms(self, request): + """ + Return empty perms dict thus hiding the model from admin index. + """ + superuser_perm = request.user.has_perm("registrar.full_access_permission") + analyst_perm = request.user.has_perm("registrar.analyst_access_permission") + if analyst_perm and not superuser_perm: + return {} + return super().get_model_perms(request) + + def has_change_permission(self, request, obj=None): + """ + Allow analysts to access the change form directly via URL. + """ + superuser_perm = request.user.has_perm("registrar.full_access_permission") + analyst_perm = request.user.has_perm("registrar.analyst_access_permission") + if analyst_perm and not superuser_perm: + return True + return super().has_change_permission(request, obj) + + def response_change(self, request, obj): + """ + Override to redirect users back to the previous page after saving. + """ + superuser_perm = request.user.has_perm("registrar.full_access_permission") + analyst_perm = request.user.has_perm("registrar.analyst_access_permission") + return_path = request.GET.get("return_path") + + # First, call the super method to perform the standard operations and capture the response + response = super().response_change(request, obj) + + # Don't redirect to the website page on save if the user is an analyst. + # Rather, just redirect back to the originating page. + if (analyst_perm and not superuser_perm) and return_path: + # Redirect to the return path if it exists + return HttpResponseRedirect(return_path) + + # If no redirection is needed, return the original response + return response + class PublicContactAdmin(ListHeaderAdmin): """Custom PublicContact admin class.""" diff --git a/src/registrar/assets/sass/_theme/_admin.scss b/src/registrar/assets/sass/_theme/_admin.scss index fd66754bc..4b69dc8e3 100644 --- a/src/registrar/assets/sass/_theme/_admin.scss +++ b/src/registrar/assets/sass/_theme/_admin.scss @@ -536,6 +536,18 @@ address.dja-address-contact-list { } } +.dja-status-list { + border-top: solid 1px var(--border-color); + margin-left: 0 !important; + padding-left: 0 !important; + padding-top: 10px; + li { + line-height: 1.5; + font-family: "Source Sans Pro Web", "Helvetica Neue", Helvetica, Roboto, Arial, sans-serif !important; + padding-top: 0; + padding-bottom: 0; + } +} // Make the clipboard button "float" inside of the input box .admin-icon-group { diff --git a/src/registrar/migrations/0084_create_groups_v11.py b/src/registrar/migrations/0084_create_groups_v11.py new file mode 100644 index 000000000..efb587520 --- /dev/null +++ b/src/registrar/migrations/0084_create_groups_v11.py @@ -0,0 +1,37 @@ +# This migration creates the create_full_access_group and create_cisa_analyst_group groups +# It is dependent on 0079 (which populates federal agencies) +# If permissions on the groups need changing, edit CISA_ANALYST_GROUP_PERMISSIONS +# in the user_group model then: +# [NOT RECOMMENDED] +# step 1: docker-compose exec app ./manage.py migrate --fake registrar 0035_contenttypes_permissions +# step 2: docker-compose exec app ./manage.py migrate registrar 0036_create_groups +# step 3: fake run the latest migration in the migrations list +# [RECOMMENDED] +# Alternatively: +# step 1: duplicate the migration that loads data +# step 2: docker-compose exec app ./manage.py migrate + +from django.db import migrations +from registrar.models import UserGroup +from typing import Any + + +# For linting: RunPython expects a function reference, +# so let's give it one +def create_groups(apps, schema_editor) -> Any: + UserGroup.create_cisa_analyst_group(apps, schema_editor) + UserGroup.create_full_access_group(apps, schema_editor) + + +class Migration(migrations.Migration): + dependencies = [ + ("registrar", "0083_alter_contact_email_alter_publiccontact_email"), + ] + + operations = [ + migrations.RunPython( + create_groups, + reverse_code=migrations.RunPython.noop, + atomic=True, + ), + ] diff --git a/src/registrar/models/contact.py b/src/registrar/models/contact.py index 2b1955c09..6a082ecf2 100644 --- a/src/registrar/models/contact.py +++ b/src/registrar/models/contact.py @@ -94,6 +94,9 @@ class Contact(TimeStampedModel): names = [n for n in [self.first_name, self.middle_name, self.last_name] if n] return " ".join(names) if names else "Unknown" + def has_contact_info(self): + return bool(self.title or self.email or self.phone) + def save(self, *args, **kwargs): # Call the parent class's save method to perform the actual save super().save(*args, **kwargs) diff --git a/src/registrar/models/user.py b/src/registrar/models/user.py index 1c987d44d..f1610831e 100644 --- a/src/registrar/models/user.py +++ b/src/registrar/models/user.py @@ -9,6 +9,7 @@ from .domain_invitation import DomainInvitation from .transition_domain import TransitionDomain from .verified_by_staff import VerifiedByStaff from .domain import Domain +from .domain_request import DomainRequest from phonenumber_field.modelfields import PhoneNumberField # type: ignore @@ -68,6 +69,33 @@ class User(AbstractUser): def is_restricted(self): return self.status == self.RESTRICTED + def get_approved_domains_count(self): + """Return count of approved domains""" + allowed_states = [Domain.State.UNKNOWN, Domain.State.DNS_NEEDED, Domain.State.READY, Domain.State.ON_HOLD] + approved_domains_count = self.domains.filter(state__in=allowed_states).count() + return approved_domains_count + + def get_active_requests_count(self): + """Return count of active requests""" + allowed_states = [ + DomainRequest.DomainRequestStatus.SUBMITTED, + DomainRequest.DomainRequestStatus.IN_REVIEW, + DomainRequest.DomainRequestStatus.ACTION_NEEDED, + ] + active_requests_count = self.domain_requests_created.filter(status__in=allowed_states).count() + return active_requests_count + + def get_rejected_requests_count(self): + """Return count of rejected requests""" + return self.domain_requests_created.filter(status=DomainRequest.DomainRequestStatus.REJECTED).count() + + def get_ineligible_requests_count(self): + """Return count of ineligible requests""" + return self.domain_requests_created.filter(status=DomainRequest.DomainRequestStatus.INELIGIBLE).count() + + def has_contact_info(self): + return bool(self.contact.title or self.contact.email or self.contact.phone) + @classmethod def needs_identity_verification(cls, email, uuid): """A method used by our oidc classes to test whether a user needs email/uuid verification diff --git a/src/registrar/models/user_group.py b/src/registrar/models/user_group.py index 82179f8dc..76657fe29 100644 --- a/src/registrar/models/user_group.py +++ b/src/registrar/models/user_group.py @@ -41,11 +41,6 @@ class UserGroup(Group): "model": "domain", "permissions": ["view_domain"], }, - { - "app_label": "registrar", - "model": "draftdomain", - "permissions": ["change_draftdomain"], - }, { "app_label": "registrar", "model": "user", @@ -56,11 +51,6 @@ class UserGroup(Group): "model": "domaininvitation", "permissions": ["add_domaininvitation", "view_domaininvitation"], }, - { - "app_label": "registrar", - "model": "website", - "permissions": ["change_website"], - }, { "app_label": "registrar", "model": "userdomainrole", diff --git a/src/registrar/templates/django/admin/includes/contact_detail_list.html b/src/registrar/templates/django/admin/includes/contact_detail_list.html index 8ad6fb96d..0ac9c4c49 100644 --- a/src/registrar/templates/django/admin/includes/contact_detail_list.html +++ b/src/registrar/templates/django/admin/includes/contact_detail_list.html @@ -1,6 +1,6 @@ {% load i18n static %} -
+ {% if show_formatted_name %} {% if contact.get_formatted_name %} @@ -10,7 +10,7 @@ {% endif %} {% endif %} - {% if user.title or user.contact.title or user.email or user.contact.email or user.phone or user.contact.phone %} + {% if user.has_contact_info %} {# Title #} {% if user.title or user.contact.title %} {% if user.contact.title %} diff --git a/src/registrar/templates/django/admin/includes/detail_table_fieldset.html b/src/registrar/templates/django/admin/includes/detail_table_fieldset.html index a0a679290..f346ee155 100644 --- a/src/registrar/templates/django/admin/includes/detail_table_fieldset.html +++ b/src/registrar/templates/django/admin/includes/detail_table_fieldset.html @@ -27,6 +27,10 @@ This is using a custom implementation fieldset.html (see admin/fieldset.html) {% endif %} + {% elif field.field.name == "requested_domain" %} + {% with current_path=request.get_full_path %} + {{ original.requested_domain }} + {% endwith%} {% elif field.field.name == "current_websites" %} {% comment %} The "website" model is essentially just a text field. @@ -49,9 +53,11 @@ This is using a custom implementation fieldset.html (see admin/fieldset.html) {% elif field.field.name == "alternative_domains" %}We received your .gov domain request. Our next step is to review your request. This usually takes 20 business days. We’ll email you if we have questions and when we complete our review. Contact us with any questions.
+We received your .gov domain request. Our next step is to review your request. This usually takes 30 business days. We’ll email you if we have questions and when we complete our review. Contact us with any questions.