mirror of
https://github.com/internetee/registry.git
synced 2025-07-24 03:30:33 +02:00
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:
commit
e0b7ce7b94
6 changed files with 275 additions and 8 deletions
15
app/controllers/repp/v1/retained_domains_controller.rb
Normal file
15
app/controllers/repp/v1/retained_domains_controller.rb
Normal 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
|
69
app/models/retained_domains.rb
Normal file
69
app/models/retained_domains.rb
Normal 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
|
|
@ -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
|
||||
|
|
96
doc/repp/v1/retained_domains.md
Normal file
96
doc/repp/v1/retained_domains.md
Normal 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"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
|
@ -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)
|
||||
|
|
74
test/integration/repp/retained_domains_test.rb
Normal file
74
test/integration/repp/retained_domains_test.rb
Normal 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
|
Loading…
Add table
Add a link
Reference in a new issue