diff --git a/src/api/tests/test_rdap.py b/src/api/tests/test_rdap.py new file mode 100644 index 000000000..789a99152 --- /dev/null +++ b/src/api/tests/test_rdap.py @@ -0,0 +1,66 @@ +"""Test the domain rdap lookup API.""" + +import json + +from django.contrib.auth import get_user_model +from django.test import RequestFactory +from django.test import TestCase + +from ..views import rdap + +API_BASE_PATH = "/api/v1/rdap/?domain=" + + +class RdapViewTest(TestCase): + """Test that the RDAP view function works as expected""" + + def setUp(self): + super().setUp() + self.user = get_user_model().objects.create(username="username") + self.factory = RequestFactory() + + def test_rdap_get_no_tld(self): + """RDAP API successfully fetches RDAP for domain without a TLD""" + request = self.factory.get(API_BASE_PATH + "whitehouse") + request.user = self.user + response = rdap(request, domain="whitehouse") + # contains the right text + self.assertContains(response, "rdap") + # can be parsed into JSON with appropriate keys + response_object = json.loads(response.content) + self.assertIn("rdapConformance", response_object) + + def test_rdap_invalid_domain(self): + """RDAP API accepts invalid domain queries and returns JSON response + with appropriate error codes""" + request = self.factory.get(API_BASE_PATH + "whitehouse.com") + request.user = self.user + response = rdap(request, domain="whitehouse.com") + + self.assertContains(response, "errorCode") + response_object = json.loads(response.content) + self.assertIn("errorCode", response_object) + + +class RdapAPITest(TestCase): + """Test that the API can be called as expected.""" + + def setUp(self): + super().setUp() + username = "test_user" + first_name = "First" + last_name = "Last" + email = "info@example.com" + title = "title" + phone = "8080102431" + self.user = get_user_model().objects.create( + username=username, title=title, first_name=first_name, last_name=last_name, email=email, phone=phone + ) + + def test_rdap_get(self): + """Can call RDAP API""" + self.client.force_login(self.user) + response = self.client.get(API_BASE_PATH + "whitehouse.gov") + self.assertContains(response, "rdap") + response_object = json.loads(response.content) + self.assertIn("rdapConformance", response_object) diff --git a/src/api/views.py b/src/api/views.py index 2199e15ac..a7b4bde75 100644 --- a/src/api/views.py +++ b/src/api/views.py @@ -2,7 +2,7 @@ from django.apps import apps from django.views.decorators.http import require_http_methods -from django.http import HttpResponse +from django.http import HttpResponse, JsonResponse from django.utils.safestring import mark_safe from registrar.templatetags.url_helpers import public_site_url @@ -18,7 +18,7 @@ from cachetools.func import ttl_cache from registrar.utility.s3_bucket import S3ClientError, S3ClientHelper -DOMAIN_FILE_URL = "https://raw.githubusercontent.com/cisagov/dotgov-data/main/current-full.csv" +RDAP_URL = "https://rdap.cloudflareregistry.com/rdap/domain/{domain}" DOMAIN_API_MESSAGES = { @@ -41,30 +41,6 @@ DOMAIN_API_MESSAGES = { } -# this file doesn't change that often, nor is it that big, so cache the result -# in memory for ten minutes -@ttl_cache(ttl=600) -def _domains(): - """Return a list of the current .gov domains. - - Fetch a file from DOMAIN_FILE_URL, parse the CSV for the domain, - lowercase everything and return the list. - """ - DraftDomain = apps.get_model("registrar.DraftDomain") - # 5 second timeout - file_contents = requests.get(DOMAIN_FILE_URL, timeout=5).text - domains = set() - # skip the first line - for line in file_contents.splitlines()[1:]: - # get the domain before the first comma - domain = line.split(",", 1)[0] - # sanity-check the string we got from the file here - if DraftDomain.string_could_be_domain(domain): - # lowercase everything when we put it in domains - domains.add(domain.lower()) - return domains - - def check_domain_available(domain): """Return true if the given domain is available. @@ -99,6 +75,22 @@ def available(request, domain=""): return json_response +@require_http_methods(["GET"]) +@login_not_required +# Since we cache domain RDAP data, cache time may need to be re-evaluated this if we encounter any memory issues +@ttl_cache(ttl=600) +def rdap(request, domain=""): + """Returns JSON dictionary of a domain's RDAP data from Cloudflare API""" + domain = request.GET.get("domain", "") + + # If inputted domain doesn't have a TLD, append .gov to it + if "." not in domain: + domain = f"{domain}.gov" + + rdap_data = requests.get(RDAP_URL.format(domain=domain), timeout=5).json() + return JsonResponse(rdap_data) + + @require_http_methods(["GET"]) @login_not_required def get_current_full(request, file_name="current-full.csv"): diff --git a/src/registrar/config/urls.py b/src/registrar/config/urls.py index 76c77955f..df5733238 100644 --- a/src/registrar/config/urls.py +++ b/src/registrar/config/urls.py @@ -32,7 +32,7 @@ from registrar.views.utility.api_views import ( ) from registrar.views.domains_json import get_domains_json from registrar.views.utility import always_404 -from api.views import available, get_current_federal, get_current_full +from api.views import available, rdap, get_current_federal, get_current_full DOMAIN_REQUEST_NAMESPACE = views.DomainRequestWizard.URL_NAMESPACE @@ -194,6 +194,7 @@ urlpatterns = [ path("openid/", include("djangooidc.urls")), 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"), path("api/v1/get-report/current-full", get_current_full, name="get-current-full"), path( diff --git a/src/registrar/tests/test_url_auth.py b/src/registrar/tests/test_url_auth.py index 284ec7638..1cd2d1384 100644 --- a/src/registrar/tests/test_url_auth.py +++ b/src/registrar/tests/test_url_auth.py @@ -116,6 +116,7 @@ class TestURLAuth(TestCase): "/api/v1/available/", "/api/v1/get-report/current-federal", "/api/v1/get-report/current-full", + "/api/v1/rdap/", "/health", ]