Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

2589: RDAP API endpoint [rh] #2843

Merged
merged 11 commits into from
Sep 30, 2024
66 changes: 66 additions & 0 deletions src/api/tests/test_rdap.py
Original file line number Diff line number Diff line change
@@ -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):
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@abroddrick Since we don't call the RDAP API in any of manage.get.gov's views, I had our tests inherit from TestCase rather than create a mock RDAP library since it would go unused unless we find a reason to add RDAP views into the registrar. I can add a mock RDAP library class if we want to follow availability standards but it will likely go unused until we use RDAP in manage.get.gov and not just get.gov

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That makes sense

"""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)
19 changes: 18 additions & 1 deletion src/api/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -19,6 +19,7 @@


DOMAIN_FILE_URL = "https://raw.githubusercontent.com/cisagov/dotgov-data/main/current-full.csv"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you remove this and any uses of this while your in the code

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@abroddrick sure! I'm not as familiar with where this is used but is current-full no longer referenced in manage.get.gov?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nope it isn't

RDAP_URL = "https://rdap.cloudflareregistry.com/rdap/domain/{domain}"


DOMAIN_API_MESSAGES = {
Expand Down Expand Up @@ -99,6 +100,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", "")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

overall lgtm! I was just wondering why the ttl is 600 seconds? Is that standard?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think generally 5 minutes is one of the standard defaults for caching which we also use in the availability endpoint! If there ends up being a cache time that makes more sense for this data we can also always update it if the need arises :")

Copy link
Contributor

@asaki222 asaki222 Sep 26, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay, LGTM. 600 seconds is 10 minutes. Was that intentional for this cache?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ack you're right, did not math that math correctly :")


# 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"):
Expand Down
3 changes: 2 additions & 1 deletion src/registrar/config/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@
)
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
Expand Down Expand Up @@ -183,6 +183,7 @@
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(
Expand Down
1 change: 1 addition & 0 deletions src/registrar/tests/test_url_auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -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",
]

Expand Down
Loading