mirror of
https://github.com/internetee/registry.git
synced 2025-06-06 20:55:44 +02:00
Merge remote-tracking branch 'origin/master' into 1763-registrar-bulk-renew
This commit is contained in:
commit
00a65b3b01
40 changed files with 818 additions and 160 deletions
10
CHANGELOG.md
10
CHANGELOG.md
|
@ -1,3 +1,13 @@
|
||||||
|
17.12.2020
|
||||||
|
* New API for registering bounced emails [#1687](https://github.com/internetee/registry/pull/1687)
|
||||||
|
|
||||||
|
16.12.2020
|
||||||
|
* Refactored domain delete confirmation for interactors [#1769](https://github.com/internetee/registry/issues/1769)
|
||||||
|
|
||||||
|
15.12.2020
|
||||||
|
* Improved logic for domain list request in registrant API [#1750](https://github.com/internetee/registry/pull/1750)
|
||||||
|
* Refactored Whois update job for interactors [#1771](https://github.com/internetee/registry/issues/1771)
|
||||||
|
|
||||||
14.12.2020
|
14.12.2020
|
||||||
* Refactored domain cron jobs for interactors [#1767](https://github.com/internetee/registry/issues/1767)
|
* Refactored domain cron jobs for interactors [#1767](https://github.com/internetee/registry/issues/1767)
|
||||||
|
|
||||||
|
|
30
app/controllers/admin/bounced_mail_addresses_controller.rb
Normal file
30
app/controllers/admin/bounced_mail_addresses_controller.rb
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
module Admin
|
||||||
|
class BouncedMailAddressesController < BaseController
|
||||||
|
before_action :set_bounced_mail_address, only: %i[show destroy]
|
||||||
|
load_and_authorize_resource
|
||||||
|
|
||||||
|
# GET /bounced_mail_addresses
|
||||||
|
def index
|
||||||
|
@bounced_mail_addresses = BouncedMailAddress.all.order(created_at: :desc)
|
||||||
|
end
|
||||||
|
|
||||||
|
# GET /bounced_mail_addresses/1
|
||||||
|
def show; end
|
||||||
|
|
||||||
|
# DELETE /bounced_mail_addresses/1
|
||||||
|
def destroy
|
||||||
|
@bounced_mail_address.destroy
|
||||||
|
redirect_to(
|
||||||
|
admin_bounced_mail_addresses_url,
|
||||||
|
notice: 'Bounced mail address was successfully destroyed.'
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
# Use callbacks to share common setup or constraints between actions.
|
||||||
|
def set_bounced_mail_address
|
||||||
|
@bounced_mail_address = BouncedMailAddress.find(params[:id])
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -10,6 +10,11 @@ module Api
|
||||||
head :unauthorized unless ip_allowed
|
head :unauthorized unless ip_allowed
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def authenticate_shared_key
|
||||||
|
api_key = "Basic #{ENV['api_shared_key']}"
|
||||||
|
head(:unauthorized) unless api_key == request.authorization
|
||||||
|
end
|
||||||
|
|
||||||
def not_found_error
|
def not_found_error
|
||||||
uuid = params['uuid']
|
uuid = params['uuid']
|
||||||
json = { error: 'Not Found', uuid: uuid, message: 'Record not found' }
|
json = { error: 'Not Found', uuid: uuid, message: 'Record not found' }
|
||||||
|
|
25
app/controllers/api/v1/bounces_controller.rb
Normal file
25
app/controllers/api/v1/bounces_controller.rb
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
module Api
|
||||||
|
module V1
|
||||||
|
class BouncesController < BaseController
|
||||||
|
before_action :authenticate_shared_key
|
||||||
|
|
||||||
|
# POST api/v1/bounces/
|
||||||
|
def create
|
||||||
|
return head(:bad_request) unless bounce_params[:bounce][:bouncedRecipients].any?
|
||||||
|
|
||||||
|
BouncedMailAddress.record(bounce_params)
|
||||||
|
head(:created)
|
||||||
|
rescue ActionController::ParameterMissing
|
||||||
|
head(:bad_request)
|
||||||
|
end
|
||||||
|
|
||||||
|
def bounce_params
|
||||||
|
params.require(:data).require(:bounce).require(:bouncedRecipients).each do |r|
|
||||||
|
r.require(:emailAddress)
|
||||||
|
end
|
||||||
|
|
||||||
|
params.require(:data)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -19,15 +19,16 @@ module Api
|
||||||
end
|
end
|
||||||
|
|
||||||
contacts = current_user_contacts.limit(limit).offset(offset)
|
contacts = current_user_contacts.limit(limit).offset(offset)
|
||||||
serialized_contacts = contacts.collect { |contact| serialize_contact(contact) }
|
serialized_contacts = contacts.collect { |contact| serialize_contact(contact, false) }
|
||||||
render json: serialized_contacts
|
render json: serialized_contacts
|
||||||
end
|
end
|
||||||
|
|
||||||
def show
|
def show
|
||||||
contact = current_user_contacts.find_by(uuid: params[:uuid])
|
contact = current_user_contacts.find_by(uuid: params[:uuid])
|
||||||
|
links = params[:links] == 'true'
|
||||||
|
|
||||||
if contact
|
if contact
|
||||||
render json: serialize_contact(contact)
|
render json: serialize_contact(contact, links)
|
||||||
else
|
else
|
||||||
render json: { errors: [{ base: ['Contact not found'] }] }, status: :not_found
|
render json: { errors: [{ base: ['Contact not found'] }] }, status: :not_found
|
||||||
end
|
end
|
||||||
|
@ -85,7 +86,7 @@ module Api
|
||||||
contact.registrar.notify(action)
|
contact.registrar.notify(action)
|
||||||
end
|
end
|
||||||
|
|
||||||
render json: serialize_contact(contact)
|
render json: serialize_contact(contact, false)
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
@ -96,8 +97,8 @@ module Api
|
||||||
current_registrant_user.direct_contacts
|
current_registrant_user.direct_contacts
|
||||||
end
|
end
|
||||||
|
|
||||||
def serialize_contact(contact)
|
def serialize_contact(contact, links)
|
||||||
Serializers::RegistrantApi::Contact.new(contact).to_json
|
Serializers::RegistrantApi::Contact.new(contact, links).to_json
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -7,6 +7,7 @@ module Api
|
||||||
def index
|
def index
|
||||||
limit = params[:limit] || 200
|
limit = params[:limit] || 200
|
||||||
offset = params[:offset] || 0
|
offset = params[:offset] || 0
|
||||||
|
simple = params[:simple] == 'true' || false
|
||||||
|
|
||||||
if limit.to_i > 200 || limit.to_i < 1
|
if limit.to_i > 200 || limit.to_i < 1
|
||||||
render(json: { errors: [{ limit: ['parameter is out of range'] }] },
|
render(json: { errors: [{ limit: ['parameter is out of range'] }] },
|
||||||
|
@ -18,21 +19,20 @@ module Api
|
||||||
status: :bad_request) && return
|
status: :bad_request) && return
|
||||||
end
|
end
|
||||||
|
|
||||||
@domains = current_user_domains.limit(limit).offset(offset)
|
domains = current_user_domains
|
||||||
|
serialized_domains = domains.limit(limit).offset(offset).map do |item|
|
||||||
serialized_domains = @domains.map do |item|
|
serializer = Serializers::RegistrantApi::Domain.new(item, simplify: simple)
|
||||||
serializer = Serializers::RegistrantApi::Domain.new(item)
|
|
||||||
serializer.to_json
|
serializer.to_json
|
||||||
end
|
end
|
||||||
|
|
||||||
render json: serialized_domains
|
render json: { count: domains.count, domains: serialized_domains }
|
||||||
end
|
end
|
||||||
|
|
||||||
def show
|
def show
|
||||||
@domain = current_user_domains.find_by(uuid: params[:uuid])
|
@domain = current_user_domains.find_by(uuid: params[:uuid])
|
||||||
|
|
||||||
if @domain
|
if @domain
|
||||||
serializer = Serializers::RegistrantApi::Domain.new(@domain)
|
serializer = Serializers::RegistrantApi::Domain.new(@domain, simplify: false)
|
||||||
render json: serializer.to_json
|
render json: serializer.to_json
|
||||||
else
|
else
|
||||||
render json: { errors: [{ base: ['Domain not found'] }] }, status: :not_found
|
render json: { errors: [{ base: ['Domain not found'] }] }, status: :not_found
|
||||||
|
|
|
@ -62,6 +62,7 @@ class Registrar
|
||||||
|
|
||||||
def find_user_by_idc_and_allowed(idc)
|
def find_user_by_idc_and_allowed(idc)
|
||||||
return User.new unless idc
|
return User.new unless idc
|
||||||
|
|
||||||
possible_users = ApiUser.where(identity_code: idc) || User.new
|
possible_users = ApiUser.where(identity_code: idc) || User.new
|
||||||
possible_users.each do |selected_user|
|
possible_users.each do |selected_user|
|
||||||
if selected_user.registrar.white_ips.registrar_area.include_ip?(request.ip)
|
if selected_user.registrar.white_ips.registrar_area.include_ip?(request.ip)
|
||||||
|
|
53
app/interactions/domains/delete_confirm/base.rb
Normal file
53
app/interactions/domains/delete_confirm/base.rb
Normal file
|
@ -0,0 +1,53 @@
|
||||||
|
module Domains
|
||||||
|
module DeleteConfirm
|
||||||
|
class Base < ActiveInteraction::Base
|
||||||
|
object :domain,
|
||||||
|
class: Domain,
|
||||||
|
description: 'Domain to confirm release'
|
||||||
|
string :action
|
||||||
|
string :initiator,
|
||||||
|
default: nil
|
||||||
|
|
||||||
|
validates :domain, :action, presence: true
|
||||||
|
validates :action, inclusion: { in: [RegistrantVerification::CONFIRMED,
|
||||||
|
RegistrantVerification::REJECTED] }
|
||||||
|
|
||||||
|
def raise_errors!(domain)
|
||||||
|
return unless domain.errors.any?
|
||||||
|
|
||||||
|
message = "domain #{domain.name} failed with errors #{domain.errors.full_messages}"
|
||||||
|
throw message
|
||||||
|
end
|
||||||
|
|
||||||
|
def notify_registrar(message_key)
|
||||||
|
domain.registrar.notifications.create!(
|
||||||
|
text: "#{I18n.t(message_key)}: #{domain.name}",
|
||||||
|
attached_obj_id: domain.id,
|
||||||
|
attached_obj_type: domain.class.to_s
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
def preclean_pendings
|
||||||
|
domain.registrant_verification_token = nil
|
||||||
|
domain.registrant_verification_asked_at = nil
|
||||||
|
end
|
||||||
|
|
||||||
|
def clean_pendings!
|
||||||
|
domain.is_admin = true
|
||||||
|
domain.registrant_verification_token = nil
|
||||||
|
domain.registrant_verification_asked_at = nil
|
||||||
|
domain.pending_json = {}
|
||||||
|
clear_statuses
|
||||||
|
domain.save
|
||||||
|
end
|
||||||
|
|
||||||
|
def clear_statuses
|
||||||
|
domain.statuses.delete(DomainStatus::PENDING_DELETE_CONFIRMATION)
|
||||||
|
domain.statuses.delete(DomainStatus::PENDING_UPDATE)
|
||||||
|
domain.statuses.delete(DomainStatus::PENDING_DELETE)
|
||||||
|
domain.status_notes[DomainStatus::PENDING_UPDATE] = ''
|
||||||
|
domain.status_notes[DomainStatus::PENDING_DELETE] = ''
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
17
app/interactions/domains/delete_confirm/process_action.rb
Normal file
17
app/interactions/domains/delete_confirm/process_action.rb
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
module Domains
|
||||||
|
module DeleteConfirm
|
||||||
|
class ProcessAction < Base
|
||||||
|
def execute
|
||||||
|
::PaperTrail.request.whodunnit = "interaction - #{self.class.name} - #{action} by"\
|
||||||
|
" #{initiator}"
|
||||||
|
|
||||||
|
case action
|
||||||
|
when RegistrantVerification::CONFIRMED
|
||||||
|
compose(ProcessDeleteConfirmed, inputs)
|
||||||
|
when RegistrantVerification::REJECTED
|
||||||
|
compose(ProcessDeleteRejected, inputs)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,50 @@
|
||||||
|
module Domains
|
||||||
|
module DeleteConfirm
|
||||||
|
class ProcessDeleteConfirmed < Base
|
||||||
|
def execute
|
||||||
|
notify_registrar(:poll_pending_delete_confirmed_by_registrant)
|
||||||
|
domain.apply_pending_delete!
|
||||||
|
raise_errors!(domain)
|
||||||
|
end
|
||||||
|
|
||||||
|
def apply_pending_delete!
|
||||||
|
preclean_pendings
|
||||||
|
clean_pendings!
|
||||||
|
DomainDeleteMailer.accepted(domain).deliver_now
|
||||||
|
domain.set_pending_delete!
|
||||||
|
end
|
||||||
|
|
||||||
|
def set_pending_delete!
|
||||||
|
unless domain.pending_deletable?
|
||||||
|
add_epp_error
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
domain.delete_date = delete_date
|
||||||
|
domain.statuses << DomainStatus::PENDING_DELETE
|
||||||
|
set_server_hold if server_holdable?
|
||||||
|
domain.save(validate: false)
|
||||||
|
end
|
||||||
|
|
||||||
|
def set_server_hold
|
||||||
|
domain.statuses << DomainStatus::SERVER_HOLD
|
||||||
|
domain.outzone_at = Time.current
|
||||||
|
end
|
||||||
|
|
||||||
|
def server_holdable?
|
||||||
|
return false if domain.statuses.include?(DomainStatus::SERVER_HOLD)
|
||||||
|
return false if domain.statuses.include?(DomainStatus::SERVER_MANUAL_INZONE)
|
||||||
|
|
||||||
|
true
|
||||||
|
end
|
||||||
|
|
||||||
|
def delete_date
|
||||||
|
Time.zone.today + Setting.redemption_grace_period.days + 1.day
|
||||||
|
end
|
||||||
|
|
||||||
|
def add_epp_error
|
||||||
|
domain.add_epp_error('2304', nil, nil, I18n.t(:object_status_prohibits_operation))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,32 @@
|
||||||
|
module Domains
|
||||||
|
module DeleteConfirm
|
||||||
|
class ProcessDeleteRejected < Base
|
||||||
|
def execute
|
||||||
|
domain.cancel_pending_delete
|
||||||
|
notify_registrar(:poll_pending_delete_rejected_by_registrant)
|
||||||
|
domain.save(validate: false)
|
||||||
|
raise_errors!(domain)
|
||||||
|
|
||||||
|
send_domain_delete_rejected_email
|
||||||
|
end
|
||||||
|
|
||||||
|
def send_domain_delete_rejected_email
|
||||||
|
if domain.registrant_verification_token.blank?
|
||||||
|
warn "EMAIL NOT DELIVERED: registrant_verification_token is missing for #{domain.name}"
|
||||||
|
elsif domain.registrant_verification_asked_at.blank?
|
||||||
|
warn "EMAIL NOT DELIVERED: registrant_verification_asked_at is missing for #{domain.name}"
|
||||||
|
else
|
||||||
|
send_email
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def warn(message)
|
||||||
|
Rails.logger.warn(message)
|
||||||
|
end
|
||||||
|
|
||||||
|
def send_email
|
||||||
|
DomainDeleteMailer.rejected(domain).deliver_now
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -1,5 +1,5 @@
|
||||||
module Domains
|
module Domains
|
||||||
module DeleteConfirm
|
module DeleteConfirmEmail
|
||||||
class SendRequest < ActiveInteraction::Base
|
class SendRequest < ActiveInteraction::Base
|
||||||
object :domain,
|
object :domain,
|
||||||
class: Domain,
|
class: Domain,
|
48
app/interactions/whois/delete_record.rb
Normal file
48
app/interactions/whois/delete_record.rb
Normal file
|
@ -0,0 +1,48 @@
|
||||||
|
module Whois
|
||||||
|
class DeleteRecord < ActiveInteraction::Base
|
||||||
|
string :name
|
||||||
|
string :type
|
||||||
|
|
||||||
|
validates :type, inclusion: { in: %w[reserved blocked domain disputed zone] }
|
||||||
|
|
||||||
|
def execute
|
||||||
|
send "delete_#{type}", name
|
||||||
|
end
|
||||||
|
|
||||||
|
# 1. deleting own
|
||||||
|
# 2. trying to regenerate reserved in order domain is still in the list
|
||||||
|
def delete_domain(name)
|
||||||
|
WhoisRecord.where(name: name).destroy_all
|
||||||
|
|
||||||
|
BlockedDomain.find_by(name: name).try(:generate_data)
|
||||||
|
ReservedDomain.find_by(name: name).try(:generate_data)
|
||||||
|
Dispute.active.find_by(domain_name: name).try(:generate_data)
|
||||||
|
end
|
||||||
|
|
||||||
|
def delete_reserved(name)
|
||||||
|
remove_status_from_whois(domain_name: name, domain_status: 'Reserved')
|
||||||
|
end
|
||||||
|
|
||||||
|
def delete_blocked(name)
|
||||||
|
delete_reserved(name)
|
||||||
|
end
|
||||||
|
|
||||||
|
def delete_disputed(name)
|
||||||
|
return if Dispute.active.find_by(domain_name: name).present?
|
||||||
|
|
||||||
|
remove_status_from_whois(domain_name: name, domain_status: 'disputed')
|
||||||
|
end
|
||||||
|
|
||||||
|
def delete_zone(name)
|
||||||
|
WhoisRecord.where(name: name).destroy_all
|
||||||
|
Whois::Record.where(name: name).destroy_all
|
||||||
|
end
|
||||||
|
|
||||||
|
def remove_status_from_whois(domain_name:, domain_status:)
|
||||||
|
Whois::Record.where(name: domain_name).each do |r|
|
||||||
|
r.json['status'] = r.json['status'].delete_if { |status| status == domain_status }
|
||||||
|
r.json['status'].blank? ? r.destroy : r.save
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
39
app/interactions/whois/update.rb
Normal file
39
app/interactions/whois/update.rb
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
module Whois
|
||||||
|
class Update < ActiveInteraction::Base
|
||||||
|
array :names
|
||||||
|
string :type
|
||||||
|
|
||||||
|
validates :type, inclusion: { in: %w[reserved blocked domain disputed zone] }
|
||||||
|
|
||||||
|
def execute
|
||||||
|
::PaperTrail.request.whodunnit = "job - #{self.class.name} - #{type}"
|
||||||
|
|
||||||
|
klass = determine_class
|
||||||
|
|
||||||
|
Array(names).each do |name|
|
||||||
|
record = find_record(klass, name)
|
||||||
|
if record
|
||||||
|
Whois::UpdateRecord.run(record: record, type: type)
|
||||||
|
else
|
||||||
|
Whois::DeleteRecord.run(name: name, type: type)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def determine_class
|
||||||
|
case type
|
||||||
|
when 'reserved' then ReservedDomain
|
||||||
|
when 'blocked' then BlockedDomain
|
||||||
|
when 'domain' then Domain
|
||||||
|
when 'disputed' then Dispute.active
|
||||||
|
else DNS::Zone
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def find_record(klass, name)
|
||||||
|
klass == DNS::Zone ? klass.find_by(origin: name) : klass.find_by(name: name)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
32
app/interactions/whois/update_record.rb
Normal file
32
app/interactions/whois/update_record.rb
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
module Whois
|
||||||
|
class UpdateRecord < ActiveInteraction::Base
|
||||||
|
interface :record
|
||||||
|
string :type
|
||||||
|
|
||||||
|
validates :type, inclusion: { in: %w[reserved blocked domain disputed zone] }
|
||||||
|
|
||||||
|
def execute
|
||||||
|
send "update_#{type}", record
|
||||||
|
end
|
||||||
|
|
||||||
|
def update_domain(domain)
|
||||||
|
domain.whois_record ? domain.whois_record.save : domain.create_whois_record
|
||||||
|
end
|
||||||
|
|
||||||
|
def update_reserved(record)
|
||||||
|
record.generate_data
|
||||||
|
end
|
||||||
|
|
||||||
|
def update_blocked(record)
|
||||||
|
update_reserved(record)
|
||||||
|
end
|
||||||
|
|
||||||
|
def update_disputed(record)
|
||||||
|
update_reserved(record)
|
||||||
|
end
|
||||||
|
|
||||||
|
def update_zone(record)
|
||||||
|
update_reserved(record)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -1,39 +1,11 @@
|
||||||
class DomainDeleteConfirmJob < Que::Job
|
class DomainDeleteConfirmJob < ApplicationJob
|
||||||
def run(domain_id, action, initiator = nil)
|
queue_as :default
|
||||||
::PaperTrail.request.whodunnit = "job - #{self.class.name} - #{action} by #{initiator}"
|
|
||||||
# it's recommended to keep transaction against job table as short as possible.
|
|
||||||
ActiveRecord::Base.transaction do
|
|
||||||
domain = Epp::Domain.find(domain_id)
|
|
||||||
|
|
||||||
case action
|
def perform(domain_id, action, initiator = nil)
|
||||||
when RegistrantVerification::CONFIRMED
|
domain = Epp::Domain.find(domain_id)
|
||||||
domain.notify_registrar(:poll_pending_delete_confirmed_by_registrant)
|
|
||||||
domain.apply_pending_delete!
|
|
||||||
raise_errors!(domain)
|
|
||||||
|
|
||||||
when RegistrantVerification::REJECTED
|
Domains::DeleteConfirm::ProcessAction.run(domain: domain,
|
||||||
domain.statuses.delete(DomainStatus::PENDING_DELETE_CONFIRMATION)
|
action: action,
|
||||||
domain.notify_registrar(:poll_pending_delete_rejected_by_registrant)
|
initiator: initiator)
|
||||||
|
|
||||||
domain.cancel_pending_delete
|
|
||||||
domain.save(validate: false)
|
|
||||||
raise_errors!(domain)
|
|
||||||
|
|
||||||
if domain.registrant_verification_token.blank?
|
|
||||||
Rails.logger.warn "EMAIL NOT DELIVERED: registrant_verification_token is missing for #{domain.name}"
|
|
||||||
elsif domain.registrant_verification_asked_at.blank?
|
|
||||||
Rails.logger.warn "EMAIL NOT DELIVERED: registrant_verification_asked_at is missing for #{domain.name}"
|
|
||||||
else
|
|
||||||
DomainDeleteMailer.rejected(domain).deliver_now
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
destroy # it's best to destroy the job in the same transaction
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
def raise_errors!(domain)
|
|
||||||
throw "domain #{domain.name} failed with errors #{domain.errors.full_messages}" if domain.errors.any?
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,87 +1,5 @@
|
||||||
class UpdateWhoisRecordJob < Que::Job
|
class UpdateWhoisRecordJob < Que::Job
|
||||||
|
|
||||||
def run(names, type)
|
def run(names, type)
|
||||||
::PaperTrail.request.whodunnit = "job - #{self.class.name} - #{type}"
|
Whois::Update.run(names: [names].flatten, type: type)
|
||||||
|
|
||||||
klass = determine_class(type)
|
|
||||||
|
|
||||||
Array(names).each do |name|
|
|
||||||
record = find_record(klass, name)
|
|
||||||
if record
|
|
||||||
send "update_#{type}", record
|
|
||||||
else
|
|
||||||
send "delete_#{type}", name
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def find_record(klass, name)
|
|
||||||
klass == DNS::Zone ? klass.find_by(origin: name) : klass.find_by(name: name)
|
|
||||||
end
|
|
||||||
|
|
||||||
def determine_class(type)
|
|
||||||
case type
|
|
||||||
when 'reserved' then ReservedDomain
|
|
||||||
when 'blocked' then BlockedDomain
|
|
||||||
when 'domain' then Domain
|
|
||||||
when 'disputed' then Dispute.active
|
|
||||||
when 'zone' then DNS::Zone
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def update_domain(domain)
|
|
||||||
domain.whois_record ? domain.whois_record.save : domain.create_whois_record
|
|
||||||
end
|
|
||||||
|
|
||||||
def update_reserved(record)
|
|
||||||
record.generate_data
|
|
||||||
end
|
|
||||||
|
|
||||||
def update_blocked(record)
|
|
||||||
update_reserved(record)
|
|
||||||
end
|
|
||||||
|
|
||||||
def update_disputed(record)
|
|
||||||
update_reserved(record)
|
|
||||||
end
|
|
||||||
|
|
||||||
def update_zone(record)
|
|
||||||
update_reserved(record)
|
|
||||||
end
|
|
||||||
|
|
||||||
# 1. deleting own
|
|
||||||
# 2. trying to regenerate reserved in order domain is still in the list
|
|
||||||
def delete_domain(name)
|
|
||||||
WhoisRecord.where(name: name).destroy_all
|
|
||||||
|
|
||||||
BlockedDomain.find_by(name: name).try(:generate_data)
|
|
||||||
ReservedDomain.find_by(name: name).try(:generate_data)
|
|
||||||
Dispute.active.find_by(domain_name: name).try(:generate_data)
|
|
||||||
end
|
|
||||||
|
|
||||||
def delete_reserved(name)
|
|
||||||
remove_status_from_whois(domain_name: name, domain_status: 'Reserved')
|
|
||||||
end
|
|
||||||
|
|
||||||
def delete_blocked(name)
|
|
||||||
delete_reserved(name)
|
|
||||||
end
|
|
||||||
|
|
||||||
def delete_disputed(name)
|
|
||||||
return if Dispute.active.find_by(domain_name: name).present?
|
|
||||||
|
|
||||||
remove_status_from_whois(domain_name: name, domain_status: 'disputed')
|
|
||||||
end
|
|
||||||
|
|
||||||
def delete_zone(name)
|
|
||||||
WhoisRecord.where(name: name).destroy_all
|
|
||||||
Whois::Record.where(name: name).destroy_all
|
|
||||||
end
|
|
||||||
|
|
||||||
def remove_status_from_whois(domain_name:, domain_status:)
|
|
||||||
Whois::Record.where(name: domain_name).each do |r|
|
|
||||||
r.json['status'] = r.json['status'].delete_if { |status| status == domain_status }
|
|
||||||
r.json['status'].blank? ? r.destroy : r.save
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -109,6 +109,7 @@ class Ability
|
||||||
can :destroy, :pending
|
can :destroy, :pending
|
||||||
can :create, :zonefile
|
can :create, :zonefile
|
||||||
can :access, :settings_menu
|
can :access, :settings_menu
|
||||||
|
can :manage, BouncedMailAddress
|
||||||
end
|
end
|
||||||
|
|
||||||
def static_registrant
|
def static_registrant
|
||||||
|
|
28
app/models/bounced_mail_address.rb
Normal file
28
app/models/bounced_mail_address.rb
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
class BouncedMailAddress < ApplicationRecord
|
||||||
|
validates :email, :message_id, :bounce_type, :bounce_subtype, :action, :status, presence: true
|
||||||
|
|
||||||
|
def bounce_reason
|
||||||
|
"#{action} (#{status} #{diagnostic})"
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.record(json)
|
||||||
|
bounced_records = json['bounce']['bouncedRecipients']
|
||||||
|
bounced_records.each do |record|
|
||||||
|
bounce_record = BouncedMailAddress.new(params_from_json(json, record))
|
||||||
|
|
||||||
|
bounce_record.save
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.params_from_json(json, bounced_record)
|
||||||
|
{
|
||||||
|
email: bounced_record['emailAddress'],
|
||||||
|
message_id: json['mail']['messageId'],
|
||||||
|
bounce_type: json['bounce']['bounceType'],
|
||||||
|
bounce_subtype: json['bounce']['bounceSubType'],
|
||||||
|
action: bounced_record['action'],
|
||||||
|
status: bounced_record['status'],
|
||||||
|
diagnostic: bounced_record['diagnosticCode'],
|
||||||
|
}
|
||||||
|
end
|
||||||
|
end
|
|
@ -347,19 +347,24 @@ class Contact < ApplicationRecord
|
||||||
@desc = {}
|
@desc = {}
|
||||||
|
|
||||||
registrant_domains.each do |dom|
|
registrant_domains.each do |dom|
|
||||||
@desc[dom.name] ||= []
|
@desc[dom.name] ||= { id: dom.uuid, roles: [] }
|
||||||
@desc[dom.name] << :registrant
|
@desc[dom.name][:roles] << :registrant
|
||||||
end
|
end
|
||||||
|
|
||||||
domain_contacts.each do |dc|
|
domain_contacts.each do |dc|
|
||||||
@desc[dc.domain.name] ||= []
|
@desc[dc.domain.name] ||= { id: dc.domain.uuid, roles: [] }
|
||||||
@desc[dc.domain.name] << dc.name.downcase.to_sym
|
@desc[dc.domain.name][:roles] << dc.name.downcase.to_sym
|
||||||
@desc[dc.domain.name] = @desc[dc.domain.name].compact
|
@desc[dc.domain.name] = @desc[dc.domain.name].compact
|
||||||
end
|
end
|
||||||
|
|
||||||
@desc
|
@desc
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def related_domains
|
||||||
|
a = related_domain_descriptions
|
||||||
|
a.keys.map { |d| { name: d, id: a[d][:id], roles: a[d][:roles] } }
|
||||||
|
end
|
||||||
|
|
||||||
def status_notes_array=(notes)
|
def status_notes_array=(notes)
|
||||||
self.status_notes = {}
|
self.status_notes = {}
|
||||||
notes ||= []
|
notes ||= []
|
||||||
|
|
|
@ -421,7 +421,7 @@ class Domain < ApplicationRecord
|
||||||
pending_delete_confirmation!
|
pending_delete_confirmation!
|
||||||
save(validate: false) # should check if this did succeed
|
save(validate: false) # should check if this did succeed
|
||||||
|
|
||||||
Domains::DeleteConfirm::SendRequest.run(domain: self)
|
Domains::DeleteConfirmEmail::SendRequest.run(domain: self)
|
||||||
end
|
end
|
||||||
|
|
||||||
def cancel_pending_delete
|
def cancel_pending_delete
|
||||||
|
|
|
@ -30,12 +30,12 @@ class RegistrantVerification < ApplicationRecord
|
||||||
def domain_registrant_delete_confirm!(initiator)
|
def domain_registrant_delete_confirm!(initiator)
|
||||||
self.action_type = DOMAIN_DELETE
|
self.action_type = DOMAIN_DELETE
|
||||||
self.action = CONFIRMED
|
self.action = CONFIRMED
|
||||||
DomainDeleteConfirmJob.enqueue domain.id, CONFIRMED, initiator if save
|
DomainDeleteConfirmJob.perform_later domain.id, CONFIRMED, initiator if save
|
||||||
end
|
end
|
||||||
|
|
||||||
def domain_registrant_delete_reject!(initiator)
|
def domain_registrant_delete_reject!(initiator)
|
||||||
self.action_type = DOMAIN_DELETE
|
self.action_type = DOMAIN_DELETE
|
||||||
self.action = REJECTED
|
self.action = REJECTED
|
||||||
DomainDeleteConfirmJob.enqueue domain.id, REJECTED, initiator if save
|
DomainDeleteConfirmJob.perform_later domain.id, REJECTED, initiator if save
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -33,6 +33,7 @@
|
||||||
%li= link_to t('.blocked_domains'), admin_blocked_domains_path
|
%li= link_to t('.blocked_domains'), admin_blocked_domains_path
|
||||||
%li= link_to t('.reserved_domains'), admin_reserved_domains_path
|
%li= link_to t('.reserved_domains'), admin_reserved_domains_path
|
||||||
%li= link_to t('.disputed_domains'), admin_disputes_path
|
%li= link_to t('.disputed_domains'), admin_disputes_path
|
||||||
|
%li= link_to t('.bounced_email_addresses'), admin_bounced_mail_addresses_path
|
||||||
%li= link_to t('.epp_log'), admin_epp_logs_path(created_after: 'today')
|
%li= link_to t('.epp_log'), admin_epp_logs_path(created_after: 'today')
|
||||||
%li= link_to t('.repp_log'), admin_repp_logs_path(created_after: 'today')
|
%li= link_to t('.repp_log'), admin_repp_logs_path(created_after: 'today')
|
||||||
%li= link_to t('.que'), '/admin/que'
|
%li= link_to t('.que'), '/admin/que'
|
||||||
|
|
36
app/views/admin/bounced_mail_addresses/index.html.erb
Normal file
36
app/views/admin/bounced_mail_addresses/index.html.erb
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
<h1>Bounced Mail Addresses</h1>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-12">
|
||||||
|
<div class="table-responsive">
|
||||||
|
<table class="table table-hover table-bordered table-condensed">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Email</th>
|
||||||
|
<th>Action</th>
|
||||||
|
<th>Status</th>
|
||||||
|
<th>Diagnostic</th>
|
||||||
|
<th>Tracked</th>
|
||||||
|
<th colspan="2">Actions</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
|
||||||
|
<tbody>
|
||||||
|
<% @bounced_mail_addresses.each do |mail_addr| %>
|
||||||
|
<tr>
|
||||||
|
<td><%= mail_addr.email %></td>
|
||||||
|
<td><%= mail_addr.action %></td>
|
||||||
|
<td><%= mail_addr.status %></td>
|
||||||
|
<td><%= mail_addr.diagnostic %></td>
|
||||||
|
<td><%= mail_addr.created_at %></td>
|
||||||
|
<td><%= link_to 'Detailed', admin_bounced_mail_address_path(mail_addr) %></td>
|
||||||
|
<td><%= link_to 'Destroy', admin_bounced_mail_address_path(mail_addr), method: :delete, data: { confirm: 'Are you sure?' } %></td>
|
||||||
|
</tr>
|
||||||
|
<% end %>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
27
app/views/admin/bounced_mail_addresses/show.html.erb
Normal file
27
app/views/admin/bounced_mail_addresses/show.html.erb
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
<p>
|
||||||
|
<strong>Email:</strong>
|
||||||
|
<%= @bounced_mail_address.email %>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
<strong>Bounced message ID:</strong>
|
||||||
|
<%= @bounced_mail_address.message_id %>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
<strong>Overall bounce type:</strong>
|
||||||
|
<%= @bounced_mail_address.bounce_type %> (<%= @bounced_mail_address.bounce_subtype %> )
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
<strong>Bounced recipient status:</strong>
|
||||||
|
<%= @bounced_mail_address.action %> (<%= @bounced_mail_address.status %>)
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
<strong>Bounced recipient diagnostic:</strong>
|
||||||
|
<pre><%= @bounced_mail_address.diagnostic %></pre>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<%= link_to 'Back', admin_bounced_mail_addresses_path %>
|
||||||
|
<%= link_to 'Destroy', admin_bounced_mail_address_path(@bounced_mail_address), method: :delete, data: { confirm: 'Are you sure?' } %></td>
|
|
@ -87,6 +87,9 @@ 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
|
||||||
|
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
|
||||||
registrant_portal_verifications_base_url: ''
|
registrant_portal_verifications_base_url: ''
|
||||||
|
|
|
@ -14,6 +14,7 @@ en:
|
||||||
blocked_domains: Blocked domains
|
blocked_domains: Blocked domains
|
||||||
reserved_domains: Reserved domains
|
reserved_domains: Reserved domains
|
||||||
disputed_domains: Disputed domains
|
disputed_domains: Disputed domains
|
||||||
|
bounced_email_addresses: Bounced emails
|
||||||
epp_log: EPP log
|
epp_log: EPP log
|
||||||
repp_log: REPP log
|
repp_log: REPP log
|
||||||
que: Que
|
que: Que
|
||||||
|
|
|
@ -91,7 +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 :bounces, only: %i[create]
|
||||||
end
|
end
|
||||||
|
|
||||||
match '*all', controller: 'cors', action: 'cors_preflight_check', via: [:options],
|
match '*all', controller: 'cors', action: 'cors_preflight_check', via: [:options],
|
||||||
|
@ -322,6 +322,7 @@ Rails.application.routes.draw do
|
||||||
resources :delayed_jobs
|
resources :delayed_jobs
|
||||||
resources :epp_logs
|
resources :epp_logs
|
||||||
resources :repp_logs
|
resources :repp_logs
|
||||||
|
resources :bounced_mail_addresses, only: %i[index show destroy]
|
||||||
|
|
||||||
authenticate :admin_user do
|
authenticate :admin_user do
|
||||||
mount Que::Web, at: 'que'
|
mount Que::Web, at: 'que'
|
||||||
|
|
15
db/migrate/20200916125326_create_bounced_mail_addresses.rb
Normal file
15
db/migrate/20200916125326_create_bounced_mail_addresses.rb
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
class CreateBouncedMailAddresses < ActiveRecord::Migration[6.0]
|
||||||
|
def change
|
||||||
|
create_table :bounced_mail_addresses do |t|
|
||||||
|
t.string :email, null: false
|
||||||
|
t.string :message_id, null: false
|
||||||
|
t.string :bounce_type, null: false
|
||||||
|
t.string :bounce_subtype, null: false
|
||||||
|
t.string :action, null: false
|
||||||
|
t.string :status, null: false
|
||||||
|
t.string :diagnostic, null: true
|
||||||
|
|
||||||
|
t.timestamps
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -475,6 +475,43 @@ CREATE SEQUENCE public.blocked_domains_id_seq
|
||||||
ALTER SEQUENCE public.blocked_domains_id_seq OWNED BY public.blocked_domains.id;
|
ALTER SEQUENCE public.blocked_domains_id_seq OWNED BY public.blocked_domains.id;
|
||||||
|
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Name: bounced_mail_addresses; Type: TABLE; Schema: public; Owner: -; Tablespace:
|
||||||
|
--
|
||||||
|
|
||||||
|
CREATE TABLE public.bounced_mail_addresses (
|
||||||
|
id bigint NOT NULL,
|
||||||
|
email character varying NOT NULL,
|
||||||
|
message_id character varying NOT NULL,
|
||||||
|
bounce_type character varying NOT NULL,
|
||||||
|
bounce_subtype character varying NOT NULL,
|
||||||
|
action character varying NOT NULL,
|
||||||
|
status character varying NOT NULL,
|
||||||
|
diagnostic character varying,
|
||||||
|
created_at timestamp(6) without time zone NOT NULL,
|
||||||
|
updated_at timestamp(6) without time zone NOT NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Name: bounced_mail_addresses_id_seq; Type: SEQUENCE; Schema: public; Owner: -
|
||||||
|
--
|
||||||
|
|
||||||
|
CREATE SEQUENCE public.bounced_mail_addresses_id_seq
|
||||||
|
START WITH 1
|
||||||
|
INCREMENT BY 1
|
||||||
|
NO MINVALUE
|
||||||
|
NO MAXVALUE
|
||||||
|
CACHE 1;
|
||||||
|
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Name: bounced_mail_addresses_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: -
|
||||||
|
--
|
||||||
|
|
||||||
|
ALTER SEQUENCE public.bounced_mail_addresses_id_seq OWNED BY public.bounced_mail_addresses.id;
|
||||||
|
|
||||||
|
|
||||||
--
|
--
|
||||||
-- Name: certificates; Type: TABLE; Schema: public; Owner: -; Tablespace:
|
-- Name: certificates; Type: TABLE; Schema: public; Owner: -; Tablespace:
|
||||||
--
|
--
|
||||||
|
@ -2658,6 +2695,13 @@ ALTER TABLE ONLY public.bank_transactions ALTER COLUMN id SET DEFAULT nextval('p
|
||||||
ALTER TABLE ONLY public.blocked_domains ALTER COLUMN id SET DEFAULT nextval('public.blocked_domains_id_seq'::regclass);
|
ALTER TABLE ONLY public.blocked_domains ALTER COLUMN id SET DEFAULT nextval('public.blocked_domains_id_seq'::regclass);
|
||||||
|
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Name: id; Type: DEFAULT; Schema: public; Owner: -
|
||||||
|
--
|
||||||
|
|
||||||
|
ALTER TABLE ONLY public.bounced_mail_addresses ALTER COLUMN id SET DEFAULT nextval('public.bounced_mail_addresses_id_seq'::regclass);
|
||||||
|
|
||||||
|
|
||||||
--
|
--
|
||||||
-- Name: id; Type: DEFAULT; Schema: public; Owner: -
|
-- Name: id; Type: DEFAULT; Schema: public; Owner: -
|
||||||
--
|
--
|
||||||
|
@ -2876,14 +2920,14 @@ ALTER TABLE ONLY public.log_payment_orders ALTER COLUMN id SET DEFAULT nextval('
|
||||||
|
|
||||||
|
|
||||||
--
|
--
|
||||||
-- Name: log_prices id; Type: DEFAULT; Schema: public; Owner: -
|
-- Name: id; Type: DEFAULT; Schema: public; Owner: -
|
||||||
--
|
--
|
||||||
|
|
||||||
ALTER TABLE ONLY public.log_prices ALTER COLUMN id SET DEFAULT nextval('public.log_prices_id_seq'::regclass);
|
ALTER TABLE ONLY public.log_prices ALTER COLUMN id SET DEFAULT nextval('public.log_prices_id_seq'::regclass);
|
||||||
|
|
||||||
|
|
||||||
--
|
--
|
||||||
-- Name: log_registrant_verifications id; Type: DEFAULT; Schema: public; Owner: -
|
-- Name: id; Type: DEFAULT; Schema: public; Owner: -
|
||||||
--
|
--
|
||||||
|
|
||||||
ALTER TABLE ONLY public.log_registrant_verifications ALTER COLUMN id SET DEFAULT nextval('public.log_registrant_verifications_id_seq'::regclass);
|
ALTER TABLE ONLY public.log_registrant_verifications ALTER COLUMN id SET DEFAULT nextval('public.log_registrant_verifications_id_seq'::regclass);
|
||||||
|
@ -3100,6 +3144,14 @@ ALTER TABLE ONLY public.blocked_domains
|
||||||
ADD CONSTRAINT blocked_domains_pkey PRIMARY KEY (id);
|
ADD CONSTRAINT blocked_domains_pkey PRIMARY KEY (id);
|
||||||
|
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Name: bounced_mail_addresses_pkey; Type: CONSTRAINT; Schema: public; Owner: -; Tablespace:
|
||||||
|
--
|
||||||
|
|
||||||
|
ALTER TABLE ONLY public.bounced_mail_addresses
|
||||||
|
ADD CONSTRAINT bounced_mail_addresses_pkey PRIMARY KEY (id);
|
||||||
|
|
||||||
|
|
||||||
--
|
--
|
||||||
-- Name: certificates_pkey; Type: CONSTRAINT; Schema: public; Owner: -; Tablespace:
|
-- Name: certificates_pkey; Type: CONSTRAINT; Schema: public; Owner: -; Tablespace:
|
||||||
--
|
--
|
||||||
|
@ -3349,7 +3401,7 @@ ALTER TABLE ONLY public.log_payment_orders
|
||||||
|
|
||||||
|
|
||||||
--
|
--
|
||||||
-- Name: log_prices log_prices_pkey; Type: CONSTRAINT; Schema: public; Owner: -
|
-- Name: log_prices_pkey; Type: CONSTRAINT; Schema: public; Owner: -
|
||||||
--
|
--
|
||||||
|
|
||||||
ALTER TABLE ONLY public.log_prices
|
ALTER TABLE ONLY public.log_prices
|
||||||
|
@ -3357,7 +3409,7 @@ ALTER TABLE ONLY public.log_prices
|
||||||
|
|
||||||
|
|
||||||
--
|
--
|
||||||
-- Name: log_registrant_verifications log_registrant_verifications_pkey; Type: CONSTRAINT; Schema: public; Owner: -
|
-- Name: log_registrant_verifications_pkey; Type: CONSTRAINT; Schema: public; Owner: -
|
||||||
--
|
--
|
||||||
|
|
||||||
ALTER TABLE ONLY public.log_registrant_verifications
|
ALTER TABLE ONLY public.log_registrant_verifications
|
||||||
|
@ -4906,5 +4958,7 @@ INSERT INTO "schema_migrations" (version) VALUES
|
||||||
('20200902131603'),
|
('20200902131603'),
|
||||||
('20200908131554'),
|
('20200908131554'),
|
||||||
('20200910085157'),
|
('20200910085157'),
|
||||||
('20200910102028');
|
('20200910102028'),
|
||||||
|
('20200916125326');
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,14 +1,15 @@
|
||||||
module Serializers
|
module Serializers
|
||||||
module RegistrantApi
|
module RegistrantApi
|
||||||
class Contact
|
class Contact
|
||||||
attr_reader :contact
|
attr_reader :contact, :links
|
||||||
|
|
||||||
def initialize(contact)
|
def initialize(contact, links)
|
||||||
@contact = contact
|
@contact = contact
|
||||||
|
@links = links
|
||||||
end
|
end
|
||||||
|
|
||||||
def to_json
|
def to_json(_obj = nil)
|
||||||
{
|
obj = {
|
||||||
id: contact.uuid,
|
id: contact.uuid,
|
||||||
name: contact.name,
|
name: contact.name,
|
||||||
code: contact.code,
|
code: contact.code,
|
||||||
|
@ -31,6 +32,10 @@ module Serializers
|
||||||
statuses: contact.statuses,
|
statuses: contact.statuses,
|
||||||
disclosed_attributes: contact.disclosed_attributes,
|
disclosed_attributes: contact.disclosed_attributes,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
obj[:links] = contact.related_domains if @links
|
||||||
|
|
||||||
|
obj
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -3,11 +3,14 @@ module Serializers
|
||||||
class Domain
|
class Domain
|
||||||
attr_reader :domain
|
attr_reader :domain
|
||||||
|
|
||||||
def initialize(domain)
|
def initialize(domain, simplify: false)
|
||||||
@domain = domain
|
@domain = domain
|
||||||
|
@simplify = simplify
|
||||||
end
|
end
|
||||||
|
|
||||||
def to_json
|
def to_json(_obj = nil)
|
||||||
|
return simple_object if @simplify
|
||||||
|
|
||||||
{
|
{
|
||||||
id: domain.uuid,
|
id: domain.uuid,
|
||||||
name: domain.name,
|
name: domain.name,
|
||||||
|
@ -49,6 +52,17 @@ module Serializers
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
|
def simple_object
|
||||||
|
{
|
||||||
|
id: domain.uuid, name: domain.name, registered_at: domain.registered_at,
|
||||||
|
valid_to: domain.valid_to, outzone_at: domain.outzone_at, statuses: domain.statuses,
|
||||||
|
registrant_verification_asked_at: domain.registrant_verification_asked_at,
|
||||||
|
registrar: { name: domain.registrar.name, website: domain.registrar.website },
|
||||||
|
registrant: { name: domain.registrant.name, id: domain.registrant.uuid,
|
||||||
|
phone: domain.registrant.phone, email: domain.registrant.email }
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
def dnssec_keys
|
def dnssec_keys
|
||||||
domain.dnskeys.map do |key|
|
domain.dnskeys.map do |key|
|
||||||
"#{key.flags} #{key.protocol} #{key.alg} #{key.public_key}"
|
"#{key.flags} #{key.protocol} #{key.alg} #{key.public_key}"
|
||||||
|
|
10
test/fixtures/bounced_mail_addresses.yml
vendored
Normal file
10
test/fixtures/bounced_mail_addresses.yml
vendored
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
one:
|
||||||
|
email: bounced@registry.test
|
||||||
|
message_id: 010f0174a0c7d348-ea6e2fc1-0854-4073-b71f-5cecf9b0d0b2-000000
|
||||||
|
bounce_type: Permanent
|
||||||
|
bounce_subtype: General
|
||||||
|
action: failed
|
||||||
|
status: '5.1.1'
|
||||||
|
diagnostic: 'smtp; 550 5.1.1 user unknown'
|
||||||
|
created_at: <%= Time.zone.parse('2010-07-05').to_s(:db) %>
|
||||||
|
updated_at: <%= Time.zone.parse('2010-07-05').to_s(:db) %>
|
|
@ -50,10 +50,10 @@ class RegistrantApiDomainsTest < ApplicationIntegrationTest
|
||||||
assert_equal(200, response.status)
|
assert_equal(200, response.status)
|
||||||
|
|
||||||
response_json = JSON.parse(response.body, symbolize_names: true)
|
response_json = JSON.parse(response.body, symbolize_names: true)
|
||||||
array_of_domain_names = response_json.map { |x| x[:name] }
|
array_of_domain_names = response_json[:domains].map { |x| x[:name] }
|
||||||
assert(array_of_domain_names.include?('hospital.test'))
|
assert(array_of_domain_names.include?('hospital.test'))
|
||||||
|
|
||||||
array_of_domain_registrars = response_json.map { |x| x[:registrar] }
|
array_of_domain_registrars = response_json[:domains].map { |x| x[:registrar] }
|
||||||
assert(array_of_domain_registrars.include?({name: 'Good Names', website: nil}))
|
assert(array_of_domain_registrars.include?({name: 'Good Names', website: nil}))
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -63,12 +63,12 @@ class RegistrantApiDomainsTest < ApplicationIntegrationTest
|
||||||
response_json = JSON.parse(response.body, symbolize_names: true)
|
response_json = JSON.parse(response.body, symbolize_names: true)
|
||||||
|
|
||||||
assert_equal(200, response.status)
|
assert_equal(200, response.status)
|
||||||
assert_equal(2, response_json.count)
|
assert_equal(2, response_json[:domains].count)
|
||||||
|
|
||||||
get '/api/v1/registrant/domains', headers: @auth_headers
|
get '/api/v1/registrant/domains', headers: @auth_headers
|
||||||
response_json = JSON.parse(response.body, symbolize_names: true)
|
response_json = JSON.parse(response.body, symbolize_names: true)
|
||||||
|
|
||||||
assert_equal(4, response_json.count)
|
assert_equal(4, response_json[:domains].count)
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_root_does_not_accept_limit_higher_than_200
|
def test_root_does_not_accept_limit_higher_than_200
|
||||||
|
|
75
test/integration/api/v1/bounces/create_test.rb
Normal file
75
test/integration/api/v1/bounces/create_test.rb
Normal file
|
@ -0,0 +1,75 @@
|
||||||
|
require 'test_helper'
|
||||||
|
|
||||||
|
class BouncesApiV1CreateTest < ActionDispatch::IntegrationTest
|
||||||
|
def setup
|
||||||
|
@api_key = "Basic #{ENV['api_shared_key']}"
|
||||||
|
@headers = { "Authorization": "#{@api_key}" }
|
||||||
|
@json_body = { "data": valid_bounce_request }.as_json
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_authorizes_api_request
|
||||||
|
post api_v1_bounces_path, params: @json_body, headers: @headers
|
||||||
|
assert_response :created
|
||||||
|
|
||||||
|
invalid_headers = { "Authorization": "Basic invalid_api_key" }
|
||||||
|
post api_v1_bounces_path, params: @json_body, headers: invalid_headers
|
||||||
|
assert_response :unauthorized
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_returns_bad_request_if_invalid_payload
|
||||||
|
invalid_json_body = @json_body.dup
|
||||||
|
invalid_json_body['data']['bounce']['bouncedRecipients'] = nil
|
||||||
|
|
||||||
|
post api_v1_bounces_path, params: invalid_json_body, headers: @headers
|
||||||
|
assert_response :bad_request
|
||||||
|
|
||||||
|
invalid_json_body = 'aaaa'
|
||||||
|
post api_v1_bounces_path, params: invalid_json_body, headers: @headers
|
||||||
|
assert_response :bad_request
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_saves_new_bounce_object
|
||||||
|
request_body = @json_body.dup
|
||||||
|
random_mail = "#{rand(10000..99999)}@registry.test"
|
||||||
|
request_body['data']['bounce']['bouncedRecipients'][0]['emailAddress'] = random_mail
|
||||||
|
|
||||||
|
post api_v1_bounces_path, params: request_body, headers: @headers
|
||||||
|
assert_response :created
|
||||||
|
|
||||||
|
bounced_mail = BouncedMailAddress.last
|
||||||
|
assert bounced_mail.email = random_mail
|
||||||
|
assert '5.1.1', bounced_mail.status
|
||||||
|
assert 'failed', bounced_mail.action
|
||||||
|
end
|
||||||
|
|
||||||
|
def valid_bounce_request
|
||||||
|
{
|
||||||
|
"notificationType": "Bounce",
|
||||||
|
"mail": {
|
||||||
|
"source": "noreply@registry.test",
|
||||||
|
"sourceIp": "195.43.86.5",
|
||||||
|
"messageId": "010f0174a0c7d348-ea6e2fc1-0854-4073-b71f-5cecf9b0d0b2-000000",
|
||||||
|
"sourceArn": "arn:aws:ses:us-east-2:65026820000:identity/noreply@registry.test",
|
||||||
|
"timestamp": "2020-09-18T10:34:44.000Z",
|
||||||
|
"destination": [ "bounced@registry.test" ],
|
||||||
|
"sendingAccountId": "650268220000"
|
||||||
|
},
|
||||||
|
"bounce": {
|
||||||
|
"timestamp": "2020-09-18T10:34:44.911Z",
|
||||||
|
"bounceType": "Permanent",
|
||||||
|
"feedbackId": "010f0174a0c7d4f9-27d59756-6111-4d5f-xxxx-26bee0d55fa2-000000",
|
||||||
|
"remoteMtaIp": "127.0.01",
|
||||||
|
"reportingMTA": "dsn; xxx.amazonses.com",
|
||||||
|
"bounceSubType": "General",
|
||||||
|
"bouncedRecipients": [
|
||||||
|
{
|
||||||
|
"action": "failed",
|
||||||
|
"status": "5.1.1",
|
||||||
|
"emailAddress": "bounced@registry.test",
|
||||||
|
"diagnosticCode": "smtp; 550 5.1.1 user unknown"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}.as_json
|
||||||
|
end
|
||||||
|
end
|
|
@ -18,7 +18,7 @@ class DomainDeleteConfirmJobTest < ActiveSupport::TestCase
|
||||||
new_registrant_email: @new_registrant.email,
|
new_registrant_email: @new_registrant.email,
|
||||||
current_user_id: @user.id })
|
current_user_id: @user.id })
|
||||||
|
|
||||||
DomainDeleteConfirmJob.enqueue(@domain.id, RegistrantVerification::REJECTED)
|
DomainDeleteConfirmJob.perform_now(@domain.id, RegistrantVerification::REJECTED)
|
||||||
|
|
||||||
last_registrar_notification = @domain.registrar.notifications.last
|
last_registrar_notification = @domain.registrar.notifications.last
|
||||||
assert_equal(last_registrar_notification.attached_obj_id, @domain.id)
|
assert_equal(last_registrar_notification.attached_obj_id, @domain.id)
|
||||||
|
@ -31,7 +31,7 @@ class DomainDeleteConfirmJobTest < ActiveSupport::TestCase
|
||||||
new_registrant_email: @new_registrant.email,
|
new_registrant_email: @new_registrant.email,
|
||||||
current_user_id: @user.id })
|
current_user_id: @user.id })
|
||||||
|
|
||||||
DomainDeleteConfirmJob.enqueue(@domain.id, RegistrantVerification::CONFIRMED)
|
DomainDeleteConfirmJob.perform_now(@domain.id, RegistrantVerification::CONFIRMED)
|
||||||
|
|
||||||
last_registrar_notification = @domain.registrar.notifications.last
|
last_registrar_notification = @domain.registrar.notifications.last
|
||||||
assert_equal(last_registrar_notification.attached_obj_id, @domain.id)
|
assert_equal(last_registrar_notification.attached_obj_id, @domain.id)
|
||||||
|
@ -51,7 +51,7 @@ class DomainDeleteConfirmJobTest < ActiveSupport::TestCase
|
||||||
assert @domain.registrant_delete_confirmable?(@domain.registrant_verification_token)
|
assert @domain.registrant_delete_confirmable?(@domain.registrant_verification_token)
|
||||||
assert_equal @user.id, @domain.pending_json['current_user_id']
|
assert_equal @user.id, @domain.pending_json['current_user_id']
|
||||||
|
|
||||||
DomainDeleteConfirmJob.enqueue(@domain.id, RegistrantVerification::CONFIRMED)
|
DomainDeleteConfirmJob.perform_now(@domain.id, RegistrantVerification::CONFIRMED)
|
||||||
@domain.reload
|
@domain.reload
|
||||||
|
|
||||||
assert @domain.statuses.include? DomainStatus::PENDING_DELETE
|
assert @domain.statuses.include? DomainStatus::PENDING_DELETE
|
||||||
|
@ -72,7 +72,7 @@ class DomainDeleteConfirmJobTest < ActiveSupport::TestCase
|
||||||
assert @domain.registrant_delete_confirmable?(@domain.registrant_verification_token)
|
assert @domain.registrant_delete_confirmable?(@domain.registrant_verification_token)
|
||||||
assert_equal @user.id, @domain.pending_json['current_user_id']
|
assert_equal @user.id, @domain.pending_json['current_user_id']
|
||||||
|
|
||||||
DomainDeleteConfirmJob.enqueue(@domain.id, RegistrantVerification::REJECTED)
|
DomainDeleteConfirmJob.perform_now(@domain.id, RegistrantVerification::REJECTED)
|
||||||
@domain.reload
|
@domain.reload
|
||||||
|
|
||||||
assert_equal ['ok'], @domain.statuses
|
assert_equal ['ok'], @domain.statuses
|
||||||
|
|
|
@ -4,7 +4,7 @@ require 'serializers/registrant_api/contact'
|
||||||
class SerializersRegistrantApiContactTest < ActiveSupport::TestCase
|
class SerializersRegistrantApiContactTest < ActiveSupport::TestCase
|
||||||
def setup
|
def setup
|
||||||
@contact = contacts(:william)
|
@contact = contacts(:william)
|
||||||
@serializer = Serializers::RegistrantApi::Contact.new(@contact)
|
@serializer = Serializers::RegistrantApi::Contact.new(@contact, false)
|
||||||
@json = @serializer.to_json
|
@json = @serializer.to_json
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
104
test/models/bounced_mail_address_test.rb
Normal file
104
test/models/bounced_mail_address_test.rb
Normal file
|
@ -0,0 +1,104 @@
|
||||||
|
require 'test_helper'
|
||||||
|
|
||||||
|
class BouncedMailAddressTest < ActiveSupport::TestCase
|
||||||
|
include ActionMailer::TestHelper
|
||||||
|
|
||||||
|
def setup
|
||||||
|
@bounced_mail = BouncedMailAddress.new
|
||||||
|
@bounced_mail.email = 'recipient@registry.test'
|
||||||
|
@bounced_mail.message_id = '010f0174a0c7d348-ea6e2fc1-0854-4073-b71f-5cecf9b0d0b2-000000'
|
||||||
|
@bounced_mail.bounce_type = 'Permanent'
|
||||||
|
@bounced_mail.bounce_subtype = 'General'
|
||||||
|
@bounced_mail.action = 'failed'
|
||||||
|
@bounced_mail.status = '5.1.1'
|
||||||
|
@bounced_mail.diagnostic = 'smtp; 550 5.1.1 user unknown'
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_email_is_required
|
||||||
|
assert @bounced_mail.valid?
|
||||||
|
@bounced_mail.email = nil
|
||||||
|
assert @bounced_mail.invalid?
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_message_id_is_required
|
||||||
|
assert @bounced_mail.valid?
|
||||||
|
@bounced_mail.message_id = nil
|
||||||
|
assert @bounced_mail.invalid?
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_bounce_type_is_required
|
||||||
|
assert @bounced_mail.valid?
|
||||||
|
@bounced_mail.bounce_type = nil
|
||||||
|
assert @bounced_mail.invalid?
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_bounce_subtype_is_required
|
||||||
|
assert @bounced_mail.valid?
|
||||||
|
@bounced_mail.bounce_subtype = nil
|
||||||
|
assert @bounced_mail.invalid?
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_action_is_required
|
||||||
|
assert @bounced_mail.valid?
|
||||||
|
@bounced_mail.action = nil
|
||||||
|
assert @bounced_mail.invalid?
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_status_is_required
|
||||||
|
assert @bounced_mail.valid?
|
||||||
|
@bounced_mail.status = nil
|
||||||
|
assert @bounced_mail.invalid?
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_diagnostic_is_not_required
|
||||||
|
assert @bounced_mail.valid?
|
||||||
|
@bounced_mail.diagnostic = nil
|
||||||
|
assert @bounced_mail.valid?
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_bounce_reason_is_determined_dynamically
|
||||||
|
assert @bounced_mail.valid?
|
||||||
|
assert_equal 'failed (5.1.1 smtp; 550 5.1.1 user unknown)', @bounced_mail.bounce_reason
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_creates_objects_from_sns_json
|
||||||
|
BouncedMailAddress.record(sns_bounce_payload)
|
||||||
|
|
||||||
|
bounced_mail = BouncedMailAddress.last
|
||||||
|
assert_equal domains(:shop).registrant.email, bounced_mail.email
|
||||||
|
assert_equal 'failed', bounced_mail.action
|
||||||
|
assert_equal '5.1.1', bounced_mail.status
|
||||||
|
assert_equal 'smtp; 550 5.1.1 user unknown', bounced_mail.diagnostic
|
||||||
|
end
|
||||||
|
|
||||||
|
def sns_bounce_payload
|
||||||
|
{
|
||||||
|
"notificationType": "Bounce",
|
||||||
|
"mail": {
|
||||||
|
"source": "noreply@registry.test",
|
||||||
|
"sourceIp": "195.43.86.5",
|
||||||
|
"messageId": "010f0174a0c7d348-ea6e2fc1-0854-4073-b71f-5cecf9b0d0b2-000000",
|
||||||
|
"sourceArn": "arn:aws:ses:us-east-2:65026820000:identity/noreply@registry.test",
|
||||||
|
"timestamp": "2020-09-18T10:34:44.000Z",
|
||||||
|
"destination": [ "#{domains(:shop).registrant.email}" ],
|
||||||
|
"sendingAccountId": "650268220000"
|
||||||
|
},
|
||||||
|
"bounce": {
|
||||||
|
"timestamp": "2020-09-18T10:34:44.911Z",
|
||||||
|
"bounceType": "Permanent",
|
||||||
|
"feedbackId": "010f0174a0c7d4f9-27d59756-6111-4d5f-xxxx-26bee0d55fa2-000000",
|
||||||
|
"remoteMtaIp": "127.0.01",
|
||||||
|
"reportingMTA": "dsn; xxx.amazonses.com",
|
||||||
|
"bounceSubType": "General",
|
||||||
|
"bouncedRecipients": [
|
||||||
|
{
|
||||||
|
"action": "failed",
|
||||||
|
"status": "5.1.1",
|
||||||
|
"emailAddress": "#{domains(:shop).registrant.email}",
|
||||||
|
"diagnosticCode": "smtp; 550 5.1.1 user unknown"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}.as_json
|
||||||
|
end
|
||||||
|
end
|
40
test/system/admin_area/bounced_mail_addresses_test.rb
Normal file
40
test/system/admin_area/bounced_mail_addresses_test.rb
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
require 'application_system_test_case'
|
||||||
|
|
||||||
|
class AdminBouncedMailAddressesTest < ApplicationSystemTestCase
|
||||||
|
include ActionView::Helpers::NumberHelper
|
||||||
|
|
||||||
|
def setup
|
||||||
|
@bounced_mail = bounced_mail_addresses(:one)
|
||||||
|
@original_default_language = Setting.default_language
|
||||||
|
sign_in users(:admin)
|
||||||
|
end
|
||||||
|
|
||||||
|
def teardown
|
||||||
|
Setting.default_language = @original_default_language
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_shows_bounced_emails
|
||||||
|
visit admin_bounced_mail_addresses_path
|
||||||
|
assert_text @bounced_mail.status
|
||||||
|
assert_text @bounced_mail.action
|
||||||
|
assert_text @bounced_mail.diagnostic
|
||||||
|
assert_text @bounced_mail.email
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_shows_detailed_bounced_email
|
||||||
|
visit admin_bounced_mail_address_path(@bounced_mail)
|
||||||
|
assert_text @bounced_mail.status
|
||||||
|
assert_text @bounced_mail.action
|
||||||
|
assert_text @bounced_mail.diagnostic
|
||||||
|
assert_text @bounced_mail.email
|
||||||
|
|
||||||
|
assert_text @bounced_mail.message_id
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_deletes_registrar
|
||||||
|
visit admin_bounced_mail_address_path(@bounced_mail)
|
||||||
|
click_on 'Destroy'
|
||||||
|
|
||||||
|
assert_text 'Bounced mail address was successfully destroyed.'
|
||||||
|
end
|
||||||
|
end
|
|
@ -1,6 +1,7 @@
|
||||||
require 'application_system_test_case'
|
require 'application_system_test_case'
|
||||||
|
|
||||||
class DomainDeleteConfirmsTest < ApplicationSystemTestCase
|
class DomainDeleteConfirmsTest < ApplicationSystemTestCase
|
||||||
|
include ActionMailer::TestHelper
|
||||||
setup do
|
setup do
|
||||||
@user = users(:registrant)
|
@user = users(:registrant)
|
||||||
sign_in @user
|
sign_in @user
|
||||||
|
@ -13,7 +14,9 @@ class DomainDeleteConfirmsTest < ApplicationSystemTestCase
|
||||||
def test_enqueues_approve_job_after_verification
|
def test_enqueues_approve_job_after_verification
|
||||||
visit registrant_domain_delete_confirm_url(@domain.id, token: @domain.registrant_verification_token)
|
visit registrant_domain_delete_confirm_url(@domain.id, token: @domain.registrant_verification_token)
|
||||||
|
|
||||||
click_on 'Confirm domain delete'
|
perform_enqueued_jobs do
|
||||||
|
click_on 'Confirm domain delete'
|
||||||
|
end
|
||||||
assert_text 'Domain registrant change has successfully received.'
|
assert_text 'Domain registrant change has successfully received.'
|
||||||
|
|
||||||
@domain.reload
|
@domain.reload
|
||||||
|
@ -23,7 +26,9 @@ class DomainDeleteConfirmsTest < ApplicationSystemTestCase
|
||||||
def test_enqueues_reject_job_after_verification
|
def test_enqueues_reject_job_after_verification
|
||||||
visit registrant_domain_delete_confirm_url(@domain.id, token: @domain.registrant_verification_token)
|
visit registrant_domain_delete_confirm_url(@domain.id, token: @domain.registrant_verification_token)
|
||||||
|
|
||||||
click_on 'Reject domain delete'
|
perform_enqueued_jobs do
|
||||||
|
click_on 'Reject domain delete'
|
||||||
|
end
|
||||||
assert_text 'Domain registrant change has been rejected successfully.'
|
assert_text 'Domain registrant change has been rejected successfully.'
|
||||||
|
|
||||||
@domain.reload
|
@domain.reload
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue