mirror of
https://github.com/internetee/registry.git
synced 2025-08-12 12:39:34 +02:00
Merge remote-tracking branch 'origin/master' into 1832-registrar-prefix-on-contact-info-and-check-requests
This commit is contained in:
commit
60f260968a
16 changed files with 238 additions and 8 deletions
10
CHANGELOG.md
10
CHANGELOG.md
|
@ -1,3 +1,13 @@
|
||||||
|
10.02.2021
|
||||||
|
* Option to remove email addresses from AWS SES Supression list [#1839](https://github.com/internetee/registry/issues/1839)
|
||||||
|
* Added separate key for bounce API [#1842](https://github.com/internetee/registry/pull/1842)
|
||||||
|
|
||||||
|
09.02.2021
|
||||||
|
* Added new endpoint for WHOIS contact requests [#1794](https://github.com/internetee/registry/pull/1794)
|
||||||
|
|
||||||
|
05.02.2021
|
||||||
|
* Fixed IPv4 empty string issue in case of IPv6 only entries for IP whitelist [#1833](https://github.com/internetee/registry/issues/1833)
|
||||||
|
|
||||||
02.02.2021
|
02.02.2021
|
||||||
* Fixed updateProhibited status not affecting bulk tech contact change operation [#1820](https://github.com/internetee/registry/pull/1820)
|
* Fixed updateProhibited status not affecting bulk tech contact change operation [#1820](https://github.com/internetee/registry/pull/1820)
|
||||||
|
|
||||||
|
|
2
Gemfile
2
Gemfile
|
@ -92,3 +92,5 @@ group :test do
|
||||||
gem 'webdrivers'
|
gem 'webdrivers'
|
||||||
gem 'webmock'
|
gem 'webmock'
|
||||||
end
|
end
|
||||||
|
|
||||||
|
gem 'aws-sdk-sesv2', '~> 1.16'
|
||||||
|
|
14
Gemfile.lock
14
Gemfile.lock
|
@ -148,6 +148,18 @@ GEM
|
||||||
attr_required (1.0.1)
|
attr_required (1.0.1)
|
||||||
autoprefixer-rails (10.0.0.2)
|
autoprefixer-rails (10.0.0.2)
|
||||||
execjs
|
execjs
|
||||||
|
aws-eventstream (1.1.0)
|
||||||
|
aws-partitions (1.424.0)
|
||||||
|
aws-sdk-core (3.112.0)
|
||||||
|
aws-eventstream (~> 1, >= 1.0.2)
|
||||||
|
aws-partitions (~> 1, >= 1.239.0)
|
||||||
|
aws-sigv4 (~> 1.1)
|
||||||
|
jmespath (~> 1.0)
|
||||||
|
aws-sdk-sesv2 (1.16.0)
|
||||||
|
aws-sdk-core (~> 3, >= 3.112.0)
|
||||||
|
aws-sigv4 (~> 1.1)
|
||||||
|
aws-sigv4 (1.2.2)
|
||||||
|
aws-eventstream (~> 1, >= 1.0.2)
|
||||||
bcrypt (3.1.16)
|
bcrypt (3.1.16)
|
||||||
bindata (2.4.8)
|
bindata (2.4.8)
|
||||||
bootsnap (1.4.8)
|
bootsnap (1.4.8)
|
||||||
|
@ -226,6 +238,7 @@ GEM
|
||||||
i18n_data (0.10.0)
|
i18n_data (0.10.0)
|
||||||
isikukood (0.1.2)
|
isikukood (0.1.2)
|
||||||
iso8601 (0.12.1)
|
iso8601 (0.12.1)
|
||||||
|
jmespath (1.4.0)
|
||||||
jquery-rails (4.4.0)
|
jquery-rails (4.4.0)
|
||||||
rails-dom-testing (>= 1, < 3)
|
rails-dom-testing (>= 1, < 3)
|
||||||
railties (>= 4.2.0)
|
railties (>= 4.2.0)
|
||||||
|
@ -484,6 +497,7 @@ DEPENDENCIES
|
||||||
active_interaction (~> 3.8)
|
active_interaction (~> 3.8)
|
||||||
activerecord-import
|
activerecord-import
|
||||||
airbrake
|
airbrake
|
||||||
|
aws-sdk-sesv2 (~> 1.16)
|
||||||
bootsnap (>= 1.1.0)
|
bootsnap (>= 1.1.0)
|
||||||
bootstrap-sass (~> 3.4)
|
bootstrap-sass (~> 3.4)
|
||||||
cancancan
|
cancancan
|
||||||
|
|
|
@ -11,7 +11,7 @@ module Api
|
||||||
end
|
end
|
||||||
|
|
||||||
def authenticate_shared_key
|
def authenticate_shared_key
|
||||||
api_key = "Basic #{ENV['api_shared_key']}"
|
api_key = "Basic #{ENV['rwhois_internal_api_shared_key']}"
|
||||||
head(:unauthorized) unless api_key == request.authorization
|
head(:unauthorized) unless api_key == request.authorization
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
module Api
|
module Api
|
||||||
module V1
|
module V1
|
||||||
class BouncesController < BaseController
|
class BouncesController < BaseController
|
||||||
before_action :authenticate_shared_key
|
before_action :validate_shared_key_integrity
|
||||||
|
|
||||||
# POST api/v1/bounces/
|
# POST api/v1/bounces/
|
||||||
def create
|
def create
|
||||||
|
@ -20,6 +20,13 @@ module Api
|
||||||
|
|
||||||
params.require(:data)
|
params.require(:data)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def validate_shared_key_integrity
|
||||||
|
api_key = "Basic #{ENV['rwhois_bounces_api_shared_key']}"
|
||||||
|
head(:unauthorized) unless api_key == request.authorization
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
37
app/controllers/api/v1/contact_requests_controller.rb
Normal file
37
app/controllers/api/v1/contact_requests_controller.rb
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
module Api
|
||||||
|
module V1
|
||||||
|
class ContactRequestsController < BaseController
|
||||||
|
before_action :authenticate_shared_key
|
||||||
|
|
||||||
|
# POST api/v1/contact_requests/
|
||||||
|
def create
|
||||||
|
return head(:bad_request) if contact_request_params[:email].blank?
|
||||||
|
|
||||||
|
contact_request = ContactRequest.save_record(contact_request_params)
|
||||||
|
render json: contact_request, status: :created
|
||||||
|
rescue StandardError
|
||||||
|
head(:bad_request)
|
||||||
|
end
|
||||||
|
|
||||||
|
def update
|
||||||
|
return head(:bad_request) if params[:id].blank?
|
||||||
|
|
||||||
|
process_id(params[:id])
|
||||||
|
end
|
||||||
|
|
||||||
|
def process_id(id)
|
||||||
|
record = ContactRequest.find_by(id: id)
|
||||||
|
return :not_found unless record
|
||||||
|
|
||||||
|
record.update_status(contact_request_params)
|
||||||
|
render json: record, status: :ok
|
||||||
|
rescue StandardError
|
||||||
|
head :bad_request
|
||||||
|
end
|
||||||
|
|
||||||
|
def contact_request_params
|
||||||
|
params.require(:contact_request).permit(:email, :whois_record_id, :name, :status, :ip)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -1,5 +1,6 @@
|
||||||
class BouncedMailAddress < ApplicationRecord
|
class BouncedMailAddress < ApplicationRecord
|
||||||
validates :email, :message_id, :bounce_type, :bounce_subtype, :action, :status, presence: true
|
validates :email, :message_id, :bounce_type, :bounce_subtype, :action, :status, presence: true
|
||||||
|
after_destroy :destroy_aws_suppression
|
||||||
|
|
||||||
def bounce_reason
|
def bounce_reason
|
||||||
"#{action} (#{status} #{diagnostic})"
|
"#{action} (#{status} #{diagnostic})"
|
||||||
|
@ -25,4 +26,20 @@ class BouncedMailAddress < ApplicationRecord
|
||||||
diagnostic: bounced_record['diagnosticCode'],
|
diagnostic: bounced_record['diagnosticCode'],
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def destroy_aws_suppression
|
||||||
|
return unless BouncedMailAddress.ses_configured?
|
||||||
|
|
||||||
|
res = Aws::SESV2::Client.new.delete_suppressed_destination(email_address: email)
|
||||||
|
res.successful?
|
||||||
|
rescue Aws::SESV2::Errors::ServiceError => e
|
||||||
|
logger.warn("Suppression not removed. #{e}")
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.ses_configured?
|
||||||
|
ses ||= Aws::SESV2::Client.new
|
||||||
|
ses.config.credentials.access_key_id.present?
|
||||||
|
rescue Aws::Errors::MissingRegionError
|
||||||
|
false
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
40
app/models/contact_request.rb
Normal file
40
app/models/contact_request.rb
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
class ContactRequest < ApplicationRecord
|
||||||
|
establish_connection :"whois_#{Rails.env}"
|
||||||
|
self.table_name = 'contact_requests'
|
||||||
|
|
||||||
|
STATUS_NEW = 'new'.freeze
|
||||||
|
STATUS_CONFIRMED = 'confirmed'.freeze
|
||||||
|
STATUS_SENT = 'sent'.freeze
|
||||||
|
STATUSES = [STATUS_NEW, STATUS_CONFIRMED, STATUS_SENT].freeze
|
||||||
|
|
||||||
|
validates :whois_record_id, presence: true
|
||||||
|
validates :email, presence: true
|
||||||
|
validates :name, presence: true
|
||||||
|
validates :status, inclusion: { in: STATUSES }
|
||||||
|
|
||||||
|
attr_readonly :secret,
|
||||||
|
:valid_to
|
||||||
|
|
||||||
|
def self.save_record(params)
|
||||||
|
contact_request = new(params)
|
||||||
|
contact_request.secret = create_random_secret
|
||||||
|
contact_request.valid_to = set_valid_to_24_hours_from_now
|
||||||
|
contact_request.status = STATUS_NEW
|
||||||
|
contact_request.save!
|
||||||
|
contact_request
|
||||||
|
end
|
||||||
|
|
||||||
|
def update_status(params)
|
||||||
|
self.status = params['status']
|
||||||
|
self.ip_address = params['ip']
|
||||||
|
save!
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.create_random_secret
|
||||||
|
SecureRandom.hex(64)
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.set_valid_to_24_hours_from_now
|
||||||
|
(Time.zone.now + 24.hours)
|
||||||
|
end
|
||||||
|
end
|
|
@ -4,8 +4,13 @@ class WhiteIp < ApplicationRecord
|
||||||
|
|
||||||
validate :valid_ipv4?
|
validate :valid_ipv4?
|
||||||
validate :valid_ipv6?
|
validate :valid_ipv6?
|
||||||
|
|
||||||
validate :validate_ipv4_and_ipv6
|
validate :validate_ipv4_and_ipv6
|
||||||
|
before_save :normalize_blank_values
|
||||||
|
|
||||||
|
def normalize_blank_values
|
||||||
|
%i[ipv4 ipv6].each { |c| self[c].present? || self[c] = nil }
|
||||||
|
end
|
||||||
|
|
||||||
def validate_ipv4_and_ipv6
|
def validate_ipv4_and_ipv6
|
||||||
return if ipv4.present? || ipv6.present?
|
return if ipv4.present? || ipv6.present?
|
||||||
errors.add(:base, I18n.t(:ipv4_or_ipv6_must_be_present))
|
errors.add(:base, I18n.t(:ipv4_or_ipv6_must_be_present))
|
||||||
|
@ -50,10 +55,10 @@ class WhiteIp < ApplicationRecord
|
||||||
def ids_including(ip)
|
def ids_including(ip)
|
||||||
ipv4 = ipv6 = []
|
ipv4 = ipv6 = []
|
||||||
if check_ip4(ip).present?
|
if check_ip4(ip).present?
|
||||||
ipv4 = select { |white_ip| IPAddr.new(white_ip.ipv4, Socket::AF_INET) === check_ip4(ip) }
|
ipv4 = select { |white_ip| check_ip4(white_ip.ipv4) === check_ip4(ip) }
|
||||||
end
|
end
|
||||||
if check_ip6(ip).present?
|
if check_ip6(ip).present?
|
||||||
ipv6 = select { |white_ip| IPAddr.new(white_ip.ipv6, Socket::AF_INET6) === check_ip6(ip) }
|
ipv6 = select { |white_ip| check_ip6(white_ip.ipv6) === check_ip6(ip) }
|
||||||
end
|
end
|
||||||
(ipv4 + ipv6).pluck(:id).flatten.uniq
|
(ipv4 + ipv6).pluck(:id).flatten.uniq
|
||||||
end
|
end
|
||||||
|
|
|
@ -87,8 +87,11 @@ sk_digi_doc_service_name: 'Testimine'
|
||||||
registrant_api_base_url:
|
registrant_api_base_url:
|
||||||
registrant_api_auth_allowed_ips: '127.0.0.1, 0.0.0.0' #ips, separated with commas
|
registrant_api_auth_allowed_ips: '127.0.0.1, 0.0.0.0' #ips, separated with commas
|
||||||
|
|
||||||
# Bounces API
|
# Shared key for REST-WHOIS Bounces API incl. CERT
|
||||||
api_shared_key: testkey
|
rwhois_bounces_api_shared_key: testkey
|
||||||
|
|
||||||
|
# Link to REST-WHOIS API
|
||||||
|
rwhois_internal_api_shared_key: testkey
|
||||||
|
|
||||||
# Base URL (inc. https://) of REST registrant portal
|
# Base URL (inc. https://) of REST registrant portal
|
||||||
# Leave blank to use internal registrant portal
|
# Leave blank to use internal registrant portal
|
||||||
|
|
4
config/initializers/aws_ses.rb
Normal file
4
config/initializers/aws_ses.rb
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
Aws.config.update(
|
||||||
|
region: ENV['aws_default_region'],
|
||||||
|
credentials: Aws::Credentials.new(ENV['aws_access_key_id'], ENV['aws_secret_access_key'])
|
||||||
|
)
|
|
@ -91,6 +91,7 @@ Rails.application.routes.draw do
|
||||||
end
|
end
|
||||||
|
|
||||||
resources :auctions, only: %i[index show update], param: :uuid
|
resources :auctions, only: %i[index show update], param: :uuid
|
||||||
|
resources :contact_requests, only: %i[create update], param: :id
|
||||||
resources :bounces, only: %i[create]
|
resources :bounces, only: %i[create]
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
8
test/fixtures/contact_requests.yml
vendored
Normal file
8
test/fixtures/contact_requests.yml
vendored
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
new:
|
||||||
|
whois_record_id: 1
|
||||||
|
email: aaa@bbb.com
|
||||||
|
name: Testname
|
||||||
|
status: new
|
||||||
|
secret: somesecret
|
||||||
|
valid_to: 2010-07-05
|
||||||
|
|
|
@ -2,7 +2,7 @@ require 'test_helper'
|
||||||
|
|
||||||
class BouncesApiV1CreateTest < ActionDispatch::IntegrationTest
|
class BouncesApiV1CreateTest < ActionDispatch::IntegrationTest
|
||||||
def setup
|
def setup
|
||||||
@api_key = "Basic #{ENV['api_shared_key']}"
|
@api_key = "Basic #{ENV['rwhois_bounces_api_shared_key']}"
|
||||||
@headers = { "Authorization": "#{@api_key}" }
|
@headers = { "Authorization": "#{@api_key}" }
|
||||||
@json_body = { "data": valid_bounce_request }.as_json
|
@json_body = { "data": valid_bounce_request }.as_json
|
||||||
end
|
end
|
||||||
|
|
68
test/integration/api/v1/contact_requests_test.rb
Normal file
68
test/integration/api/v1/contact_requests_test.rb
Normal file
|
@ -0,0 +1,68 @@
|
||||||
|
require 'test_helper'
|
||||||
|
|
||||||
|
class ApiV1ContactRequestTest < ActionDispatch::IntegrationTest
|
||||||
|
def setup
|
||||||
|
@api_key = "Basic #{ENV['rwhois_internal_api_shared_key']}"
|
||||||
|
@headers = { "Authorization": "#{@api_key}" }
|
||||||
|
@json_create = { "contact_request": valid_contact_request_create }.as_json
|
||||||
|
@json_update = { "contact_request": valid_contact_request_update }.as_json
|
||||||
|
@contact_request = contact_requests(:new)
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_authorizes_api_request
|
||||||
|
post api_v1_contact_requests_path, params: @json_create, headers: @headers
|
||||||
|
assert_response :created
|
||||||
|
|
||||||
|
invalid_headers = { "Authorization": "Basic invalid_api_key" }
|
||||||
|
post api_v1_contact_requests_path, params: @json_create, headers: invalid_headers
|
||||||
|
assert_response :unauthorized
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_saves_new_contact_request
|
||||||
|
request_body = @json_create.dup
|
||||||
|
random_mail = "#{rand(10000..99999)}@registry.test"
|
||||||
|
request_body['contact_request']['email'] = random_mail
|
||||||
|
|
||||||
|
post api_v1_contact_requests_path, params: request_body, headers: @headers
|
||||||
|
assert_response :created
|
||||||
|
|
||||||
|
contact_request = ContactRequest.last
|
||||||
|
assert_equal contact_request.email, random_mail
|
||||||
|
assert ContactRequest::STATUS_NEW, contact_request.status
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_updates_existing_contact_request
|
||||||
|
request_body = @json_update.dup
|
||||||
|
|
||||||
|
put api_v1_contact_request_path(@contact_request.id), params: request_body, headers: @headers
|
||||||
|
assert_response :ok
|
||||||
|
|
||||||
|
@contact_request.reload
|
||||||
|
assert ContactRequest::STATUS_CONFIRMED, @contact_request.status
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_not_updates_if_status_error
|
||||||
|
request_body = @json_update.dup
|
||||||
|
request_body['contact_request']['status'] = 'some_error_status'
|
||||||
|
|
||||||
|
put api_v1_contact_request_path(@contact_request.id), params: request_body, headers: @headers
|
||||||
|
assert_response 400
|
||||||
|
|
||||||
|
@contact_request.reload
|
||||||
|
assert ContactRequest::STATUS_NEW, @contact_request.status
|
||||||
|
end
|
||||||
|
|
||||||
|
def valid_contact_request_create
|
||||||
|
{
|
||||||
|
"email": "aaa@bbb.com",
|
||||||
|
"whois_record_id": "1",
|
||||||
|
"name": "test"
|
||||||
|
}.as_json
|
||||||
|
end
|
||||||
|
|
||||||
|
def valid_contact_request_update
|
||||||
|
{
|
||||||
|
"status": "#{ContactRequest::STATUS_CONFIRMED}",
|
||||||
|
}.as_json
|
||||||
|
end
|
||||||
|
end
|
|
@ -38,6 +38,20 @@ class WhiteIpTest < ActiveSupport::TestCase
|
||||||
assert white_ip.valid?
|
assert white_ip.valid?
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def test_validates_include_empty_ipv4
|
||||||
|
white_ip = WhiteIp.new
|
||||||
|
|
||||||
|
white_ip.ipv4 = nil
|
||||||
|
white_ip.ipv6 = '001:0db8:85a3:0000:0000:8a2e:0370:7334'
|
||||||
|
white_ip.registrar = registrars(:bestnames)
|
||||||
|
|
||||||
|
assert_nothing_raised { white_ip.save }
|
||||||
|
assert white_ip.valid?
|
||||||
|
|
||||||
|
assert WhiteIp.include_ip?(white_ip.ipv6)
|
||||||
|
assert_not WhiteIp.include_ip?('192.168.1.1')
|
||||||
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def valid_white_ip
|
def valid_white_ip
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue