mirror of
https://github.com/cisagov/manage.get.gov.git
synced 2025-07-10 21:23:32 +02:00
Merge branch 'main' of https://github.com/cisagov/getgov into rh/687-formatting-nameservers
This commit is contained in:
commit
5ae98c97d2
10 changed files with 105 additions and 54 deletions
|
@ -46,7 +46,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.
|
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.
|
||||||
|
|
||||||
Generate a new key using this command (or whatever is most recently recommended by Login.gov):
|
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
|
```bash
|
||||||
openssl req -nodes -x509 -days 365 -newkey rsa:2048 -keyout private.pem -out public.crt
|
openssl req -nodes -x509 -days 365 -newkey rsa:2048 -keyout private.pem -out public.crt
|
||||||
|
|
|
@ -5,14 +5,13 @@ import json
|
||||||
from django.contrib.auth import get_user_model
|
from django.contrib.auth import get_user_model
|
||||||
from django.test import RequestFactory
|
from django.test import RequestFactory
|
||||||
|
|
||||||
from ..views import available, in_domains
|
from ..views import available, check_domain_available
|
||||||
from .common import less_console_noise
|
from .common import less_console_noise
|
||||||
from registrar.tests.common import MockEppLib
|
from registrar.tests.common import MockEppLib
|
||||||
from unittest.mock import call
|
from unittest.mock import call
|
||||||
|
|
||||||
from epplibwrapper import (
|
from epplibwrapper import (
|
||||||
commands,
|
commands,
|
||||||
RegistryError,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
API_BASE_PATH = "/api/v1/available/"
|
API_BASE_PATH = "/api/v1/available/"
|
||||||
|
@ -37,10 +36,10 @@ class AvailableViewTest(MockEppLib):
|
||||||
response_object = json.loads(response.content)
|
response_object = json.loads(response.content)
|
||||||
self.assertIn("available", response_object)
|
self.assertIn("available", response_object)
|
||||||
|
|
||||||
def test_in_domains_makes_calls_(self):
|
def test_domain_available_makes_calls_(self):
|
||||||
"""Domain searches successfully make correct mock EPP calls"""
|
"""Domain searches successfully make correct mock EPP calls"""
|
||||||
gsa_available = in_domains("gsa.gov")
|
gsa_available = check_domain_available("gsa.gov")
|
||||||
igorville_available = in_domains("igorvilleremixed.gov")
|
igorville_available = check_domain_available("igorville.gov")
|
||||||
|
|
||||||
"""Domain searches successfully make mock EPP calls"""
|
"""Domain searches successfully make mock EPP calls"""
|
||||||
self.mockedSendFunction.assert_has_calls(
|
self.mockedSendFunction.assert_has_calls(
|
||||||
|
@ -53,29 +52,32 @@ class AvailableViewTest(MockEppLib):
|
||||||
),
|
),
|
||||||
call(
|
call(
|
||||||
commands.CheckDomain(
|
commands.CheckDomain(
|
||||||
["igorvilleremixed.gov"],
|
["igorville.gov"],
|
||||||
),
|
),
|
||||||
cleaned=True,
|
cleaned=True,
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
"""Domain searches return correct availability results"""
|
"""Domain searches return correct availability results"""
|
||||||
self.assertTrue(gsa_available)
|
self.assertFalse(gsa_available)
|
||||||
self.assertFalse(igorville_available)
|
self.assertTrue(igorville_available)
|
||||||
|
|
||||||
def test_in_domains_capitalized(self):
|
def test_domain_available_capitalized(self):
|
||||||
"""Domain searches work without case sensitivity"""
|
"""Domain searches work without case sensitivity"""
|
||||||
self.assertTrue(in_domains("gsa.gov"))
|
self.assertFalse(check_domain_available("gsa.gov"))
|
||||||
# input is lowercased so GSA.GOV should be found
|
self.assertTrue(check_domain_available("igorville.gov"))
|
||||||
self.assertTrue(in_domains("GSA.gov"))
|
# input is lowercased so GSA.GOV should also not be available
|
||||||
|
self.assertFalse(check_domain_available("GSA.gov"))
|
||||||
|
# input is lowercased so IGORVILLE.GOV should also not be available
|
||||||
|
self.assertFalse(check_domain_available("IGORVILLE.gov"))
|
||||||
|
|
||||||
def test_in_domains_dotgov(self):
|
def test_domain_available_dotgov(self):
|
||||||
"""Domain searches work without trailing .gov"""
|
"""Domain searches work without trailing .gov"""
|
||||||
self.assertTrue(in_domains("gsa"))
|
self.assertFalse(check_domain_available("gsa"))
|
||||||
# input is lowercased so GSA.GOV should be found
|
# input is lowercased so GSA.GOV should be found
|
||||||
self.assertTrue(in_domains("GSA"))
|
self.assertFalse(check_domain_available("GSA"))
|
||||||
# This domain should not have been registered
|
# This domain should be available to register
|
||||||
self.assertFalse(in_domains("igorvilleremixed"))
|
self.assertTrue(check_domain_available("igorville"))
|
||||||
|
|
||||||
def test_not_available_domain(self):
|
def test_not_available_domain(self):
|
||||||
"""gsa.gov is not available"""
|
"""gsa.gov is not available"""
|
||||||
|
@ -85,17 +87,17 @@ class AvailableViewTest(MockEppLib):
|
||||||
self.assertFalse(json.loads(response.content)["available"])
|
self.assertFalse(json.loads(response.content)["available"])
|
||||||
|
|
||||||
def test_available_domain(self):
|
def test_available_domain(self):
|
||||||
"""igorvilleremixed.gov is still available"""
|
"""igorville.gov is still available"""
|
||||||
request = self.factory.get(API_BASE_PATH + "igorvilleremixed.gov")
|
request = self.factory.get(API_BASE_PATH + "igorville.gov")
|
||||||
request.user = self.user
|
request.user = self.user
|
||||||
response = available(request, domain="igorvilleremixed.gov")
|
response = available(request, domain="igorville.gov")
|
||||||
self.assertTrue(json.loads(response.content)["available"])
|
self.assertTrue(json.loads(response.content)["available"])
|
||||||
|
|
||||||
def test_available_domain_dotgov(self):
|
def test_available_domain_dotgov(self):
|
||||||
"""igorvilleremixed.gov is still available even without the .gov suffix"""
|
"""igorville.gov is still available even without the .gov suffix"""
|
||||||
request = self.factory.get(API_BASE_PATH + "igorvilleremixed")
|
request = self.factory.get(API_BASE_PATH + "igorville")
|
||||||
request.user = self.user
|
request.user = self.user
|
||||||
response = available(request, domain="igorvilleremixed")
|
response = available(request, domain="igorville")
|
||||||
self.assertTrue(json.loads(response.content)["available"])
|
self.assertTrue(json.loads(response.content)["available"])
|
||||||
|
|
||||||
def test_error_handling(self):
|
def test_error_handling(self):
|
||||||
|
@ -105,10 +107,9 @@ class AvailableViewTest(MockEppLib):
|
||||||
request.user = self.user
|
request.user = self.user
|
||||||
response = available(request, domain=bad_string)
|
response = available(request, domain=bad_string)
|
||||||
self.assertFalse(json.loads(response.content)["available"])
|
self.assertFalse(json.loads(response.content)["available"])
|
||||||
# domain set to raise error successfully raises error
|
# domain set to raise error returns false for availability
|
||||||
with self.assertRaises(RegistryError):
|
error_domain_available = available(request, "errordomain.gov")
|
||||||
error_domain_available = available(request, "errordomain.gov")
|
self.assertFalse(json.loads(error_domain_available.content)["available"])
|
||||||
self.assertFalse(json.loads(error_domain_available.content)["available"])
|
|
||||||
|
|
||||||
|
|
||||||
class AvailableAPITest(MockEppLib):
|
class AvailableAPITest(MockEppLib):
|
||||||
|
|
|
@ -5,6 +5,8 @@ from django.http import JsonResponse
|
||||||
|
|
||||||
import requests
|
import requests
|
||||||
|
|
||||||
|
from login_required import login_not_required
|
||||||
|
|
||||||
from cachetools.func import ttl_cache
|
from cachetools.func import ttl_cache
|
||||||
|
|
||||||
|
|
||||||
|
@ -23,6 +25,7 @@ DOMAIN_API_MESSAGES = {
|
||||||
"invalid": "Enter a domain using only letters,"
|
"invalid": "Enter a domain using only letters,"
|
||||||
" numbers, or hyphens (though we don't recommend using hyphens).",
|
" numbers, or hyphens (though we don't recommend using hyphens).",
|
||||||
"success": "That domain is available!",
|
"success": "That domain is available!",
|
||||||
|
"error": "Error finding domain availability.",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -50,22 +53,26 @@ def _domains():
|
||||||
return domains
|
return domains
|
||||||
|
|
||||||
|
|
||||||
def in_domains(domain):
|
def check_domain_available(domain):
|
||||||
"""Return true if the given domain is in the domains list.
|
"""Return true if the given domain is available.
|
||||||
|
|
||||||
The given domain is lowercased to match against the domains list. If the
|
The given domain is lowercased to match against the domains list. If the
|
||||||
given domain doesn't end with .gov, ".gov" is added when looking for
|
given domain doesn't end with .gov, ".gov" is added when looking for
|
||||||
a match.
|
a match.
|
||||||
"""
|
"""
|
||||||
Domain = apps.get_model("registrar.Domain")
|
Domain = apps.get_model("registrar.Domain")
|
||||||
if domain.endswith(".gov"):
|
try:
|
||||||
return Domain.available(domain)
|
if domain.endswith(".gov"):
|
||||||
else:
|
return Domain.available(domain)
|
||||||
# domain search string doesn't end with .gov, add it on here
|
else:
|
||||||
return Domain.available(domain + ".gov")
|
# domain search string doesn't end with .gov, add it on here
|
||||||
|
return Domain.available(domain + ".gov")
|
||||||
|
except Exception:
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
@require_http_methods(["GET"])
|
@require_http_methods(["GET"])
|
||||||
|
@login_not_required
|
||||||
def available(request, domain=""):
|
def available(request, domain=""):
|
||||||
"""Is a given domain available or not.
|
"""Is a given domain available or not.
|
||||||
|
|
||||||
|
@ -83,11 +90,16 @@ def available(request, domain=""):
|
||||||
{"available": False, "message": DOMAIN_API_MESSAGES["invalid"]}
|
{"available": False, "message": DOMAIN_API_MESSAGES["invalid"]}
|
||||||
)
|
)
|
||||||
# a domain is available if it is NOT in the list of current domains
|
# a domain is available if it is NOT in the list of current domains
|
||||||
if in_domains(domain):
|
try:
|
||||||
|
if check_domain_available(domain):
|
||||||
|
return JsonResponse(
|
||||||
|
{"available": True, "message": DOMAIN_API_MESSAGES["success"]}
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
return JsonResponse(
|
||||||
|
{"available": False, "message": DOMAIN_API_MESSAGES["unavailable"]}
|
||||||
|
)
|
||||||
|
except Exception:
|
||||||
return JsonResponse(
|
return JsonResponse(
|
||||||
{"available": False, "message": DOMAIN_API_MESSAGES["unavailable"]}
|
{"available": False, "message": DOMAIN_API_MESSAGES["error"]}
|
||||||
)
|
|
||||||
else:
|
|
||||||
return JsonResponse(
|
|
||||||
{"available": True, "message": DOMAIN_API_MESSAGES["success"]}
|
|
||||||
)
|
)
|
||||||
|
|
20
src/registrar/migrations/0043_domain_expiration_date.py
Normal file
20
src/registrar/migrations/0043_domain_expiration_date.py
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
# Generated by Django 4.2.6 on 2023-10-30 15:37
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
dependencies = [
|
||||||
|
("registrar", "0042_create_groups_v03"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name="domain",
|
||||||
|
name="expiration_date",
|
||||||
|
field=models.DateField(
|
||||||
|
help_text="Duplication of registry's expirationdate saved for ease of reporting",
|
||||||
|
null=True,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
]
|
|
@ -5,6 +5,7 @@ import re
|
||||||
from datetime import date
|
from datetime import date
|
||||||
from string import digits
|
from string import digits
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
|
||||||
from django_fsm import FSMField, transition, TransitionNotAllowed # type: ignore
|
from django_fsm import FSMField, transition, TransitionNotAllowed # type: ignore
|
||||||
|
|
||||||
from django.db import models
|
from django.db import models
|
||||||
|
@ -29,6 +30,7 @@ from epplibwrapper import (
|
||||||
|
|
||||||
from registrar.models.utility.contact_error import ContactError, ContactErrorCodes
|
from registrar.models.utility.contact_error import ContactError, ContactErrorCodes
|
||||||
|
|
||||||
|
from django.db.models import DateField
|
||||||
from .utility.domain_field import DomainField
|
from .utility.domain_field import DomainField
|
||||||
from .utility.domain_helper import DomainHelper
|
from .utility.domain_helper import DomainHelper
|
||||||
from .utility.time_stamped_model import TimeStampedModel
|
from .utility.time_stamped_model import TimeStampedModel
|
||||||
|
@ -209,12 +211,12 @@ class Domain(TimeStampedModel, DomainHelper):
|
||||||
return self._get_property("up_date")
|
return self._get_property("up_date")
|
||||||
|
|
||||||
@Cache
|
@Cache
|
||||||
def expiration_date(self) -> date:
|
def registry_expiration_date(self) -> date:
|
||||||
"""Get or set the `ex_date` element from the registry."""
|
"""Get or set the `ex_date` element from the registry."""
|
||||||
return self._get_property("ex_date")
|
return self._get_property("ex_date")
|
||||||
|
|
||||||
@expiration_date.setter # type: ignore
|
@registry_expiration_date.setter # type: ignore
|
||||||
def expiration_date(self, ex_date: date):
|
def registry_expiration_date(self, ex_date: date):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@Cache
|
@Cache
|
||||||
|
@ -943,6 +945,13 @@ class Domain(TimeStampedModel, DomainHelper):
|
||||||
help_text="Very basic info about the lifecycle of this domain object",
|
help_text="Very basic info about the lifecycle of this domain object",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
expiration_date = DateField(
|
||||||
|
null=True,
|
||||||
|
help_text=(
|
||||||
|
"Duplication of registry's expiration" "date saved for ease of reporting"
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
def isActive(self):
|
def isActive(self):
|
||||||
return self.state == Domain.State.CREATED
|
return self.state == Domain.State.CREATED
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import re
|
import re
|
||||||
|
|
||||||
from api.views import in_domains
|
from api.views import check_domain_available
|
||||||
from registrar.utility import errors
|
from registrar.utility import errors
|
||||||
|
|
||||||
|
|
||||||
|
@ -44,7 +44,7 @@ class DomainHelper:
|
||||||
raise errors.ExtraDotsError()
|
raise errors.ExtraDotsError()
|
||||||
if not DomainHelper.string_could_be_domain(domain + ".gov"):
|
if not DomainHelper.string_could_be_domain(domain + ".gov"):
|
||||||
raise ValueError()
|
raise ValueError()
|
||||||
if in_domains(domain):
|
if not check_domain_available(domain):
|
||||||
raise errors.DomainUnavailableError()
|
raise errors.DomainUnavailableError()
|
||||||
return domain
|
return domain
|
||||||
|
|
||||||
|
|
|
@ -12,11 +12,13 @@
|
||||||
email, and DNS name servers.
|
email, and DNS name servers.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<ul>
|
<ul class="usa-list">
|
||||||
<li>There is no limit to the number of domain managers you can add.</li>
|
<li>There is no limit to the number of domain managers you can add.</li>
|
||||||
<li>After adding a domain manager, an email invitation will be sent to that user with
|
<li>After adding a domain manager, an email invitation will be sent to that user with
|
||||||
instructions on how to set up an account.</li>
|
instructions on how to set up an account.</li>
|
||||||
<li>To remove a domain manager, <a href="{% public_site_url 'contact/' %}" class="usa-link">contact us</a> for assistance.
|
<li>To remove a domain manager, <a href="{% public_site_url 'contact/' %}"
|
||||||
|
target="_blank" rel="noopener noreferrer" class="usa-link">contact us</a> for
|
||||||
|
assistance.</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
{% if domain.permissions %}
|
{% if domain.permissions %}
|
||||||
|
|
|
@ -860,11 +860,17 @@ class MockEppLib(TestCase):
|
||||||
|
|
||||||
def mockCheckDomainCommand(self, _request, cleaned):
|
def mockCheckDomainCommand(self, _request, cleaned):
|
||||||
if "gsa.gov" in getattr(_request, "names", None):
|
if "gsa.gov" in getattr(_request, "names", None):
|
||||||
return self._mockDomainName("gsa.gov", True)
|
return self._mockDomainName("gsa.gov", False)
|
||||||
elif "GSA.gov" in getattr(_request, "names", None):
|
elif "GSA.gov" in getattr(_request, "names", None):
|
||||||
return self._mockDomainName("GSA.gov", True)
|
return self._mockDomainName("GSA.gov", False)
|
||||||
elif "igorvilleremixed.gov" in getattr(_request, "names", None):
|
elif "igorville.gov" in getattr(_request, "names", None):
|
||||||
return self._mockDomainName("igorvilleremixed.gov", False)
|
return self._mockDomainName("igorvilleremixed.gov", True)
|
||||||
|
elif "top-level-agency.gov" in getattr(_request, "names", None):
|
||||||
|
return self._mockDomainName("top-level-agency.gov", True)
|
||||||
|
elif "city.gov" in getattr(_request, "names", None):
|
||||||
|
return self._mockDomainName("city.gov", True)
|
||||||
|
elif "city1.gov" in getattr(_request, "names", None):
|
||||||
|
return self._mockDomainName("city1.gov", True)
|
||||||
elif "errordomain.gov" in getattr(_request, "names", None):
|
elif "errordomain.gov" in getattr(_request, "names", None):
|
||||||
raise RegistryError("Registry cannot find domain availability.")
|
raise RegistryError("Registry cannot find domain availability.")
|
||||||
else:
|
else:
|
||||||
|
|
|
@ -56,7 +56,7 @@ class TestDomainCache(MockEppLib):
|
||||||
self.assertFalse("avail" in domain._cache.keys())
|
self.assertFalse("avail" in domain._cache.keys())
|
||||||
|
|
||||||
# using a setter should clear the cache
|
# using a setter should clear the cache
|
||||||
domain.expiration_date = datetime.date.today()
|
domain.registry_expiration_date = datetime.date.today()
|
||||||
self.assertEquals(domain._cache, {})
|
self.assertEquals(domain._cache, {})
|
||||||
|
|
||||||
# send should have been called only once
|
# send should have been called only once
|
||||||
|
|
|
@ -110,12 +110,13 @@ class TestURLAuth(TestCase):
|
||||||
# Note that the trailing slash is wobbly depending on how the URL was defined.
|
# Note that the trailing slash is wobbly depending on how the URL was defined.
|
||||||
IGNORE_URLS = [
|
IGNORE_URLS = [
|
||||||
# These are the OIDC auth endpoints that always need
|
# These are the OIDC auth endpoints that always need
|
||||||
# to be public.
|
# to be public. Use the exact URLs that will be tested.
|
||||||
"/openid/login/",
|
"/openid/login/",
|
||||||
"/openid/logout/",
|
"/openid/logout/",
|
||||||
"/openid/callback",
|
"/openid/callback",
|
||||||
"/openid/callback/login/",
|
"/openid/callback/login/",
|
||||||
"/openid/callback/logout/",
|
"/openid/callback/logout/",
|
||||||
|
"/api/v1/available/whitehouse.gov",
|
||||||
]
|
]
|
||||||
|
|
||||||
def assertURLIsProtectedByAuth(self, url):
|
def assertURLIsProtectedByAuth(self, url):
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue