-
Notifications
You must be signed in to change notification settings - Fork 18
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
Changes from all commits
2dca24f
3f7dbd5
919fbbf
5439894
23cb568
155e736
a435eb1
d6201fc
a4c4bba
ea4b4e2
2ab29b9
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
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): | ||
"""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) |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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 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 @@ | |
} | ||
|
||
|
||
# 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", "") | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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? There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 :") There was a problem hiding this comment. Choose a reason for hiding this commentThe 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? There was a problem hiding this comment. Choose a reason for hiding this commentThe 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"): | ||
|
There was a problem hiding this comment.
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
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That makes sense