Merge pull request #1579 from internetee/add-machine-readable-list-of-blocked-domains

Simple implementation of retained domains API endpoint
This commit is contained in:
Timo Võhmar 2020-05-18 14:51:23 +03:00 committed by GitHub
commit e0b7ce7b94
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 275 additions and 8 deletions

View file

@ -0,0 +1,15 @@
module Repp
module V1
class RetainedDomainsController < ActionController::API
def index
domains = RetainedDomains.new(query_params)
render json: { count: domains.count, domains: domains.to_jsonable }
end
def query_params
params.permit(:type)
end
end
end
end

View file

@ -0,0 +1,69 @@
# Hiding the queries behind its own class will allow us to include disputed or
# auctioned domains without meddling up with controller logic.
class RetainedDomains
RESERVED = 'reserved'.freeze
BLOCKED = 'blocked'.freeze
attr_reader :domains,
:type
def initialize(params)
@type = establish_type(params)
@domains = gather_domains
end
delegate :count, to: :domains
def to_jsonable
domains.map { |el| domain_to_jsonable(el) }
end
private
def establish_type(params)
type = params[:type]
case type
when RESERVED then :reserved
when BLOCKED then :blocked
else :all
end
end
def gather_domains
domains = blocked_domains.to_a.union(reserved_domains.to_a)
domains.sort_by(&:name)
end
def blocked_domains
if %i[all blocked].include?(type)
BlockedDomain.order(name: :desc).all
else
[]
end
end
def reserved_domains
if %i[all reserved].include?(type)
ReservedDomain.order(name: :desc).all
else
[]
end
end
def domain_to_jsonable(domain)
status = case domain
when ReservedDomain then RESERVED
when BlockedDomain then BLOCKED
end
punycode = SimpleIDN.to_ascii(domain.name)
{
name: domain.name,
status: status,
punycode_name: punycode,
}
end
end

View file

@ -39,6 +39,18 @@ Rails.application.routes.draw do
mount Repp::API => '/'
namespace :repp do
namespace :v1 do
resources :retained_domains, only: %i[index]
end
end
match 'repp/v1/retained_domains',
controller: 'api/cors',
action: 'cors_preflight_check',
via: [:options],
as: 'repp_cors_preflight_check'
namespace :api do
namespace :v1 do
namespace :registrant do

View file

@ -0,0 +1,96 @@
## GET /repp/v1/retained_domains
Return a list of reserved and blocked domains, along with total count. You can
filter them by type of the domain, which can be either reserved or blocked.
In contrast with other endpoints in REPP, this one is publicly available for
anyone without authentication.
#### Parameters
| Field name | Required | Type | Allowed values | Description |
| ---------- | -------- | ---- | -------------- | ----------- |
| type | false | string | ["reserved", "blocked"] | Type of domains to show |
#### Request
```
GET /repp/v1/retained_domains?type=reserved HTTP/1.1
Accept: application/json
User-Agent: curl/7.64.1
```
#### Response
```
HTTP/1.1 200 OK
Date: Fri, 15 May 2020 11:30:07 GMT
Content-Type: application/json; charset=utf-8
ETag: W/"a905b531243a6b0be42beb9d6ce60619"
Cache-Control: max-age=0, private, must-revalidate
Transfer-Encoding: chunked
{
"count": 1,
"domains": [
{
"name": "reserved.test",
"status": "reserved",
"punycode_name": "reserved.test"
}
]
}
```
After you have made the first request, you can save the ETag header, and
send it as If-None-Match in the subsequent request for cache validation.
Due to the fact that the lists are not changing frequently and are quite long,
it is recommended that you take advantage of ETag cache.
ETag key values depend on the request parameters. A request for only blocked
domains returns different cache key than request for all domains.
### Cache Request
```
GET /repp/v1/retained_domains?type=reserved HTTP/1.1
Accept: application/json
User-Agent: curl/7.64.1
If-None-Match: W/"a905b531243a6b0be42beb9d6ce60619"
```
#### Cache hit response
Response with no body and status 304 is sent in case the list have not changed.
```
HTTP/1.1 304 Not Modified
Date: Fri, 15 May 2020 11:34:25 GMT
ETag: W/"a905b531243a6b0be42beb9d6ce60619"
Cache-Control: max-age=0, private, must-revalidate
```
#### Cache miss response
Standard 200 response (with the current complete list) is sent when the list have changed since last requested.
```
HTTP/1.1 200 OK
Date: Fri, 15 May 2020 11:30:07 GMT
Content-Type: application/json; charset=utf-8
ETag: W/"a905b531243a6b0be42beb9d6ce60619"
Transfer-Encoding: chunked
{
"count": 1,
"domains": [
{
"name": "reserved.test",
"status": "reserved",
"punycode_name": "reserved.test"
}
]
}
```

View file

@ -1,7 +1,7 @@
# REPP integration specification
REPP uses HTTP/1.1 protocol (http://tools.ietf.org/html/rfc2616) and
Basic Authentication (http://tools.ietf.org/html/rfc2617#section-2) using
REPP uses HTTP/1.1 protocol (http://tools.ietf.org/html/rfc2616) and
Basic Authentication (http://tools.ietf.org/html/rfc2617#section-2) using
Secure Transport (https://tools.ietf.org/html/rfc5246) with certificate and key (https://tools.ietf.org/html/rfc5280).
Credentials and certificate are issued by EIS (in an exchange for desired API username, CSR and IP).
@ -10,13 +10,14 @@ To quickly test the API, use curl:
curl -q -k --cert user.crt.pem --key user.key.pem https://TBA/repp/v1/accounts/balance -u username:password
Test API endpoint: https://testepp.internet.ee/repp/v1
Test API endpoint: https://testepp.internet.ee/repp/v1
Production API endpoint: TBA
Main communication specification through Restful EPP (REPP):
[Contact related functions](repp/v1/contact.md)
[Domain related functions](repp/v1/domain.md)
[Domain transfers](repp/v1/domain_transfers.md)
[Account related functions](repp/v1/account.md)
[Nameservers](repp/v1/nameservers.md)
[Contact related functions](repp/v1/contact.md)
[Domain related functions](repp/v1/domain.md)
[Domain transfers](repp/v1/domain_transfers.md)
[Account related functions](repp/v1/account.md)
[Nameservers](repp/v1/nameservers.md)
[Retained domains](repp/v1/retained_domains.md)

View file

@ -0,0 +1,74 @@
require 'test_helper'
class ReppV1RetainedDomainsTest < ActionDispatch::IntegrationTest
# Uses magical fixtures, will fail once fixtures inside are changed:
# test/fixtures/blocked_domains.yml
# test/fixtures/reserved_domains.yml
def test_get_index
get repp_v1_retained_domains_path
response_json = JSON.parse(response.body, symbolize_names: true)
assert response_json[:count] == 3
expected_objects = [{ name: 'blocked.test',
status: 'blocked',
punycode_name: 'blocked.test' },
{ name: 'blockedäöüõ.test',
status: 'blocked',
punycode_name: 'xn--blocked-cxa7mj0e.test' },
{ name: 'reserved.test',
status: 'reserved',
punycode_name: 'reserved.test' }]
assert_equal response_json[:domains], expected_objects
end
def test_get_index_with_type_parameter
get repp_v1_retained_domains_path({ 'type' => 'reserved' })
response_json = JSON.parse(response.body, symbolize_names: true)
assert response_json[:count] == 1
expected_objects = [{ name: 'reserved.test',
status: 'reserved',
punycode_name: 'reserved.test' }]
assert_equal response_json[:domains], expected_objects
end
def test_etags_cache
get repp_v1_retained_domains_path({ 'type' => 'reserved' })
etag = response.headers['ETag']
get repp_v1_retained_domains_path({ 'type' => 'reserved' }),
headers: { 'If-None-Match' => etag }
assert_equal response.status, 304
assert_equal response.body, ''
end
def test_etags_cache_valid_for_type_only
get repp_v1_retained_domains_path({ 'type' => 'blocked' })
etag = response.headers['ETag']
get repp_v1_retained_domains_path, headers: { 'If-None-Match' => etag }
assert_equal response.status, 200
response_json = JSON.parse(response.body, symbolize_names: true)
assert response_json[:count] == 3
end
def test_cors_preflight
process :options, repp_v1_retained_domains_path, headers: { 'Origin' => 'https://example.com' }
assert_equal('https://example.com', response.headers['Access-Control-Allow-Origin'])
assert_equal('POST, GET, PUT, PATCH, DELETE, OPTIONS',
response.headers['Access-Control-Allow-Methods'])
assert_equal('Origin, Content-Type, Accept, Authorization, Token, Auth-Token, Email, ' \
'X-User-Token, X-User-Email',
response.headers['Access-Control-Allow-Headers'])
assert_equal('3600', response.headers['Access-Control-Max-Age'])
assert_equal('', response.body)
end
end