diff --git a/CHANGELOG.md b/CHANGELOG.md index 90e2523dc..9774d5cb3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,54 @@ +01.12.2020 +* Refactored clientHold for interactors [#1751](https://github.com/internetee/registry/issues/1751) +* Fixed internal error on removing clientHold status when not present [#1766](https://github.com/internetee/registry/issues/1766) + +30.11.2020 +* Refactor - interactors moved to domain space [#1762](https://github.com/internetee/registry/pull/1762) + +27.11.2020 +* Refactored delete confirmation for interactors [#1753](https://github.com/internetee/registry/issues/1753) + +24.11.2020 +* Added subnet support for list of allowed IPs [#983](https://github.com/internetee/registry/issues/983) +* Added contact endpoint to Restful EPP API [#1580](https://github.com/internetee/registry/issues/1580) + +20.11.2020 +* Registrant confirmation over Registrant API [#1742](https://github.com/internetee/registry/pull/1742) +* Refactored forceDelete cancellation for interactors [#1743](https://github.com/internetee/registry/issues/1743) + +19.11.2020 +* Only sponsoring registrar has access to private contact's details [#1745](https://github.com/internetee/registry/issues/1745) +* Refactor ForceDelete [#1740](https://github.com/internetee/registry/issues/1740) + +13.11.2020 +* Fixed per registrar epp session limit [#729](https://github.com/internetee/registry/issues/729) +* Correct error code is returned on reaching session limit [#587](https://github.com/internetee/registry/issues/587) +* No logins within active session [#1313](https://github.com/internetee/registry/issues/1313) + +06.11.2020 +* Csv option to limit list of domains for bulk nameserver change in registrar portal [#1737](https://github.com/internetee/registry/issues/1737) +* New forceDelete email template for invalid contact data [#1178](https://github.com/internetee/registry/issues/1178) + +05.11.2020 +* Registrant API contact name update feature [#1724](https://github.com/internetee/registry/issues/1724) +* New email template for expired domains in forceDelete [#1725](https://github.com/internetee/registry/issues/1725) +* Cancelling forceDelete (FD) restores the state of the domain prior application of FD [#1136](https://github.com/internetee/registry/issues/1136) + +04.11.2020 +* Email notification templates for forceDelete are now automatically selected according to registrant type [#442](https://github.com/internetee/registry/issues/442) + +03.11.2020 +* Fixed registrant confirmation while forcedelete is set on a domain [#1729](https://github.com/internetee/registry/issues/1729) +* Fixed search in registrar domain view [#262](https://github.com/internetee/registry/issues/262) +* Fixed double status issue on setting forceDelete [#1135](https://github.com/internetee/registry/issues/1135) + +28.10.2020 +* Domain renew now canceles pending delete process [#1664](https://github.com/internetee/registry/issues/1664) +* Added multi-language support to whois disclaimer [#1703](https://github.com/internetee/registry/issues/1703) + +27.10.2020 +* Fixed 1 day delay in force delete for multi year registrations [#1720](https://github.com/internetee/registry/issues/1720) + 20.10.2020 * ForceDelete mailer now respects option to not notify registrant [#1719](https://github.com/internetee/registry/pull/1719) diff --git a/Gemfile b/Gemfile index 25c3eafff..7ae8d4ef6 100644 --- a/Gemfile +++ b/Gemfile @@ -1,6 +1,7 @@ source 'https://rubygems.org' # core +gem 'active_interaction', '~> 3.8' gem 'bootsnap', '>= 1.1.0', require: false gem 'iso8601', '0.12.1' # for dates and times gem 'rails', '~> 6.0' @@ -35,8 +36,6 @@ gem 'select2-rails', '3.5.9.3' # for autocomplete gem 'cancancan' gem 'devise', '~> 4.7' -gem 'grape' - # registry specfic gem 'data_migrate', '~> 6.1' gem 'isikukood' # for EE-id validation diff --git a/Gemfile.lock b/Gemfile.lock index c628257a2..72c716aae 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -112,6 +112,8 @@ GEM erubi (~> 1.4) rails-dom-testing (~> 2.0) rails-html-sanitizer (~> 1.1, >= 1.2.0) + active_interaction (3.8.3) + activemodel (>= 4, < 7) activejob (6.0.3.3) activesupport (= 6.0.3.3) globalid (>= 0.3.6) @@ -196,28 +198,6 @@ GEM docile (1.3.2) domain_name (0.5.20190701) unf (>= 0.0.5, < 1.0.0) - dry-configurable (0.11.6) - concurrent-ruby (~> 1.0) - dry-core (~> 0.4, >= 0.4.7) - dry-equalizer (~> 0.2) - dry-container (0.7.2) - concurrent-ruby (~> 1.0) - dry-configurable (~> 0.1, >= 0.1.3) - dry-core (0.4.9) - concurrent-ruby (~> 1.0) - dry-equalizer (0.3.0) - dry-inflector (0.2.0) - dry-logic (1.0.7) - concurrent-ruby (~> 1.0) - dry-core (~> 0.2) - dry-equalizer (~> 0.2) - dry-types (1.4.0) - concurrent-ruby (~> 1.0) - dry-container (~> 0.3) - dry-core (~> 0.4, >= 0.4.4) - dry-equalizer (~> 0.3) - dry-inflector (~> 0.1, >= 0.1.2) - dry-logic (~> 1.0, >= 1.0.2) erubi (1.9.0) erubis (2.7.0) execjs (2.7.0) @@ -226,13 +206,6 @@ GEM thor (~> 0.14) globalid (0.4.2) activesupport (>= 4.2.0) - grape (1.4.0) - activesupport - builder - dry-types (>= 1.1) - mustermann-grape (~> 1.0.0) - rack (>= 1.3.0) - rack-accept gyoku (1.3.1) builder (>= 2.1.2) haml (5.1.2) @@ -312,8 +285,6 @@ GEM multi_json (1.15.0) mustermann (1.1.1) ruby2_keywords (~> 0.0.1) - mustermann-grape (1.0.1) - mustermann (>= 1.0.0) netrc (0.11.0) nio4r (2.5.4) nokogiri (1.10.10) @@ -357,8 +328,6 @@ GEM que (~> 0.8) sinatra rack (2.2.3) - rack-accept (0.4.5) - rack (>= 0.4) rack-oauth2 (1.16.0) activesupport attr_required @@ -521,6 +490,7 @@ PLATFORMS ruby DEPENDENCIES + active_interaction (~> 3.8) activerecord-import airbrake bootsnap (>= 1.1.0) @@ -542,7 +512,6 @@ DEPENDENCIES epp! epp-xml (= 1.1.0)! figaro (= 1.1.1) - grape haml (~> 5.0) isikukood iso8601 (= 0.12.1) diff --git a/app/api/repp/account_v1.rb b/app/api/repp/account_v1.rb deleted file mode 100644 index fe3acd387..000000000 --- a/app/api/repp/account_v1.rb +++ /dev/null @@ -1,16 +0,0 @@ -module Repp - class AccountV1 < Grape::API - version 'v1', using: :path - - resource :accounts do - desc 'Return current cash account balance' - - get 'balance' do - @response = { - balance: current_user.registrar.cash_account.balance, - currency: current_user.registrar.cash_account.currency - } - end - end - end -end diff --git a/app/api/repp/api.rb b/app/api/repp/api.rb deleted file mode 100644 index af6864cfa..000000000 --- a/app/api/repp/api.rb +++ /dev/null @@ -1,65 +0,0 @@ -module Repp - class API < Grape::API - format :json - prefix :repp - - http_basic do |username, password| - @current_user ||= ApiUser.find_by(username: username, plain_text_password: password) - if @current_user - true - else - error! I18n.t('api_user_not_found'), 401 - end - end - - before do - webclient_request = ENV['webclient_ips'].split(',').map(&:strip).include?(request.ip) - unless webclient_request - error! I18n.t('api.authorization.ip_not_allowed', ip: request.ip), 401 unless @current_user.registrar.api_ip_white?(request.ip) - end - - if @current_user.cannot?(:view, :repp) - error! I18n.t('no_permission'), 401 unless @current_user.registrar.api_ip_white?(request.ip) - end - - next if Rails.env.test? || Rails.env.development? - message = 'Certificate mismatch! Cert common name should be:' - request_name = env['HTTP_SSL_CLIENT_S_DN_CN'] - - if webclient_request - webclient_cert_name = ENV['webclient_cert_common_name'] || 'webclient' - error! "Webclient #{message} #{webclient_cert_name}", 401 if webclient_cert_name != request_name - else - unless @current_user.pki_ok?(request.env['HTTP_SSL_CLIENT_CERT'], - request.env['HTTP_SSL_CLIENT_S_DN_CN']) - error! "#{message} #{@current_user.username}", 401 - end - end - end - - helpers do - attr_reader :current_user - end - - after do - ApiLog::ReppLog.create({ - request_path: request.path, - request_method: request.request_method, - request_params: request.params.except('route_info').to_json, - response: @response.to_json, - response_code: status, - api_user_name: current_user.try(:username), - api_user_registrar: current_user.try(:registrar).try(:to_s), - ip: request.ip, - uuid: request.try(:uuid) - }) - end - - mount Repp::DomainV1 - mount Repp::ContactV1 - mount Repp::AccountV1 - mount Repp::DomainTransfersV1 - mount Repp::NameserversV1 - mount Repp::DomainContactsV1 - end -end diff --git a/app/api/repp/contact_v1.rb b/app/api/repp/contact_v1.rb deleted file mode 100644 index 810829ef7..000000000 --- a/app/api/repp/contact_v1.rb +++ /dev/null @@ -1,35 +0,0 @@ -module Repp - class ContactV1 < Grape::API - version 'v1', using: :path - - resource :contacts do - desc 'Return list of contact' - params do - optional :limit, type: Integer, values: (1..200).to_a, desc: 'How many contacts to show' - optional :offset, type: Integer, desc: 'Contact number to start at' - optional :details, type: String, values: %w(true false), desc: 'Whether to include details' - end - - get '/' do - limit = params[:limit] || 200 - offset = params[:offset] || 0 - - if params[:details] == 'true' - contacts = current_user.registrar.contacts.limit(limit).offset(offset) - - unless Contact.address_processing? - attributes = Contact.attribute_names - Contact.address_attribute_names - contacts = contacts.select(attributes) - end - else - contacts = current_user.registrar.contacts.limit(limit).offset(offset).pluck(:code) - end - - @response = { - contacts: contacts, - total_number_of_records: current_user.registrar.contacts.count - } - end - end - end -end diff --git a/app/api/repp/domain_contacts_v1.rb b/app/api/repp/domain_contacts_v1.rb deleted file mode 100644 index 7f3e323ac..000000000 --- a/app/api/repp/domain_contacts_v1.rb +++ /dev/null @@ -1,47 +0,0 @@ -module Repp - class DomainContactsV1 < Grape::API - version 'v1', using: :path - - resource :domains do - resource :contacts do - patch '/' do - current_contact = current_user.registrar.contacts - .find_by(code: params[:current_contact_id]) - new_contact = current_user.registrar.contacts.find_by(code: params[:new_contact_id]) - - unless current_contact - error!({ error: { type: 'invalid_request_error', - param: 'current_contact_id', - message: "No such contact: #{params[:current_contact_id]}"} }, - :bad_request) - end - - unless new_contact - error!({ error: { type: 'invalid_request_error', - param: 'new_contact_id', - message: "No such contact: #{params[:new_contact_id]}" } }, - :bad_request) - end - - if new_contact.invalid? - error!({ error: { type: 'invalid_request_error', - param: 'new_contact_id', - message: 'New contact must be valid' } }, - :bad_request) - end - - if current_contact == new_contact - error!({ error: { type: 'invalid_request_error', - message: 'New contact ID must be different from current' \ - ' contact ID' } }, - :bad_request) - end - - affected_domains, skipped_domains = TechDomainContact - .replace(current_contact, new_contact) - @response = { affected_domains: affected_domains, skipped_domains: skipped_domains } - end - end - end - end -end diff --git a/app/api/repp/domain_transfers_v1.rb b/app/api/repp/domain_transfers_v1.rb deleted file mode 100644 index c6a48df6d..000000000 --- a/app/api/repp/domain_transfers_v1.rb +++ /dev/null @@ -1,48 +0,0 @@ -module Repp - class DomainTransfersV1 < Grape::API - version 'v1', using: :path - - resource :domain_transfers do - post '/' do - params do - requires :data, type: Hash do - requires :domainTransfers, type: Array do - requires :domainName, type: String, allow_blank: false - requires :transferCode, type: String, allow_blank: false - end - end - end - - new_registrar = current_user.registrar - domain_transfers = params['data']['domainTransfers'] - successful_domain_transfers = [] - errors = [] - - domain_transfers.each do |domain_transfer| - domain_name = domain_transfer['domainName'] - transfer_code = domain_transfer['transferCode'] - domain = Domain.find_by(name: domain_name) - - if domain - if domain.transfer_code == transfer_code - DomainTransfer.request(domain, new_registrar) - successful_domain_transfers << { type: 'domain_transfer', attributes: { domain_name: domain.name } } - else - errors << { title: "#{domain_name} transfer code is wrong" } - end - else - errors << { title: "#{domain_name} does not exist" } - end - end - - if errors.none? - status 200 - @response = { data: successful_domain_transfers } - else - status 400 - @response = { errors: errors } - end - end - end - end -end diff --git a/app/api/repp/domain_v1.rb b/app/api/repp/domain_v1.rb deleted file mode 100644 index cf45bfc6f..000000000 --- a/app/api/repp/domain_v1.rb +++ /dev/null @@ -1,50 +0,0 @@ -module Repp - class DomainV1 < Grape::API - version 'v1', using: :path - - resource :domains do - desc 'Return list of domains' - params do - optional :limit, type: Integer, values: (1..200).to_a, desc: 'How many domains to show' - optional :offset, type: Integer, desc: 'Domain number to start at' - optional :details, type: String, values: %w(true false), desc: 'Whether to include details' - end - - get '/' do - limit = params[:limit] || 200 - offset = params[:offset] || 0 - - if params[:details] == 'true' - domains = current_user.registrar.domains.limit(limit).offset(offset) - else - domains = current_user.registrar.domains.limit(limit).offset(offset).pluck(:name) - end - - @response = { - domains: domains, - total_number_of_records: current_user.registrar.domains.count - } - end - - # example: curl -u registrar1:password localhost:3000/repp/v1/domains/1/transfer_info -H "Auth-Code: authinfopw1" - get '/:id/transfer_info', requirements: { id: /.*/ } do - ident = params[:id] - domain = ident.match?(/\A[0-9]+\z/) ? Domain.find_by(id: ident) : Domain.find_by_idn(ident) - - error! I18n.t('errors.messages.epp_domain_not_found'), 404 unless domain - error! I18n.t('errors.messages.epp_authorization_error'), 401 unless domain.transfer_code.eql? request.headers['Auth-Code'] - - contact_repp_json = proc{|contact| - contact.as_json.slice("code", "name", "ident", "ident_type", "ident_country_code", "phone", "email", "street", "city", "zip","country_code", "statuses") - } - - @response = { - domain: domain.name, - registrant: contact_repp_json.call(domain.registrant), - admin_contacts: domain.admin_contacts.map{|e| contact_repp_json.call(e)}, - tech_contacts: domain.tech_contacts.map{|e| contact_repp_json.call(e)} - } - end - end - end -end diff --git a/app/api/repp/nameservers_v1.rb b/app/api/repp/nameservers_v1.rb deleted file mode 100644 index 04d7d4f6a..000000000 --- a/app/api/repp/nameservers_v1.rb +++ /dev/null @@ -1,45 +0,0 @@ -module Repp - class NameserversV1 < Grape::API - version 'v1', using: :path - - resource 'registrar/nameservers' do - put '/' do - params do - requires :data, type: Hash, allow_blank: false do - requires :type, type: String, allow_blank: false - requires :id, type: String, allow_blank: false - requires :attributes, type: Hash, allow_blank: false do - requires :hostname, type: String, allow_blank: false - requires :ipv4, type: Array - requires :ipv6, type: Array - end - end - end - - hostname = params[:data][:id] - - unless current_user.registrar.nameservers.exists?(hostname: hostname) - error!({ errors: [{ title: "Hostname #{hostname} does not exist" }] }, 404) - end - - new_attributes = { - hostname: params[:data][:attributes][:hostname], - ipv4: params[:data][:attributes][:ipv4], - ipv6: params[:data][:attributes][:ipv6], - } - - begin - affected_domains = current_user.registrar.replace_nameservers(hostname, new_attributes) - rescue ActiveRecord::RecordInvalid => e - error!({ errors: e.record.errors.full_messages.map { |error| { title: error } } }, 400) - end - - status 200 - @response = { data: { type: 'nameserver', - id: params[:data][:attributes][:hostname], - attributes: params[:data][:attributes] }, - affected_domains: affected_domains } - end - end - end -end diff --git a/app/controllers/admin/domains/force_delete_controller.rb b/app/controllers/admin/domains/force_delete_controller.rb index c61f050d2..4fe85fa3b 100644 --- a/app/controllers/admin/domains/force_delete_controller.rb +++ b/app/controllers/admin/domains/force_delete_controller.rb @@ -4,26 +4,15 @@ module Admin def create authorize! :manage, domain + notice = t('.scheduled') + domain.transaction do - domain.schedule_force_delete(type: force_delete_type) - domain.registrar.notifications.create!(text: t('force_delete_set_on_domain', - domain_name: domain.name, - outzone_date: domain.outzone_date, - purge_date: domain.purge_date)) - - notify_by_email if notify_by_email? + result = domain.schedule_force_delete(type: force_delete_type, + notify_by_email: notify_by_email?) + notice = result.errors.messages[:domain].first unless result.valid? end - redirect_to edit_admin_domain_url(domain), notice: t('.scheduled') - end - - def notify_by_email - if force_delete_type == :fast_track - send_email - domain.update(contact_notification_sent_date: Time.zone.today) - else - domain.update(template_name: params[:template_name]) - end + redirect_to edit_admin_domain_url(domain), notice: notice end def destroy @@ -42,13 +31,6 @@ module Admin ActiveRecord::Type::Boolean.new.cast(params[:notify_by_email]) end - def send_email - DomainDeleteMailer.forced(domain: domain, - registrar: domain.registrar, - registrant: domain.registrant, - template_name: params[:template_name]).deliver_now - end - def force_delete_type soft_delete? ? :soft : :fast_track end diff --git a/app/controllers/admin/domains_controller.rb b/app/controllers/admin/domains_controller.rb index 2b94607c5..4a8e5961e 100644 --- a/app/controllers/admin/domains_controller.rb +++ b/app/controllers/admin/domains_controller.rb @@ -2,7 +2,6 @@ module Admin class DomainsController < BaseController before_action :set_domain, only: %i[show edit update keep] authorize_resource - helper_method :force_delete_templates def index params[:q] ||= {} @@ -105,9 +104,5 @@ module Admin params[:q][:valid_to_lteq] = ca_cache end - - def force_delete_templates - DomainDeleteMailer.force_delete_templates - end end end diff --git a/app/controllers/api/v1/registrant/confirms_controller.rb b/app/controllers/api/v1/registrant/confirms_controller.rb new file mode 100644 index 000000000..057400c8e --- /dev/null +++ b/app/controllers/api/v1/registrant/confirms_controller.rb @@ -0,0 +1,119 @@ +require 'serializers/registrant_api/domain' + +module Api + module V1 + module Registrant + class ConfirmsController < ::Api::V1::Registrant::BaseController + skip_before_action :authenticate, :set_paper_trail_whodunnit + before_action :set_domain, only: %i[index update] + before_action :verify_action, only: %i[index update] + before_action :verify_decision, only: %i[update] + + def index + res = { + domain_name: @domain.name, + current_registrant: serialized_registrant(@domain.registrant), + } + + unless delete_action? + res[:new_registrant] = serialized_registrant(@domain.pending_registrant) + end + + render json: res, status: :ok + end + + def update + verification = RegistrantVerification.new(domain_id: @domain.id, + verification_token: verify_params[:token]) + + unless delete_action? ? delete_action(verification) : change_action(verification) + head :bad_request + return + end + + render json: { domain_name: @domain.name, + current_registrant: serialized_registrant(current_registrant), + status: params[:decision] }, status: :ok + end + + private + + def initiator + "email link, #{I18n.t(:user_not_authenticated)}" + end + + def current_registrant + confirmed? && !delete_action? ? @domain.pending_registrant : @domain.registrant + end + + def confirmed? + verify_params[:decision] == 'confirmed' + end + + def change_action(verification) + if confirmed? + verification.domain_registrant_change_confirm!(initiator) + else + verification.domain_registrant_change_reject!(initiator) + end + end + + def delete_action(verification) + if confirmed? + verification.domain_registrant_delete_confirm!(initiator) + else + verification.domain_registrant_delete_reject!(initiator) + end + end + + def serialized_registrant(registrant) + { + name: registrant.try(:name), + ident: registrant.try(:ident), + country: registrant.try(:ident_country_code), + } + end + + def verify_params + params do |p| + p.require(:name) + p.require(:token) + p.permit(:decision) + end + end + + def delete_action? + return true if params[:template] == 'delete' + + false + end + + def verify_decision + return if %w[confirmed rejected].include?(params[:decision]) + + head :not_found + end + + def set_domain + @domain = Domain.find_by(name: verify_params[:name]) + @domain ||= Domain.find_by(name_puny: verify_params[:name]) + return if @domain + + render json: { error: 'Domain not found' }, status: :not_found + end + + def verify_action + action = if params[:template] == 'change' + @domain.registrant_update_confirmable?(verify_params[:token]) + elsif params[:template] == 'delete' + @domain.registrant_delete_confirmable?(verify_params[:token]) + end + + return if action + + render json: { error: 'Application expired or not found' }, status: :unauthorized + end + end + end + end +end diff --git a/app/controllers/epp/contacts_controller.rb b/app/controllers/epp/contacts_controller.rb index df9755af6..85305213b 100644 --- a/app/controllers/epp/contacts_controller.rb +++ b/app/controllers/epp/contacts_controller.rb @@ -1,10 +1,9 @@ require 'deserializers/xml/contact_update' - +require 'deserializers/xml/contact_create' module Epp class ContactsController < BaseController before_action :find_contact, only: [:info, :update, :delete] before_action :find_password, only: [:info, :update, :delete] - helper_method :address_processing? def info authorize! :info, @contact, @password @@ -21,25 +20,13 @@ module Epp def create authorize! :create, Epp::Contact - frame = params[:parsed_frame] - @contact = Epp::Contact.new(frame, current_user.registrar) - @contact.add_legal_file_to_new(frame) - @contact.generate_code + @contact = Epp::Contact.new(params[:parsed_frame], current_user.registrar) + collected_data = ::Deserializers::Xml::ContactCreate.new(params[:parsed_frame]) + action = Actions::ContactCreate.new(@contact, collected_data.legal_document, + collected_data.ident) - if @contact.save - if !address_processing? && address_given? - @response_code = 1100 - @response_description = t('epp.contacts.completed_without_address') - else - @response_code = 1000 - @response_description = t('epp.contacts.completed') - end - - render_epp_response '/epp/contacts/save' - else - handle_errors(@contact) - end + action_call_response(action: action) end def update @@ -52,29 +39,18 @@ module Epp collected_data.ident, current_user) - if action.call - if !address_processing? && address_given? - @response_code = 1100 - @response_description = t('epp.contacts.completed_without_address') - else - @response_code = 1000 - @response_description = t('epp.contacts.completed') - end - - render_epp_response 'epp/contacts/save' - else - handle_errors(@contact) - end + action_call_response(action: action) end def delete authorize! :delete, @contact, @password - - if @contact.destroy_and_clean(params[:parsed_frame]) - render_epp_response '/epp/contacts/delete' - else + action = Actions::ContactDelete.new(@contact, params[:legal_document]) + unless action.call handle_errors(@contact) + return end + + render_epp_response '/epp/contacts/delete' end def renew @@ -91,6 +67,26 @@ module Epp private + def opt_addr? + !Contact.address_processing? && address_given? + end + + def action_call_response(action:) + # rubocop:disable Style/AndOr + (handle_errors(@contact) and return) unless action.call + # rubocop:enable Style/AndOr + + if opt_addr? + @response_code = 1100 + @response_description = t('epp.contacts.completed_without_address') + else + @response_code = 1000 + @response_description = t('epp.contacts.completed') + end + + render_epp_response('epp/contacts/save') + end + def find_password @password = params[:parsed_frame].css('authInfo pw').text end @@ -129,8 +125,7 @@ module Epp 'postalInfo > addr > cc', ] - required_attributes.concat(address_attributes) if address_processing? - + required_attributes.concat(address_attributes) if Contact.address_processing? requires(*required_attributes) ident = params[:parsed_frame].css('ident') @@ -206,9 +201,5 @@ module Epp def address_given? params[:parsed_frame].css('postalInfo addr').size != 0 end - - def address_processing? - Contact.address_processing? - end end end diff --git a/app/controllers/epp/sessions_controller.rb b/app/controllers/epp/sessions_controller.rb index b1c7fbbfb..04603dbe7 100644 --- a/app/controllers/epp/sessions_controller.rb +++ b/app/controllers/epp/sessions_controller.rb @@ -79,19 +79,32 @@ module Epp if success && EppSession.limit_reached?(@api_user.registrar) epp_errors << { - msg: 'Authentication error; server closing connection (connection limit reached)', - code: '2501' + msg: 'Session limit exceeded; server closing connection (connection limit reached)', + code: '2502', } success = false end if success - if params[:parsed_frame].css('newPW').first - unless @api_user.update(plain_text_password: params[:parsed_frame].css('newPW').first.text) - handle_errors(@api_user) and return + new_password = params[:parsed_frame].at_css('newPW')&.text + password_change = new_password.present? + + if password_change + @api_user.plain_text_password = new_password + @api_user.save! + end + + already_authenticated = EppSession.exists?(session_id: epp_session_id) + + if already_authenticated + epp_errors << { + msg: 'Command use error; Already authenticated', + code: 2002, + } + handle_errors + return end - end epp_session = EppSession.new epp_session.session_id = epp_session_id @@ -100,8 +113,8 @@ module Epp render_epp_response('login_success') else handle_errors + end end - end def ip_white? webclient_request = ENV['webclient_ips'].split(',').map(&:strip).include?(request.ip) @@ -125,7 +138,7 @@ module Epp @api_user = current_user # cache current_user for logging epp_session.destroy render_epp_response('logout') - end + end ### HELPER METHODS ### diff --git a/app/controllers/registrar/domain_transfers_controller.rb b/app/controllers/registrar/domain_transfers_controller.rb index acacc3ef4..ca08c73cf 100644 --- a/app/controllers/registrar/domain_transfers_controller.rb +++ b/app/controllers/registrar/domain_transfers_controller.rb @@ -15,12 +15,12 @@ class Registrar csv.each do |row| domain_name = row['Domain'] transfer_code = row['Transfer code'] - domain_transfers << { 'domainName' => domain_name, 'transferCode' => transfer_code } + domain_transfers << { 'domain_name' => domain_name, 'transfer_code' => transfer_code } end - uri = URI.parse("#{ENV['repp_url']}domain_transfers") + uri = URI.parse("#{ENV['repp_url']}domains/transfer") request = Net::HTTP::Post.new(uri, 'Content-Type' => 'application/json') - request.body = { data: { domainTransfers: domain_transfers } }.to_json + request.body = { data: { domain_transfers: domain_transfers } }.to_json request.basic_auth(current_registrar_user.username, current_registrar_user.plain_text_password) diff --git a/app/controllers/registrar/domains_controller.rb b/app/controllers/registrar/domains_controller.rb index 50ad0bd10..1a46da463 100644 --- a/app/controllers/registrar/domains_controller.rb +++ b/app/controllers/registrar/domains_controller.rb @@ -11,26 +11,27 @@ class Registrar search_params[:name_matches].present? domain = Domain.find_by(name: search_params[:name_matches]) - if domain - redirect_to info_registrar_domains_url(domain_name: domain.name) and return - end + redirect_to info_registrar_domains_url(domain_name: domain.name) and return if domain end - if params[:statuses_contains] - domains = current_registrar_user.registrar.domains.includes(:registrar, :registrant).where( - "statuses @> ?::varchar[]", "{#{params[:statuses_contains].join(',')}}" - ) - else - domains = current_registrar_user.registrar.domains.includes(:registrar, :registrant) + domains = if params[:statuses_contains] + current_domain_scope.where('domains.statuses @> ?::varchar[]', + "{#{params[:statuses_contains].join(',')}}") + else + current_domain_scope + end + + if params[:contacts_ident_eq] + domains = domains.where(contacts: { ident: params[:contacts_ident_eq] }) end normalize_search_parameters do - @q = domains.search(search_params) + @q = domains.search(search_params.except(:contacts_ident_eq)) @domains = @q.result.page(params[:page]) # if we do not get any results, add wildcards to the name field and search again if @domains.count == 0 && search_params[:name_matches] !~ /^%.+%$/ - new_search_params = search_params.to_h + new_search_params = search_params.to_h.except(:contacts_ident_eq) new_search_params[:name_matches] = "%#{new_search_params[:name_matches]}%" @q = domains.search(new_search_params) @domains = @q.result.page(params[:page]) @@ -56,6 +57,10 @@ class Registrar end end + def current_domain_scope + current_registrar_user.registrar.domains.includes(:registrar, :registrant) + end + def info authorize! :info, Depp::Domain @data = @domain.info(params[:domain_name]) if params[:domain_name] diff --git a/app/controllers/registrar/nameservers_controller.rb b/app/controllers/registrar/nameservers_controller.rb index 95da7e329..c8c88c8ca 100644 --- a/app/controllers/registrar/nameservers_controller.rb +++ b/app/controllers/registrar/nameservers_controller.rb @@ -6,9 +6,12 @@ class Registrar ipv4 = params[:ipv4].split("\r\n") ipv6 = params[:ipv6].split("\r\n") + domains = domain_list_from_csv + uri = URI.parse("#{ENV['repp_url']}registrar/nameservers") request = Net::HTTP::Put.new(uri, 'Content-Type' => 'application/json') request.body = { data: { type: 'nameserver', id: params[:old_hostname], + domains: domains, attributes: { hostname: params[:new_hostname], ipv4: ipv4, ipv6: ipv6 } } }.to_json @@ -55,5 +58,13 @@ class Registrar render file: 'registrar/bulk_change/new', locals: { active_tab: :nameserver } end end + + def domain_list_from_csv + return [] if params[:puny_file].blank? + + domains = [] + CSV.read(params[:puny_file].path, headers: true).each { |b| domains << b['domain_name'] } + domains + end end end diff --git a/app/controllers/repp/v1/accounts_controller.rb b/app/controllers/repp/v1/accounts_controller.rb new file mode 100644 index 000000000..89c14808f --- /dev/null +++ b/app/controllers/repp/v1/accounts_controller.rb @@ -0,0 +1,11 @@ +module Repp + module V1 + class AccountsController < BaseController + def balance + resp = { balance: current_user.registrar.cash_account.balance, + currency: current_user.registrar.cash_account.currency } + render_success(data: resp) + end + end + end +end diff --git a/app/controllers/repp/v1/auctions_controller.rb b/app/controllers/repp/v1/auctions_controller.rb index 4a5265d13..676dac266 100644 --- a/app/controllers/repp/v1/auctions_controller.rb +++ b/app/controllers/repp/v1/auctions_controller.rb @@ -3,9 +3,9 @@ module Repp class AuctionsController < ActionController::API def index auctions = Auction.started + @response = { count: auctions.count, auctions: auctions_to_json(auctions) } - render json: { count: auctions.count, - auctions: auctions_to_json(auctions) } + render json: @response end private diff --git a/app/controllers/repp/v1/base_controller.rb b/app/controllers/repp/v1/base_controller.rb new file mode 100644 index 000000000..ca9ef6fb5 --- /dev/null +++ b/app/controllers/repp/v1/base_controller.rb @@ -0,0 +1,111 @@ +module Repp + module V1 + class BaseController < ActionController::API + rescue_from ActiveRecord::RecordNotFound, with: :not_found_error + before_action :authenticate_user + before_action :check_ip_restriction + attr_reader :current_user + + before_action :set_paper_trail_whodunnit + + rescue_from ActionController::ParameterMissing do |exception| + render json: { code: 2003, message: exception }, status: :bad_request + end + + after_action do + ApiLog::ReppLog.create( + request_path: request.path, request_method: request.request_method, + request_params: request.params.except('route_info').to_json, uuid: request.try(:uuid), + response: @response.to_json, response_code: status, ip: request.ip, + api_user_name: current_user.try(:username), + api_user_registrar: current_user.try(:registrar).try(:to_s) + ) + end + + private + + def set_paper_trail_whodunnit + ::PaperTrail.request.whodunnit = current_user + end + + def render_success(code: nil, message: nil, data: nil) + @response = { code: code || 1000, message: message || 'Command completed successfully', + data: data || {} } + + render(json: @response, status: :ok) + end + + def epp_errors + @epp_errors ||= [] + end + + def handle_errors(obj = nil, update: false) + @epp_errors ||= [] + + obj&.construct_epp_errors + @epp_errors += obj.errors[:epp_errors] if obj + + format_epp_errors if update + @epp_errors.uniq! + + render_epp_error + end + + def format_epp_errors + @epp_errors.each_with_index do |error, index| + blocked_by_delete_prohibited?(error, index) + end + end + + def blocked_by_delete_prohibited?(error, index) + if error[:code] == 2304 && error[:value][:val] == DomainStatus::SERVER_DELETE_PROHIBITED && + error[:value][:obj] == 'status' + + @epp_errors[index][:value][:val] = DomainStatus::PENDING_UPDATE + end + end + + def render_epp_error(status = :bad_request, data = {}) + @epp_errors ||= [] + @epp_errors << { code: 2304, msg: 'Command failed' } if data != {} + + @response = { code: @epp_errors[0][:code].to_i, message: @epp_errors[0][:msg], data: data } + render(json: @response, status: status) + end + + def basic_token + pattern = /^Basic / + header = request.headers['Authorization'] + header = header.gsub(pattern, '') if header&.match(pattern) + header.strip + end + + def authenticate_user + username, password = Base64.urlsafe_decode64(basic_token).split(':') + @current_user ||= ApiUser.find_by(username: username, plain_text_password: password) + + return if @current_user + + raise(ArgumentError) + rescue NoMethodError, ArgumentError + @response = { code: 2202, message: 'Invalid authorization information' } + render(json: @response, status: :unauthorized) + end + + def check_ip_restriction + allowed = @current_user.registrar.api_ip_white?(request.ip) + + return if allowed + + @response = { code: 2202, + message: I18n.t('registrar.authorization.ip_not_allowed', ip: request.ip) } + render(json: @response, status: :unauthorized) + end + + def not_found_error + @response = { code: 2303, message: 'Object does not exist' } + render(json: @response, status: :not_found) + end + end + end +end diff --git a/app/controllers/repp/v1/contacts_controller.rb b/app/controllers/repp/v1/contacts_controller.rb new file mode 100644 index 000000000..acf275c47 --- /dev/null +++ b/app/controllers/repp/v1/contacts_controller.rb @@ -0,0 +1,137 @@ +require 'serializers/repp/contact' +module Repp + module V1 + class ContactsController < BaseController + before_action :find_contact, only: %i[show update destroy] + + ## GET /repp/v1/contacts + def index + record_count = current_user.registrar.contacts.count + contacts = showable_contacts(params[:details], params[:limit] || 200, + params[:offset] || 0) + @response = { contacts: contacts, total_number_of_records: record_count } + render(json: @response, status: :ok) + end + + ## GET /repp/v1/contacts/1 + def show + serializer = ::Serializers::Repp::Contact.new(@contact, + show_address: Contact.address_processing?) + render_success(data: serializer.to_json) + end + + ## GET /repp/v1/contacts/check/1 + def check + contact = Epp::Contact.find_by(code: params[:id]) + data = { contact: { id: params[:id], available: contact.nil? } } + + render_success(data: data) + end + + ## POST /repp/v1/contacts + def create + @contact = Epp::Contact.new(contact_params_with_address, current_user.registrar, epp: false) + action = Actions::ContactCreate.new(@contact, params[:legal_document], + contact_ident_params) + + unless action.call + handle_errors(@contact) + return + end + + render_success(create_update_success_body) + end + + ## PUT /repp/v1/contacts/1 + def update + action = Actions::ContactUpdate.new(@contact, contact_params_with_address(required: false), + params[:legal_document], + contact_ident_params(required: false), current_user) + + unless action.call + handle_errors(@contact) + return + end + + render_success(create_update_success_body) + end + + def destroy + action = Actions::ContactDelete.new(@contact, params[:legal_document]) + unless action.call + handle_errors(@contact) + return + end + + render_success + end + + def contact_addr_present? + return false unless contact_addr_params.key?(:addr) + + contact_addr_params[:addr].keys.any? + end + + def create_update_success_body + { code: opt_addr? ? 1100 : nil, data: { contact: { id: @contact.code } }, + message: opt_addr? ? I18n.t('epp.contacts.completed_without_address') : nil } + end + + def showable_contacts(details, limit, offset) + contacts = current_user.registrar.contacts.limit(limit).offset(offset) + + return contacts.pluck(:code) unless details + + contacts = contacts.map do |contact| + serializer = ::Serializers::Repp::Contact.new(contact, + show_address: Contact.address_processing?) + serializer.to_json + end + + contacts + end + + def opt_addr? + !Contact.address_processing? && contact_addr_present? + end + + def find_contact + code = params[:id] + @contact = Epp::Contact.find_by!(code: code, registrar: current_user.registrar) + end + + def contact_params_with_address(required: true) + return contact_create_params(required: required) unless contact_addr_params.key?(:addr) + + addr = {} + contact_addr_params[:addr].each_key { |k| addr[k] = contact_addr_params[:addr][k] } + contact_create_params(required: required).merge(addr) + end + + def contact_create_params(required: true) + params.require(:contact).require(%i[name email phone]) if required + params.require(:contact).permit(:name, :email, :phone, :id) + end + + def contact_ident_params(required: true) + if required + params.require(:contact).require(:ident).require(%i[ident ident_type ident_country_code]) + params.require(:contact).require(:ident).permit(:ident, :ident_type, :ident_country_code) + else + params.permit(contact: { ident: %i[ident ident_type ident_country_code] }) + end + + params[:contact][:ident] + end + + def contact_addr_params + if Contact.address_processing? + params.require(:contact).require(:addr).require(%i[country_code city street zip]) + params.require(:contact).require(:addr).permit(:country_code, :city, :street, :zip) + else + params.require(:contact).permit(addr: %i[country_code city street zip]) + end + end + end + end +end diff --git a/app/controllers/repp/v1/domains/contacts_controller.rb b/app/controllers/repp/v1/domains/contacts_controller.rb new file mode 100644 index 000000000..75404e0c6 --- /dev/null +++ b/app/controllers/repp/v1/domains/contacts_controller.rb @@ -0,0 +1,42 @@ +module Repp + module V1 + module Domains + class ContactsController < BaseController + before_action :set_current_contact, only: [:update] + before_action :set_new_contact, only: [:update] + + def set_current_contact + @current_contact = current_user.registrar.contacts.find_by!( + code: contact_params[:current_contact_id] + ) + end + + def set_new_contact + @new_contact = current_user.registrar.contacts.find_by!(code: params[:new_contact_id]) + end + + def update + @epp_errors ||= [] + @epp_errors << { code: 2304, msg: 'New contact must be valid' } if @new_contact.invalid? + + if @new_contact == @current_contact + @epp_errors << { code: 2304, msg: 'New contact must be different from current' } + end + + return handle_errors if @epp_errors.any? + + affected, skipped = TechDomainContact.replace(@current_contact, @new_contact) + @response = { affected_domains: affected, skipped_domains: skipped } + render_success(data: @response) + end + + private + + def contact_params + params.require(%i[current_contact_id new_contact_id]) + params.permit(:current_contact_id, :new_contact_id) + end + end + end + end +end diff --git a/app/controllers/repp/v1/domains_controller.rb b/app/controllers/repp/v1/domains_controller.rb new file mode 100644 index 000000000..ba90f23f2 --- /dev/null +++ b/app/controllers/repp/v1/domains_controller.rb @@ -0,0 +1,94 @@ +module Repp + module V1 + class DomainsController < BaseController + before_action :set_authorized_domain, only: [:transfer_info] + + def index + records = current_user.registrar.domains + domains = records.limit(limit).offset(offset) + domains = domains.pluck(:name) unless index_params[:details] == 'true' + + render_success(data: { domains: domains, total_number_of_records: records.count }) + end + + def transfer_info + contact_fields = %i[code name ident ident_type ident_country_code phone email street city + zip country_code statuses] + + data = { + domain: @domain.name, + registrant: @domain.registrant.as_json(only: contact_fields), + admin_contacts: @domain.admin_contacts.map { |c| c.as_json(only: contact_fields) }, + tech_contacts: @domain.tech_contacts.map { |c| c.as_json(only: contact_fields) }, + } + + render_success(data: data) + end + + def transfer + @errors ||= [] + @successful = [] + + transfer_params[:domain_transfers].each do |transfer| + initiate_transfer(transfer) + end + + render_success(data: { success: @successful, failed: @errors }) + end + + def initiate_transfer(transfer) + domain = Epp::Domain.find_or_initialize_by(name: transfer[:domain_name]) + action = Actions::DomainTransfer.new(domain, transfer[:transfer_code], + current_user.registrar) + + if action.call + @successful << { type: 'domain_transfer', domain_name: domain.name } + else + @errors << { type: 'domain_transfer', domain_name: domain.name, + errors: domain.errors[:epp_errors] } + end + end + + private + + def transfer_params + params.require(:data).require(:domain_transfers).each do |t| + t.require(:domain_name) + t.permit(:domain_name) + t.require(:transfer_code) + t.permit(:transfer_code) + end + params.require(:data).permit(domain_transfers: %i[domain_name transfer_code]) + end + + def transfer_info_params + params.require(:id) + params.permit(:id) + end + + def set_authorized_domain + @epp_errors ||= [] + h = {} + h[transfer_info_params[:id].match?(/\A[0-9]+\z/) ? :id : :name] = transfer_info_params[:id] + @domain = Domain.find_by!(h) + + return if @domain.transfer_code.eql?(request.headers['Auth-Code']) + + @epp_errors << { code: 2202, msg: I18n.t('errors.messages.epp_authorization_error') } + handle_errors + end + + def limit + index_params[:limit] || 200 + end + + def offset + index_params[:offset] || 0 + end + + def index_params + params.permit(:limit, :offset, :details) + end + end + end +end diff --git a/app/controllers/repp/v1/registrar/nameservers_controller.rb b/app/controllers/repp/v1/registrar/nameservers_controller.rb new file mode 100644 index 000000000..47004d97b --- /dev/null +++ b/app/controllers/repp/v1/registrar/nameservers_controller.rb @@ -0,0 +1,51 @@ +module Repp + module V1 + module Registrar + class NameserversController < BaseController + before_action :verify_nameserver_existance, only: %i[update] + + def update + domains = params[:data][:domains] || [] + affected = current_user.registrar + .replace_nameservers(hostname, + hostname_params[:data][:attributes], + domains: domains) + + render_success(data: data_format_for_success(affected)) + rescue ActiveRecord::RecordInvalid => e + handle_errors(e.record) + end + + private + + def data_format_for_success(affected_domains) + { + type: 'nameserver', + id: params[:data][:attributes][:hostname], + attributes: params[:data][:attributes], + affected_domains: affected_domains, + } + end + + def hostname_params + params.require(:data).require(%i[type id]) + params.require(:data).require(:attributes).require([:hostname]) + + params.permit(data: [ + :type, :id, + { domains: [], + attributes: [:hostname, { ipv4: [], ipv6: [] }] } + ]) + end + + def hostname + hostname_params[:data][:id] + end + + def verify_nameserver_existance + current_user.registrar.nameservers.find_by!(hostname: hostname) + end + end + end + end +end diff --git a/app/controllers/repp/v1/retained_domains_controller.rb b/app/controllers/repp/v1/retained_domains_controller.rb index c1bb458e9..8edc32f5b 100644 --- a/app/controllers/repp/v1/retained_domains_controller.rb +++ b/app/controllers/repp/v1/retained_domains_controller.rb @@ -3,8 +3,9 @@ module Repp class RetainedDomainsController < ActionController::API def index domains = RetainedDomains.new(query_params) + @response = { count: domains.count, domains: domains.to_jsonable } - render json: { count: domains.count, domains: domains.to_jsonable } + render json: @response end def query_params diff --git a/app/helpers/object_versions_helper.rb b/app/helpers/object_versions_helper.rb index d8e00abbe..be8ef1217 100644 --- a/app/helpers/object_versions_helper.rb +++ b/app/helpers/object_versions_helper.rb @@ -3,7 +3,7 @@ module ObjectVersionsHelper version.object_changes.to_h.each do |key, value| method_name = "#{key}=".to_sym if new_object.respond_to?(method_name) - new_object.public_send(method_name, value.last) + new_object.public_send(method_name, event_value(version, value)) end end end @@ -12,4 +12,10 @@ module ObjectVersionsHelper field_names = model.column_names version.object.to_h.select { |key, _value| field_names.include?(key) } end + + private + + def event_value(version, val) + version.event == 'destroy' ? val.first : val.last + end end diff --git a/app/interactions/domains/cancel_force_delete/base.rb b/app/interactions/domains/cancel_force_delete/base.rb new file mode 100644 index 000000000..b15a3ae77 --- /dev/null +++ b/app/interactions/domains/cancel_force_delete/base.rb @@ -0,0 +1,9 @@ +module Domains + module CancelForceDelete + class Base < ActiveInteraction::Base + object :domain, + class: Domain, + description: 'Domain to cancel ForceDelete on' + end + end +end diff --git a/app/interactions/domains/cancel_force_delete/cancel_force_delete.rb b/app/interactions/domains/cancel_force_delete/cancel_force_delete.rb new file mode 100644 index 000000000..7c4ca90e1 --- /dev/null +++ b/app/interactions/domains/cancel_force_delete/cancel_force_delete.rb @@ -0,0 +1,12 @@ +module Domains + module CancelForceDelete + class CancelForceDelete < Base + def execute + compose(RemoveForceDeleteStatuses, inputs) + compose(RestoreStatusesBeforeForceDelete, inputs) + compose(ClearForceDeleteData, inputs) + compose(NotifyRegistrar, inputs) + end + end + end +end diff --git a/app/interactions/domains/cancel_force_delete/clear_force_delete_data.rb b/app/interactions/domains/cancel_force_delete/clear_force_delete_data.rb new file mode 100644 index 000000000..066df04fd --- /dev/null +++ b/app/interactions/domains/cancel_force_delete/clear_force_delete_data.rb @@ -0,0 +1,12 @@ +module Domains + module CancelForceDelete + class ClearForceDeleteData < Base + def execute + domain.force_delete_data = nil + domain.force_delete_date = nil + domain.force_delete_start = nil + domain.save(validate: false) + end + end + end +end diff --git a/app/interactions/domains/cancel_force_delete/notify_registrar.rb b/app/interactions/domains/cancel_force_delete/notify_registrar.rb new file mode 100644 index 000000000..0de69e13e --- /dev/null +++ b/app/interactions/domains/cancel_force_delete/notify_registrar.rb @@ -0,0 +1,10 @@ +module Domains + module CancelForceDelete + class NotifyRegistrar < Base + def execute + domain.registrar.notifications.create!(text: I18n.t('force_delete_cancelled', + domain_name: domain.name)) + end + end + end +end diff --git a/app/interactions/domains/cancel_force_delete/remove_force_delete_statuses.rb b/app/interactions/domains/cancel_force_delete/remove_force_delete_statuses.rb new file mode 100644 index 000000000..c77820ecf --- /dev/null +++ b/app/interactions/domains/cancel_force_delete/remove_force_delete_statuses.rb @@ -0,0 +1,13 @@ +module Domains + module CancelForceDelete + class RemoveForceDeleteStatuses < Base + def execute + domain.statuses.delete(DomainStatus::FORCE_DELETE) + domain.statuses.delete(DomainStatus::SERVER_RENEW_PROHIBITED) + domain.statuses.delete(DomainStatus::SERVER_TRANSFER_PROHIBITED) + domain.statuses.delete(DomainStatus::CLIENT_HOLD) + domain.save(validate: false) + end + end + end +end diff --git a/app/interactions/domains/cancel_force_delete/restore_statuses_before_force_delete.rb b/app/interactions/domains/cancel_force_delete/restore_statuses_before_force_delete.rb new file mode 100644 index 000000000..c55c06764 --- /dev/null +++ b/app/interactions/domains/cancel_force_delete/restore_statuses_before_force_delete.rb @@ -0,0 +1,11 @@ +module Domains + module CancelForceDelete + class RestoreStatusesBeforeForceDelete < Base + def execute + domain.statuses = domain.statuses_before_force_delete + domain.statuses_before_force_delete = nil + domain.save(validate: false) + end + end + end +end diff --git a/app/interactions/domains/client_hold/base.rb b/app/interactions/domains/client_hold/base.rb new file mode 100644 index 000000000..c3c626b79 --- /dev/null +++ b/app/interactions/domains/client_hold/base.rb @@ -0,0 +1,10 @@ +module Domains + module ClientHold + class Base < ActiveInteraction::Base + def to_stdout(message) + time = Time.zone.now.utc + STDOUT << "#{time} - #{message}\n" unless Rails.env.test? + end + end + end +end diff --git a/app/interactions/domains/client_hold/process_client_hold.rb b/app/interactions/domains/client_hold/process_client_hold.rb new file mode 100644 index 000000000..8b8d60403 --- /dev/null +++ b/app/interactions/domains/client_hold/process_client_hold.rb @@ -0,0 +1,69 @@ +module Domains + module ClientHold + class ProcessClientHold < Base + object :domain, + class: Domain, + description: 'Domain to set ClientHold on' + + # rubocop:disable Metrics/AbcSize + def execute + notify_on_grace_period if should_notify_on_soft_force_delete? + + return unless client_holdable? + + domain.statuses << DomainStatus::CLIENT_HOLD + to_stdout("DomainCron.start_client_hold: #{domain.id} (#{domain.name}) #{domain.changes}\n") + + domain.save(validate: false) + notify_client_hold + + to_stdout("Successfully set client_hold on (#{domain.name})") + end + + def notify_on_grace_period + domain.registrar.notifications.create!(text: I18n.t('grace_period_started_domain', + domain_name: domain.name, + date: domain.force_delete_start)) + send_mail if domain.template_name.present? + domain.update(contact_notification_sent_date: Time.zone.today) + end + + def notify_client_hold + domain.registrar.notifications.create!(text: I18n.t('force_delete_set_on_domain', + domain_name: domain.name, + outzone_date: domain.outzone_date, + purge_date: domain.purge_date)) + end + + def send_mail + DomainDeleteMailer.forced(domain: domain, + registrar: domain.registrar, + registrant: domain.registrant, + template_name: domain.template_name).deliver_now + end + + def should_notify_on_soft_force_delete? + domain.force_delete_scheduled? && domain.contact_notification_sent_date.blank? && + domain.force_delete_start.to_date <= Time.zone.now.to_date && + domain.force_delete_type.to_sym == :soft && + !domain.statuses.include?(DomainStatus::CLIENT_HOLD) + end + # rubocop:enable Metrics/AbcSize + + def client_holdable? + domain.force_delete_scheduled? && + !domain.statuses.include?(DomainStatus::CLIENT_HOLD) && + domain.force_delete_start.present? && + force_delete_lte_today && force_delete_lte_valid_date + end + + def force_delete_lte_today + domain.force_delete_start + Setting.expire_warning_period.days <= Time.zone.now + end + + def force_delete_lte_valid_date + domain.force_delete_start + Setting.expire_warning_period.days <= domain.valid_to + end + end + end +end diff --git a/app/interactions/domains/client_hold/set_client_hold.rb b/app/interactions/domains/client_hold/set_client_hold.rb new file mode 100644 index 000000000..0e54b531f --- /dev/null +++ b/app/interactions/domains/client_hold/set_client_hold.rb @@ -0,0 +1,17 @@ +module Domains + module ClientHold + class SetClientHold < Base + def execute + to_stdout('Setting client_hold to domains\n') + + ::PaperTrail.request.whodunnit = "cron - #{self.class.name}" + + ::Domain.force_delete_scheduled.each do |domain| + Domains::ClientHold::ProcessClientHold.run(domain: domain) + end + + to_stdout('All client_hold setting are done\n') + end + end + end +end diff --git a/app/interactions/domains/delete_confirm/send_request.rb b/app/interactions/domains/delete_confirm/send_request.rb new file mode 100644 index 000000000..91afaefb8 --- /dev/null +++ b/app/interactions/domains/delete_confirm/send_request.rb @@ -0,0 +1,24 @@ +module Domains + module DeleteConfirm + class SendRequest < ActiveInteraction::Base + object :domain, + class: Domain, + description: 'Domain to send delete confirmation' + + def execute + log + DomainDeleteMailer.confirmation_request(domain: domain, + registrar: domain.registrar, + registrant: domain.registrant).deliver_later + end + + private + + def log + message = "Send DomainDeleteMailer#confirm email for domain #{domain.name} (##{domain.id})"\ + " to #{domain.registrant.email}" + Rails.logger.info(message) + end + end + end +end diff --git a/app/interactions/domains/force_delete/base.rb b/app/interactions/domains/force_delete/base.rb new file mode 100644 index 000000000..27601c1d2 --- /dev/null +++ b/app/interactions/domains/force_delete/base.rb @@ -0,0 +1,17 @@ +module Domains + module ForceDelete + class Base < ActiveInteraction::Base + object :domain, + class: Domain, + description: 'Domain to set ForceDelete on' + symbol :type, + default: :fast_track, + description: 'Force delete type, might be :fast_track or :soft' + boolean :notify_by_email, + default: false, + description: 'Do we need to send email notification' + + validates :type, inclusion: { in: %i[fast_track soft] } + end + end +end diff --git a/app/interactions/domains/force_delete/check_discarded.rb b/app/interactions/domains/force_delete/check_discarded.rb new file mode 100644 index 000000000..cbb9c1dc6 --- /dev/null +++ b/app/interactions/domains/force_delete/check_discarded.rb @@ -0,0 +1,12 @@ +module Domains + module ForceDelete + class CheckDiscarded < Base + def execute + return true unless domain.discarded? + + message = 'Force delete procedure cannot be scheduled while a domain is discarded' + errors.add(:domain, message) + end + end + end +end diff --git a/app/interactions/domains/force_delete/notify_by_email.rb b/app/interactions/domains/force_delete/notify_by_email.rb new file mode 100644 index 000000000..b60f54a5e --- /dev/null +++ b/app/interactions/domains/force_delete/notify_by_email.rb @@ -0,0 +1,23 @@ +module Domains + module ForceDelete + class NotifyByEmail < Base + def execute + return unless notify_by_email + + if type == :fast_track + send_email + domain.update(contact_notification_sent_date: Time.zone.today) + else + domain.update(template_name: domain.notification_template) + end + end + + def send_email + DomainDeleteMailer.forced(domain: domain, + registrar: domain.registrar, + registrant: domain.registrant, + template_name: domain.notification_template).deliver_now + end + end + end +end diff --git a/app/interactions/domains/force_delete/notify_registrar.rb b/app/interactions/domains/force_delete/notify_registrar.rb new file mode 100644 index 000000000..522502640 --- /dev/null +++ b/app/interactions/domains/force_delete/notify_registrar.rb @@ -0,0 +1,12 @@ +module Domains + module ForceDelete + class NotifyRegistrar < Base + def execute + domain.registrar.notifications.create!(text: I18n.t('force_delete_set_on_domain', + domain_name: domain.name, + outzone_date: domain.outzone_date, + purge_date: domain.purge_date)) + end + end + end +end diff --git a/app/interactions/domains/force_delete/post_set_process.rb b/app/interactions/domains/force_delete/post_set_process.rb new file mode 100644 index 000000000..904149f93 --- /dev/null +++ b/app/interactions/domains/force_delete/post_set_process.rb @@ -0,0 +1,19 @@ +module Domains + module ForceDelete + class PostSetProcess < Base + def execute + statuses = domain.statuses + # Stop all pending actions + statuses.delete(DomainStatus::PENDING_UPDATE) + statuses.delete(DomainStatus::PENDING_TRANSFER) + statuses.delete(DomainStatus::PENDING_RENEW) + statuses.delete(DomainStatus::PENDING_CREATE) + + # Allow deletion + statuses.delete(DomainStatus::CLIENT_DELETE_PROHIBITED) + statuses.delete(DomainStatus::SERVER_DELETE_PROHIBITED) + domain.save(validate: false) + end + end + end +end diff --git a/app/interactions/domains/force_delete/prepare_domain.rb b/app/interactions/domains/force_delete/prepare_domain.rb new file mode 100644 index 000000000..74eea21ed --- /dev/null +++ b/app/interactions/domains/force_delete/prepare_domain.rb @@ -0,0 +1,15 @@ +module Domains + module ForceDelete + class PrepareDomain < Base + STATUSES_TO_SET = [DomainStatus::FORCE_DELETE, + DomainStatus::SERVER_RENEW_PROHIBITED, + DomainStatus::SERVER_TRANSFER_PROHIBITED].freeze + + def execute + domain.statuses_before_force_delete = domain.statuses + domain.statuses |= STATUSES_TO_SET + domain.save(validate: false) + end + end + end +end diff --git a/app/interactions/domains/force_delete/set_force_delete.rb b/app/interactions/domains/force_delete/set_force_delete.rb new file mode 100644 index 000000000..16a0b09fa --- /dev/null +++ b/app/interactions/domains/force_delete/set_force_delete.rb @@ -0,0 +1,14 @@ +module Domains + module ForceDelete + class SetForceDelete < Base + def execute + compose(CheckDiscarded, inputs) + compose(PrepareDomain, inputs) + compose(SetStatus, inputs) + compose(PostSetProcess, inputs) + compose(NotifyRegistrar, inputs) + compose(NotifyByEmail, inputs) + end + end + end +end diff --git a/app/interactions/domains/force_delete/set_status.rb b/app/interactions/domains/force_delete/set_status.rb new file mode 100644 index 000000000..b0a53ad82 --- /dev/null +++ b/app/interactions/domains/force_delete/set_status.rb @@ -0,0 +1,40 @@ +module Domains + module ForceDelete + class SetStatus < Base + def execute + domain.force_delete_type = type + type == :fast_track ? force_delete_fast_track : force_delete_soft + domain.save(validate: false) + end + + def force_delete_fast_track + domain.force_delete_date = Time.zone.today + + expire_warning_period_days + + redemption_grace_period_days + domain.force_delete_start = Time.zone.today + 1.day + end + + def force_delete_soft + years = (domain.valid_to.to_date - Time.zone.today).to_i / 365 + soft_forcedelete_dates(years) if years.positive? + end + + private + + def soft_forcedelete_dates(years) + domain.force_delete_start = domain.valid_to - years.years + domain.force_delete_date = domain.force_delete_start + + Setting.expire_warning_period.days + + Setting.redemption_grace_period.days + end + + def redemption_grace_period_days + Setting.redemption_grace_period.days + 1.day + end + + def expire_warning_period_days + Setting.expire_warning_period.days + end + end + end +end diff --git a/app/jobs/domain_delete_confirm_email_job.rb b/app/jobs/domain_delete_confirm_email_job.rb deleted file mode 100644 index ea5a9bf49..000000000 --- a/app/jobs/domain_delete_confirm_email_job.rb +++ /dev/null @@ -1,22 +0,0 @@ -class DomainDeleteConfirmEmailJob < Que::Job - def run(domain_id) - domain = Domain.find(domain_id) - - log(domain) - DomainDeleteMailer.confirmation_request(domain: domain, - registrar: domain.registrar, - registrant: domain.registrant).deliver_now - end - - private - - def log(domain) - message = "Send DomainDeleteMailer#confirm email for domain #{domain.name} (##{domain.id})" \ - " to #{domain.registrant.email}" - logger.info(message) - end - - def logger - Rails.logger - end -end diff --git a/app/jobs/domain_expire_email_job.rb b/app/jobs/domain_expire_email_job.rb index 9b70a54e6..94bd8670c 100644 --- a/app/jobs/domain_expire_email_job.rb +++ b/app/jobs/domain_expire_email_job.rb @@ -4,6 +4,10 @@ class DomainExpireEmailJob < Que::Job return if domain.registered? - DomainExpireMailer.expired(domain: domain, registrar: domain.registrar).deliver_now + if domain.force_delete_scheduled? + DomainExpireMailer.expired_soft(domain: domain, registrar: domain.registrar).deliver_now + else + DomainExpireMailer.expired(domain: domain, registrar: domain.registrar).deliver_now + end end end diff --git a/app/mailers/application_mailer.rb b/app/mailers/application_mailer.rb index 9269a1102..af5d59d63 100644 --- a/app/mailers/application_mailer.rb +++ b/app/mailers/application_mailer.rb @@ -1,4 +1,15 @@ class ApplicationMailer < ActionMailer::Base append_view_path Rails.root.join('app', 'views', 'mailers') layout 'mailer' -end \ No newline at end of file + + def registrant_confirm_url(domain:, method:) + token = domain.registrant_verification_token + base_url = ENV['registrant_portal_verifications_base_url'] + + url = registrant_domain_delete_confirm_url(domain, token: token) if method == 'delete' + url ||= registrant_domain_update_confirm_url(domain, token: token) + return url if base_url.blank? + + "#{base_url}/confirmation/#{domain.name_puny}/#{method}/#{token}" + end +end diff --git a/app/mailers/domain_delete_mailer.rb b/app/mailers/domain_delete_mailer.rb index 1f08204bf..3e4507194 100644 --- a/app/mailers/domain_delete_mailer.rb +++ b/app/mailers/domain_delete_mailer.rb @@ -1,12 +1,8 @@ class DomainDeleteMailer < ApplicationMailer - def self.force_delete_templates - %w[private_person legal_person] - end - def confirmation_request(domain:, registrar:, registrant:) @domain = DomainPresenter.new(domain: domain, view: view_context) @registrar = RegistrarPresenter.new(registrar: registrar, view: view_context) - @confirmation_url = confirmation_url(domain) + @confirmation_url = registrant_confirm_url(domain: domain, method: 'delete') subject = default_i18n_subject(domain_name: domain.name) mail(to: registrant.email, subject: subject) @@ -52,10 +48,6 @@ class DomainDeleteMailer < ApplicationMailer private - def confirmation_url(domain) - registrant_domain_delete_confirm_url(domain, token: domain.registrant_verification_token) - end - def forced_email_from ENV['action_mailer_force_delete_from'] || self.class.default[:from] end diff --git a/app/mailers/domain_expire_mailer.rb b/app/mailers/domain_expire_mailer.rb index ecbd8ee3d..229120825 100644 --- a/app/mailers/domain_expire_mailer.rb +++ b/app/mailers/domain_expire_mailer.rb @@ -1,19 +1,38 @@ class DomainExpireMailer < ApplicationMailer + attr_accessor :domain, :registrar + def expired(domain:, registrar:) - @domain = domain_presenter(domain: domain) - @registrar = registrar_presenter(registrar: registrar) + process_mail(domain: domain, registrar: registrar, method_name: __method__.to_s) + end - recipient = filter_invalid_emails(emails: domain.primary_contact_emails, domain: domain) - subject = default_i18n_subject(domain_name: domain.name) - - logger.info("Send DomainExpireMailer#expired email for domain #{domain.name} (##{domain.id})" \ - " to #{recipient.join(', ')}") - - mail(to: recipient, subject: subject) + def expired_soft(domain:, registrar:) + process_mail(domain: domain, registrar: registrar, method_name: __method__.to_s) end private + def process_mail(domain:, registrar:, method_name:) + init(domain, registrar) + + logger.info("Send DomainExpireMailer##{method_name} email for #{domain.name} (##{domain.id})" \ + " to #{recipient(domain).join(', ')}") + + mail(to: recipient(domain), subject: subject(method_name)) + end + + def init(domain, registrar) + @domain = domain_presenter(domain: domain) + @registrar = registrar_presenter(registrar: registrar) + end + + def recipient(domain) + filter_invalid_emails(emails: domain.primary_contact_emails, domain: @domain) + end + + def subject(method_name) + I18n.t("domain_expire_mailer.#{method_name}.subject", domain_name: @domain.name) + end + def domain_presenter(domain:) DomainPresenter.new(domain: domain, view: view_context) end diff --git a/app/mailers/registrant_change_mailer.rb b/app/mailers/registrant_change_mailer.rb index ff3cfa18e..8f43f4ab5 100644 --- a/app/mailers/registrant_change_mailer.rb +++ b/app/mailers/registrant_change_mailer.rb @@ -5,7 +5,7 @@ class RegistrantChangeMailer < ApplicationMailer @domain = DomainPresenter.new(domain: domain, view: view_context) @registrar = RegistrarPresenter.new(registrar: registrar, view: view_context) @new_registrant = RegistrantPresenter.new(registrant: new_registrant, view: view_context) - @confirmation_url = confirmation_url(domain) + @confirmation_url = registrant_confirm_url(domain: domain, method: 'change') subject = default_i18n_subject(domain_name: domain.name) mail(to: current_registrant.email, subject: subject) @@ -49,10 +49,6 @@ class RegistrantChangeMailer < ApplicationMailer private - def confirmation_url(domain) - registrant_domain_update_confirm_url(domain, token: domain.registrant_verification_token) - end - def address_processing Contact.address_processing? end diff --git a/app/models/actions/contact_create.rb b/app/models/actions/contact_create.rb new file mode 100644 index 000000000..22fabc7b9 --- /dev/null +++ b/app/models/actions/contact_create.rb @@ -0,0 +1,81 @@ +module Actions + class ContactCreate + attr_reader :contact, :legal_document, :ident + + def initialize(contact, legal_document, ident) + @contact = contact + @legal_document = legal_document + @ident = ident + end + + def call + maybe_remove_address + maybe_attach_legal_doc + validate_ident + commit + end + + def maybe_remove_address + return if Contact.address_processing? + + contact.city = nil + contact.zip = nil + contact.street = nil + contact.state = nil + contact.country_code = nil + end + + def validate_ident + validate_ident_integrity + validate_ident_birthday + + identifier = ::Contact::Ident.new(code: ident[:ident], type: ident[:ident_type], + country_code: ident[:ident_country_code]) + + identifier.validate + contact.identifier = identifier + end + + def validate_ident_integrity + return if ident.blank? + + if ident[:ident_type].blank? + contact.add_epp_error('2003', nil, 'ident_type', + I18n.t('errors.messages.required_ident_attribute_missing')) + @error = true + elsif !%w[priv org birthday].include?(ident[:ident_type]) + contact.add_epp_error('2003', nil, 'ident_type', 'Invalid ident type') + @error = true + end + end + + def validate_ident_birthday + return if ident.blank? + return unless ident[:ident_type] != 'birthday' && ident[:ident_country_code].blank? + + contact.add_epp_error('2003', nil, 'ident_country_code', + I18n.t('errors.messages.required_ident_attribute_missing')) + @error = true + end + + def maybe_attach_legal_doc + return unless legal_document + + doc = LegalDocument.create( + documentable_type: Contact, + document_type: legal_document[:type], body: legal_document[:body] + ) + + contact.legal_documents = [doc] + contact.legal_document_id = doc.id + end + + def commit + contact.id = nil # new record + return false if @error + + contact.generate_code + contact.save + end + end +end diff --git a/app/models/actions/contact_delete.rb b/app/models/actions/contact_delete.rb new file mode 100644 index 000000000..59032d566 --- /dev/null +++ b/app/models/actions/contact_delete.rb @@ -0,0 +1,41 @@ +module Actions + class ContactDelete + attr_reader :contact + attr_reader :new_attributes + attr_reader :legal_document + attr_reader :ident + attr_reader :user + + def initialize(contact, legal_document = nil) + @legal_document = legal_document + @contact = contact + end + + def call + maybe_attach_legal_doc + + if contact.linked? + contact.errors.add(:domains, :exist) + return + end + + commit + end + + def maybe_attach_legal_doc + return unless legal_document + + document = contact.legal_documents.create( + document_type: legal_document[:type], + body: legal_document[:body] + ) + + contact.legal_document_id = document.id + contact.save + end + + def commit + contact.destroy + end + end +end diff --git a/app/models/actions/contact_update.rb b/app/models/actions/contact_update.rb index f8b39ecb4..7ca7b6b04 100644 --- a/app/models/actions/contact_update.rb +++ b/app/models/actions/contact_update.rb @@ -17,7 +17,7 @@ module Actions def call maybe_remove_address maybe_update_statuses - maybe_update_ident + maybe_update_ident if ident.present? maybe_attach_legal_doc commit end @@ -53,7 +53,11 @@ module Actions end def maybe_update_ident - return unless ident[:ident] + unless ident.is_a?(Hash) + contact.add_epp_error('2308', nil, nil, I18n.t('epp.contacts.errors.valid_ident')) + @error = true + return + end if contact.identifier.valid? submitted_ident = ::Contact::Ident.new(code: ident[:ident], diff --git a/app/models/actions/domain_transfer.rb b/app/models/actions/domain_transfer.rb new file mode 100644 index 000000000..1ff9aafe9 --- /dev/null +++ b/app/models/actions/domain_transfer.rb @@ -0,0 +1,74 @@ +module Actions + class DomainTransfer + attr_reader :domain + attr_reader :transfer_code + attr_reader :legal_document + attr_reader :ident + attr_reader :user + + def initialize(domain, transfer_code, user) + @domain = domain + @transfer_code = transfer_code + @user = user + end + + def call + return unless domain_exists? + return unless valid_transfer_code? + + run_validations + + # return domain.pending_transfer if domain.pending_transfer + # attach_legal_document(::Deserializers::Xml::LegalDocument.new(frame).call) + + return if domain.errors[:epp_errors].any? + + commit + end + + def domain_exists? + return true if domain.persisted? + + domain.add_epp_error('2303', nil, nil, 'Object does not exist') + + false + end + + def run_validations + validate_registrar + validate_eligilibty + validate_not_discarded + end + + def valid_transfer_code? + return true if transfer_code == domain.transfer_code + + domain.add_epp_error('2202', nil, nil, 'Invalid authorization information') + false + end + + def validate_registrar + return unless user == domain.registrar + + domain.add_epp_error('2002', nil, nil, + I18n.t(:domain_already_belongs_to_the_querying_registrar)) + end + + def validate_eligilibty + return unless domain.non_transferable? + + domain.add_epp_error('2304', nil, nil, 'Object status prohibits operation') + end + + def validate_not_discarded + return unless domain.discarded? + + domain.add_epp_error('2106', nil, nil, 'Object is not eligible for transfer') + end + + def commit + bare_domain = Domain.find(domain.id) + ::DomainTransfer.request(bare_domain, user) + end + end +end diff --git a/app/models/concerns/domain/force_delete.rb b/app/models/concerns/domain/force_delete.rb index af3aaa7c7..e06da25cc 100644 --- a/app/models/concerns/domain/force_delete.rb +++ b/app/models/concerns/domain/force_delete.rb @@ -19,74 +19,28 @@ module Concerns::Domain::ForceDelete # rubocop:disable Metrics/ModuleLength end end + def notification_template + if contact_emails_verification_failed.present? + 'invalid_email' + elsif registrant.org? + 'legal_person' + else + 'private_person' + end + end + def force_delete_scheduled? statuses.include?(DomainStatus::FORCE_DELETE) end - def should_notify_on_soft_force_delete? - force_delete_scheduled? && contact_notification_sent_date.blank? && - force_delete_start.to_date <= Time.zone.now.to_date && force_delete_type.to_sym == :soft && - !statuses.include?(DomainStatus::CLIENT_HOLD) - end - - def client_holdable? - force_delete_scheduled? && !statuses.include?(DomainStatus::CLIENT_HOLD) && - force_delete_start.present? && force_delete_lte_today && force_delete_lte_valid_date - end - - def force_delete_lte_today - force_delete_start + Setting.expire_warning_period.days <= Time.zone.now - end - - def force_delete_lte_valid_date - force_delete_start + Setting.expire_warning_period.days <= valid_to - end - - def schedule_force_delete(type: :fast_track) - if discarded? - raise StandardError, 'Force delete procedure cannot be scheduled while a domain is discarded' - end - - type == :fast_track ? force_delete_fast_track : force_delete_soft - end - - def add_force_delete_type(force_delete_type) - self.force_delete_type = force_delete_type - end - - def force_delete_fast_track - preserve_current_statuses_for_force_delete - add_force_delete_statuses - add_force_delete_type(:fast) - self.force_delete_date = force_delete_fast_track_start_date + 1.day - self.force_delete_start = Time.zone.today + 1.day - stop_all_pending_actions - allow_deletion - save(validate: false) - end - - def force_delete_soft - preserve_current_statuses_for_force_delete - add_force_delete_statuses - add_force_delete_type(:soft) - calculate_soft_delete_date - stop_all_pending_actions - allow_deletion - save(validate: false) - end - - def clear_force_delete_data - self.force_delete_data = nil + def schedule_force_delete(type: :fast_track, notify_by_email: false) + Domains::ForceDelete::SetForceDelete.run(domain: self, + type: type, + notify_by_email: notify_by_email) end def cancel_force_delete - restore_statuses_before_force_delete - remove_force_delete_statuses - clear_force_delete_data - self.force_delete_date = nil - self.force_delete_start = nil - save(validate: false) - registrar.notifications.create!(text: I18n.t('force_delete_cancelled', domain_name: name)) + Domains::CancelForceDelete::CancelForceDelete.run(domain: self) end def outzone_date @@ -97,55 +51,4 @@ module Concerns::Domain::ForceDelete # rubocop:disable Metrics/ModuleLength (force_delete_date&.beginning_of_day || valid_to + Setting.expire_warning_period.days + Setting.redemption_grace_period.days) end - - private - - def calculate_soft_delete_date - years = (valid_to.to_date - Time.zone.today).to_i / 365 - soft_delete_dates(years) if years.positive? - end - - def soft_delete_dates(years) - self.force_delete_start = valid_to - years.years + 1.day - self.force_delete_date = force_delete_start + Setting.expire_warning_period.days + - Setting.redemption_grace_period.days - end - - def stop_all_pending_actions - statuses.delete(DomainStatus::PENDING_UPDATE) - statuses.delete(DomainStatus::PENDING_TRANSFER) - statuses.delete(DomainStatus::PENDING_RENEW) - statuses.delete(DomainStatus::PENDING_CREATE) - end - - def preserve_current_statuses_for_force_delete - self.statuses_before_force_delete = statuses.clone - end - - def restore_statuses_before_force_delete - self.statuses = statuses_before_force_delete - self.statuses_before_force_delete = nil - end - - def add_force_delete_statuses - statuses << DomainStatus::FORCE_DELETE - statuses << DomainStatus::SERVER_RENEW_PROHIBITED - statuses << DomainStatus::SERVER_TRANSFER_PROHIBITED - end - - def remove_force_delete_statuses - statuses.delete(DomainStatus::FORCE_DELETE) - statuses.delete(DomainStatus::SERVER_RENEW_PROHIBITED) - statuses.delete(DomainStatus::SERVER_TRANSFER_PROHIBITED) - statuses.delete(DomainStatus::CLIENT_HOLD) - end - - def allow_deletion - statuses.delete(DomainStatus::CLIENT_DELETE_PROHIBITED) - statuses.delete(DomainStatus::SERVER_DELETE_PROHIBITED) - end - - def force_delete_fast_track_start_date - Time.zone.today + Setting.expire_warning_period.days + Setting.redemption_grace_period.days - end end diff --git a/app/models/concerns/email_verifable.rb b/app/models/concerns/email_verifable.rb index dc512b2c8..d0bb6aecb 100644 --- a/app/models/concerns/email_verifable.rb +++ b/app/models/concerns/email_verifable.rb @@ -15,6 +15,10 @@ module Concerns domain: domain(billing_email)) end + def email_verification_failed? + email_verification&.failed? + end + class_methods do def domain(email) Mail::Address.new(email).domain&.downcase || 'not_found' diff --git a/app/models/concerns/job/force_delete.rb b/app/models/concerns/job/force_delete.rb deleted file mode 100644 index 316612d1e..000000000 --- a/app/models/concerns/job/force_delete.rb +++ /dev/null @@ -1,34 +0,0 @@ -module Concerns - module Job - module ForceDelete - extend ActiveSupport::Concern - - class_methods do - def start_client_hold - log_prepare_client_hold - - ::PaperTrail.request.whodunnit = "cron - #{__method__}" - - ::Domain.force_delete_scheduled.each do |domain| - proceed_client_hold(domain: domain) - end - - log_end_end_force_delete_job - end - - def proceed_client_hold(domain:) - notify_on_grace_period(domain) if domain.should_notify_on_soft_force_delete? - return unless domain.client_holdable? - - domain.statuses << DomainStatus::CLIENT_HOLD - log_start_client_hold(domain) - - domain.save(validate: false) - notify_client_hold(domain) - - log_end_end_client_hold(domain) - end - end - end - end -end diff --git a/app/models/concerns/job/force_delete_logging.rb b/app/models/concerns/job/force_delete_logging.rb deleted file mode 100644 index 8f6ee227c..000000000 --- a/app/models/concerns/job/force_delete_logging.rb +++ /dev/null @@ -1,34 +0,0 @@ -module Concerns - module Job - module ForceDeleteLogging - extend ActiveSupport::Concern - - class_methods do - def log_prepare_client_hold - return if Rails.env.test? - - STDOUT << "#{Time.zone.now.utc} - Setting client_hold to domains\n" - end - - def log_start_client_hold(domain) - return if Rails.env.test? - - STDOUT << "#{Time.zone.now.utc} DomainCron.start_client_hold: ##{domain.id} "\ - "(#{domain.name}) #{domain.changes}\n" - end - - def log_end_end_client_hold(domain) - return if Rails.env.test? - - STDOUT << "#{Time.zone.now.utc} - Successfully set client_hold on (#{domain.name})" - end - - def log_end_end_force_delete_job - return if Rails.env.test? - - STDOUT << "#{Time.zone.now.utc} - All client_hold setting are done\n" - end - end - end - end -end diff --git a/app/models/concerns/job/force_delete_notify.rb b/app/models/concerns/job/force_delete_notify.rb deleted file mode 100644 index bc291354e..000000000 --- a/app/models/concerns/job/force_delete_notify.rb +++ /dev/null @@ -1,31 +0,0 @@ -module Concerns - module Job - module ForceDeleteNotify - extend ActiveSupport::Concern - - class_methods do - def notify_client_hold(domain) - domain.registrar.notifications.create!(text: I18n.t('force_delete_set_on_domain', - domain_name: domain.name, - outzone_date: domain.outzone_date, - purge_date: domain.purge_date)) - end - - def notify_on_grace_period(domain) - domain.registrar.notifications.create!(text: I18n.t('grace_period_started_domain', - domain_name: domain.name, - date: domain.force_delete_start)) - send_mail(domain) if domain.template_name.present? - domain.update(contact_notification_sent_date: Time.zone.today) - end - - def send_mail(domain) - DomainDeleteMailer.forced(domain: domain, - registrar: domain.registrar, - registrant: domain.registrant, - template_name: domain.template_name).deliver_now - end - end - end - end -end diff --git a/app/models/contact.rb b/app/models/contact.rb index 9dc1e34a2..8a154c50c 100644 --- a/app/models/contact.rb +++ b/app/models/contact.rb @@ -333,31 +333,6 @@ class Contact < ApplicationRecord Country.new(country_code) end - # TODO: refactor, it should not allow to destroy with normal destroy, - # no need separate method - # should use only in transaction - def destroy_and_clean frame - if linked? - errors.add(:domains, :exist) - return false - end - - legal_document_data = ::Deserializers::Xml::LegalDocument.new(frame).call - - if legal_document_data - - doc = LegalDocument.create( - documentable_type: Contact, - document_type: legal_document_data[:type], - body: legal_document_data[:body] - ) - self.legal_documents = [doc] - self.legal_document_id = doc.id - self.save - end - destroy - end - def to_upcase_country_code self.ident_country_code = ident_country_code.upcase if ident_country_code self.country_code = country_code.upcase if country_code diff --git a/app/models/domain.rb b/app/models/domain.rb index 391c9c030..dc7d86da8 100644 --- a/app/models/domain.rb +++ b/app/models/domain.rb @@ -320,8 +320,8 @@ class Domain < ApplicationRecord def renew_blocking_statuses disallowed = [DomainStatus::DELETE_CANDIDATE, DomainStatus::PENDING_RENEW, DomainStatus::PENDING_TRANSFER, DomainStatus::CLIENT_RENEW_PROHIBITED, - DomainStatus::PENDING_UPDATE, DomainStatus::PENDING_DELETE, - DomainStatus::PENDING_DELETE_CONFIRMATION, DomainStatus::SERVER_RENEW_PROHIBITED] + DomainStatus::PENDING_UPDATE, DomainStatus::SERVER_RENEW_PROHIBITED, + DomainStatus::PENDING_DELETE_CONFIRMATION] (statuses & disallowed) end @@ -384,7 +384,7 @@ class Domain < ApplicationRecord end def registrant_update_confirmable?(token) - return false if (statuses & [DomainStatus::FORCE_DELETE, DomainStatus::DELETE_CANDIDATE]).any? + return false if statuses.include? DomainStatus::DELETE_CANDIDATE return false unless pending_update? return false unless registrant_verification_asked? return false unless registrant_verification_token == token @@ -418,7 +418,7 @@ class Domain < ApplicationRecord pending_delete_confirmation! save(validate: false) # should check if this did succeed - DomainDeleteConfirmEmailJob.enqueue(id) + Domains::DeleteConfirm::SendRequest.run(domain: self) end def cancel_pending_delete @@ -642,6 +642,10 @@ class Domain < ApplicationRecord DNS::DomainName.new(name) end + def contact_emails_verification_failed + contacts.select(&:email_verification_failed?)&.map(&:email)&.uniq + end + def self.to_csv CSV.generate do |csv| csv << column_names diff --git a/app/models/domain_cron.rb b/app/models/domain_cron.rb index ad64456ca..d09141d52 100644 --- a/app/models/domain_cron.rb +++ b/app/models/domain_cron.rb @@ -1,8 +1,4 @@ class DomainCron - include Concerns::Job::ForceDelete - include Concerns::Job::ForceDeleteLogging - include Concerns::Job::ForceDeleteNotify - def self.clean_expired_pendings STDOUT << "#{Time.zone.now.utc} - Clean expired domain pendings\n" unless Rails.env.test? @@ -81,4 +77,8 @@ class DomainCron STDOUT << "#{Time.zone.now.utc} - Successfully set server_hold to #{marked} of #{real} domains\n" unless Rails.env.test? marked end + + def self.start_client_hold + Domains::ClientHold::SetClientHold.run! + end end diff --git a/app/models/epp/contact.rb b/app/models/epp/contact.rb index 6867b037d..50ebac065 100644 --- a/app/models/epp/contact.rb +++ b/app/models/epp/contact.rb @@ -30,12 +30,13 @@ class Epp::Contact < Contact at end - def new(frame, registrar) + def new(frame, registrar, epp: true) return super if frame.blank? + attrs = epp ? attrs_from(frame, new_record: true) : frame super( - attrs_from(frame, new_record: true).merge( - code: frame.css('id').text, + attrs.merge( + code: epp ? frame.css('id').text : frame[:id], registrar: registrar ) ) diff --git a/app/models/epp/domain.rb b/app/models/epp/domain.rb index 530e54a0f..7fb23a6e9 100644 --- a/app/models/epp/domain.rb +++ b/app/models/epp/domain.rb @@ -417,7 +417,7 @@ class Epp::Domain < Domain if statuses.include?(x) to_destroy << x else - add_epp_error('2303', 'status', x, [:domain_statuses, :not_found]) + add_epp_error('2303', 'status', x, %i[statuses not_found]) end end @@ -432,7 +432,7 @@ class Epp::Domain < Domain frame.css('status').each do |x| unless DomainStatus::CLIENT_STATUSES.include?(x['s']) - add_epp_error('2303', 'status', x['s'], [:domain_statuses, :not_found]) + add_epp_error('2303', 'status', x['s'], %i[statuses not_found]) next end @@ -612,6 +612,7 @@ class Epp::Domain < Domain statuses.delete(DomainStatus::SERVER_HOLD) statuses.delete(DomainStatus::EXPIRED) statuses.delete(DomainStatus::SERVER_UPDATE_PROHIBITED) + cancel_pending_delete save end diff --git a/app/models/epp/response/result/code.rb b/app/models/epp/response/result/code.rb index 1be4a3f7c..10edf0a35 100644 --- a/app/models/epp/response/result/code.rb +++ b/app/models/epp/response/result/code.rb @@ -30,6 +30,7 @@ module Epp data_management_policy_violation: 2308, command_failed: 2400, authentication_error_server_closing_connection: 2501, + session_limit_exceeded_server_closing_connection: 2502, }.freeze private_constant :KEY_TO_VALUE @@ -59,6 +60,7 @@ module Epp 2308 => 'Data management policy violation', 2400 => 'Command failed', 2501 => 'Authentication error; server closing connection', + 2502 => 'Session limit exceeded; server closing connection', }.freeze private_constant :DEFAULT_DESCRIPTIONS diff --git a/app/models/epp_session.rb b/app/models/epp_session.rb index f1b641aa0..a6fb97ed2 100644 --- a/app/models/epp_session.rb +++ b/app/models/epp_session.rb @@ -6,19 +6,26 @@ class EppSession < ApplicationRecord class_attribute :timeout self.timeout = (ENV['epp_session_timeout_seconds'] || 300).to_i.seconds + class_attribute :sessions_per_registrar + self.sessions_per_registrar = (ENV['epp_sessions_per_registrar'] || 4).to_i + alias_attribute :last_access, :updated_at - def self.limit_per_registrar - 4 - end + scope :not_expired, + lambda { + where(':now <= (updated_at + interval :interval)', now: Time.zone.now, interval: interval) + } def self.limit_reached?(registrar) - count = where(user_id: registrar.api_users.ids).where('updated_at >= ?', Time.zone.now - 1.second).count - count >= limit_per_registrar + count = where(user_id: registrar.api_users.ids).not_expired.count + count >= sessions_per_registrar + end + + def self.interval + "#{timeout.parts.first.second} #{timeout.parts.first.first}" end def self.expired - interval = "#{timeout.parts.first.second} #{timeout.parts.first.first}" where(':now > (updated_at + interval :interval)', now: Time.zone.now, interval: interval) end diff --git a/app/models/registrant_user.rb b/app/models/registrant_user.rb index c0addb5cd..aa41ccff8 100644 --- a/app/models/registrant_user.rb +++ b/app/models/registrant_user.rb @@ -54,14 +54,25 @@ class RegistrantUser < User username.split.second end + def update_related_contacts + contacts = Contact.where(ident: ident, ident_country_code: country.alpha2) + .where('UPPER(name) != UPPER(?)', username) + + contacts.each do |contact| + contact.update(name: username) + action = actions.create!(contact: contact, operation: :update) + contact.registrar.notify(action) + end + end + class << self def find_or_create_by_api_data(user_data = {}) return false unless user_data[:ident] return false unless user_data[:first_name] return false unless user_data[:last_name] - user_data.each_value { |v| v.upcase! if v.is_a?(String) } user_data[:country_code] ||= 'EE' + %i[ident country_code].each { |f| user_data[f].upcase! if user_data[f].is_a?(String) } find_or_create_by_user_data(user_data) end @@ -91,6 +102,7 @@ class RegistrantUser < User user.username = "#{user_data[:first_name]} #{user_data[:last_name]}" user.save + user.update_related_contacts user end end diff --git a/app/models/registrar.rb b/app/models/registrar.rb index e2ffcbfd4..149bb7541 100644 --- a/app/models/registrar.rb +++ b/app/models/registrar.rb @@ -137,15 +137,18 @@ class Registrar < ApplicationRecord def api_ip_white?(ip) return true unless Setting.api_ip_whitelist_enabled - white_ips.api.pluck(:ipv4, :ipv6).flatten.include?(ip) + + white_ips.api.include_ip?(ip) end # Audit log is needed, therefore no raw SQL - def replace_nameservers(hostname, new_attributes) + def replace_nameservers(hostname, new_attributes, domains: []) transaction do domain_list = [] nameservers.where(hostname: hostname).find_each do |original_nameserver| + next unless domains.include?(original_nameserver.domain.name_puny) || domains.empty? + new_nameserver = Nameserver.new new_nameserver.domain = original_nameserver.domain new_nameserver.attributes = new_attributes diff --git a/app/models/white_ip.rb b/app/models/white_ip.rb index 303ff5886..417633b12 100644 --- a/app/models/white_ip.rb +++ b/app/models/white_ip.rb @@ -2,8 +2,8 @@ class WhiteIp < ApplicationRecord include Versions belongs_to :registrar - validates :ipv4, format: { with: /\A(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\z/, allow_blank: true } - validates :ipv6, format: { with: /(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]).){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]).){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))/, allow_blank: true } + validate :valid_ipv4? + validate :valid_ipv6? validate :validate_ipv4_and_ipv6 def validate_ipv4_and_ipv6 @@ -11,6 +11,22 @@ class WhiteIp < ApplicationRecord errors.add(:base, I18n.t(:ipv4_or_ipv6_must_be_present)) end + def valid_ipv4? + return if ipv4.blank? + + IPAddr.new(ipv4, Socket::AF_INET) + rescue StandardError => _e + errors.add(:ipv4, :invalid) + end + + def valid_ipv6? + return if ipv6.blank? + + IPAddr.new(ipv6, Socket::AF_INET6) + rescue StandardError => _e + errors.add(:ipv6, :invalid) + end + API = 'api' REGISTRAR = 'registrar' INTERFACES = [API, REGISTRAR] @@ -23,8 +39,37 @@ class WhiteIp < ApplicationRecord end class << self + # rubocop:disable Style/CaseEquality + # rubocop:disable Metrics/AbcSize def include_ip?(ip) - where('ipv4 = :ip OR ipv6 = :ip', ip: ip).any? + return false if ip.blank? + + where(id: ids_including(ip)).any? + end + + def ids_including(ip) + ipv4 = ipv6 = [] + if check_ip4(ip).present? + ipv4 = select { |white_ip| IPAddr.new(white_ip.ipv4, Socket::AF_INET) === check_ip4(ip) } + end + if check_ip6(ip).present? + ipv6 = select { |white_ip| IPAddr.new(white_ip.ipv6, Socket::AF_INET6) === check_ip6(ip) } + end + (ipv4 + ipv6).pluck(:id).flatten.uniq + end + # rubocop:enable Style/CaseEquality + # rubocop:enable Metrics/AbcSize + + def check_ip4(ip) + IPAddr.new(ip, Socket::AF_INET) + rescue StandardError => _e + nil + end + + def check_ip6(ip) + IPAddr.new(ip, Socket::AF_INET6) + rescue StandardError => _e + nil end end end diff --git a/app/presenters/domain_presenter.rb b/app/presenters/domain_presenter.rb index 0915722b1..bcbd5d600 100644 --- a/app/presenters/domain_presenter.rb +++ b/app/presenters/domain_presenter.rb @@ -52,6 +52,10 @@ class DomainPresenter end end + def contact_emails_verification_failed + domain.contact_emails_verification_failed.join(', ') + end + def remove_registry_lock_btn return unless domain.locked_by_registrant? diff --git a/app/services/registrant_change.rb b/app/services/registrant_change.rb index 35b631fb6..fdee7654a 100644 --- a/app/services/registrant_change.rb +++ b/app/services/registrant_change.rb @@ -5,6 +5,7 @@ class RegistrantChange end def confirm + Dispute.close_by_domain(@domain.name) if @domain.disputed? notify_registrant end diff --git a/app/views/admin/domains/_force_delete_dialog.html.erb b/app/views/admin/domains/_force_delete_dialog.html.erb index a76c14edd..932a3f75c 100644 --- a/app/views/admin/domains/_force_delete_dialog.html.erb +++ b/app/views/admin/domains/_force_delete_dialog.html.erb @@ -33,12 +33,6 @@ -
- -
- <%= select_tag 'template_name', options_for_select(templates), class: 'form-control' %> -
-
<% end %> <%= render 'form' %> -<%= render 'force_delete_dialog', domain: @domain, templates: force_delete_templates %> +<%= render 'force_delete_dialog', domain: @domain %> diff --git a/app/views/epp/contacts/info.xml.builder b/app/views/epp/contacts/info.xml.builder index 1945e7def..6ce0a17f4 100644 --- a/app/views/epp/contacts/info.xml.builder +++ b/app/views/epp/contacts/info.xml.builder @@ -14,11 +14,15 @@ xml.epp_head do end xml.tag!('contact:postalInfo', type: 'int') do - xml.tag!('contact:name', @contact.name) + if can? :view_full_info, @contact, @password + xml.tag!('contact:name', @contact.name) + else + xml.tag!('contact:name', 'No access') + end if can? :view_full_info, @contact, @password xml.tag!('contact:org', @contact.org_name) if @contact.org_name.present? - if address_processing? + if Contact.address_processing? xml.tag!('contact:addr') do xml.tag!('contact:street', @contact.street) xml.tag!('contact:city', @contact.city) @@ -31,7 +35,7 @@ xml.epp_head do else xml.tag!('contact:org', 'No access') - if address_processing? + if Contact.address_processing? xml.tag!('contact:addr') do xml.tag!('contact:street', 'No access') xml.tag!('contact:city', 'No access') diff --git a/app/views/mailers/domain_delete_mailer/forced/invalid_email.html.erb b/app/views/mailers/domain_delete_mailer/forced/invalid_email.html.erb new file mode 100644 index 000000000..817538afd --- /dev/null +++ b/app/views/mailers/domain_delete_mailer/forced/invalid_email.html.erb @@ -0,0 +1,47 @@ +

Lugupeetud domeeni <%= @domain.name %> registreerija/halduskontakt

+ +

Eesti Interneti Sihtasutusele (EIS) on saanud teatavaks, et domeeni <%= @domain.name %> kontaktandmed on puudulikud - eposti aadress <%= @domain.contact_emails_verification_failed %>.

+ +

Et see olukord on vastuolus .ee domeenireeglitega algatas EIS <%= @delete_period_length %> päeva pikkuse kustutusmenetluse. Menetluse käigus on domeen <%= @expire_warning_period %> esimest päeva internetis kättesaadav.

+ +

Andmete parandamiseks pöörduge palun oma registripidaja <%= @registrar.name %> poole või isiklike ja oma ettevõtte andmete puhul registreerija portaali.

+ +

Kui kontaktandmed ei ole <%= @delete_period_length %> päeva jooksul parandatud, läheb domeen <%= @domain.name %> <%= @domain.force_delete_date %> domeenioksjonile .ee oksjonikeskkonda. Juhul kui domeenile <%= @domain.name %> ei tehta oksjonil 24h möödudes pakkumist, domeen vabaneb ja on registreerimiseks vabalt kättesaadav kõigile huvilistele. Muude võimalike oksjoni tulemuste kohta loe siit.

+ +

Lisaküsimuste korral võtke palun ühendust oma registripidajaga:

+<%= render 'mailers/shared/registrar/registrar.et.html', registrar: @registrar %> + +<%= render 'mailers/shared/signatures/signature.et.html' %> + +
+ +

Dear registrant/administrative contact of .ee domain,

+ +

Estonian Internet Foundation has learned that contact data of the domain <%= @domain.name %> s invalid - email(s) <%= @domain.contact_emails_verification_failed %>.

+ +

Since this is a violation of Estonian domain regulations, <%= @delete_period_length %>-day deletion process has started for the <%= @domain.name %> domain. For the first <%= @expire_warning_period %> days the domain will remain available on the Internet during the deletion process.

+ +

Please, contact your registrar <%= @registrar.name %> with updated contact data, or in case of your personal or business data use .ee portal for registrants

+ +

If the data is not fixed within <%= @delete_period_length %> days, the domain <%= @domain.name %> will go to domain auction on <%= @domain.force_delete_date %> in the .ee auction environment. If no offer is made for the domain <%= @domain.name %> at auction within 24 hours, the domain will be released and made freely available for registration to anyone interested on a first-come, first-served basis. Read more about other potential auction results here.

+ +

Should you have additional questions, please contact your registrar:

+<%= render 'mailers/shared/registrar/registrar.en.html', registrar: @registrar %> + +<%= render 'mailers/shared/signatures/signature.en.html' %> +
+ +

Уважаемый регистрант/административный контакт домена .ee

+ +

Целевому учреждению Eesti Internet (EIS) стало известно, что контактные данные домена <%= @registrant.reg_no %> неверны - электронная почта <%= @domain.contact_emails_verification_failed %>.

+ +

Так как это является нарушением Правил домена .ee, <%= @delete_period_length %>-дневный процесс удаления начат для доменного имени <%= @domain.name %>. В течение первых <%= @expire_warning_period %> дней домен будет доступен в интернете.

+ +

Для уточнения контактных данных, пожалуйста, свяжитесь с регистратором <%= @registrar.name %>, либо воспользуйтесь порталом для регистрантов

+ +

Если контактные данные не будут исправлены в течение <%= @delete_period_length %> дней, домен <%= @domain.name %> отправится <%= @domain.force_delete_date %> на доменный аукцион в аукционной среде.ee. Если в течение 24 часов в отношении домена <%= @domain.name %> е поступит предложений, домен освободится и станет доступным для всех желающих по принципу «кто раньше». О других возможных результатах аукциона читайте здесь.

+ +

В случае возникновения дополнительных вопросов свяжитесь, пожалуйста, со своим регистратором: +<%= render 'mailers/shared/registrar/registrar.ru.html', registrar: @registrar %>

+ +<%= render 'mailers/shared/signatures/signature.ru.html' %> diff --git a/app/views/mailers/domain_delete_mailer/forced/invalid_email.text.erb b/app/views/mailers/domain_delete_mailer/forced/invalid_email.text.erb new file mode 100644 index 000000000..8d2fc58ce --- /dev/null +++ b/app/views/mailers/domain_delete_mailer/forced/invalid_email.text.erb @@ -0,0 +1,47 @@ +

Lugupeetud domeeni <%= @domain.name %> registreerija/halduskontakt

+ +

Eesti Interneti Sihtasutusele (EIS) on saanud teatavaks, et domeeni <%= @domain.name %> kontaktandmed on puudulikud - eposti aadress <%= @domain.contact_emails_verification_failed %>

+ +

Et see olukord on vastuolus .ee domeenireeglitega algatas EIS <%= @delete_period_length %> päeva pikkuse kustutusmenetluse. Menetluse käigus on domeen <%= @expire_warning_period %> esimest päeva internetis kättesaadav.

+ +

Andmete parandamiseks pöörduge palun oma registripidaja <%= @registrar.name %> poole või isiklike ja oma ettevõtte andmete puhul registreerija portaali.

+ +

Kui kontaktandmed ei ole <%= @delete_period_length %> päeva jooksul parandatud, läheb domeen <%= @domain.name %> <%= @domain.force_delete_date %> domeenioksjonile .ee oksjonikeskkonda. Juhul kui domeenile <%= @domain.name %> ei tehta oksjonil 24h möödudes pakkumist, domeen vabaneb ja on registreerimiseks vabalt kättesaadav kõigile huvilistele. Muude võimalike oksjoni tulemuste kohta loe siit.

+ +

Lisaküsimuste korral võtke palun ühendust oma registripidajaga:

+<%= render 'mailers/shared/registrar/registrar.et.html', registrar: @registrar %> + +<%= render 'mailers/shared/signatures/signature.et.html' %> + +
+ +

Dear registrant/administrative contact of .ee domain,

+ +

Estonian Internet Foundation has learned that contact data of the domain <%= @domain.name %> s invalid - email(s) <%= @domain.contact_emails_verification_failed %>.

+ +

Since this is a violation of Estonian domain regulations, <%= @delete_period_length %>-day deletion process has started for the <%= @domain.name %> domain. For the first <%= @expire_warning_period %> days the domain will remain available on the Internet during the deletion process.

+ +

Please, contact your registrar <%= @registrar.name %> with updated contact data, or in case of your personal or business data use .ee portal for registrants

+ +

If the data is not fixed within <%= @delete_period_length %> days, the domain <%= @domain.name %> will go to domain auction on <%= @domain.force_delete_date %> in the .ee auction environment. If no offer is made for the domain <%= @domain.name %> at auction within 24 hours, the domain will be released and made freely available for registration to anyone interested on a first-come, first-served basis. Read more about other potential auction results here.

+ +

Should you have additional questions, please contact your registrar:

+<%= render 'mailers/shared/registrar/registrar.en.html', registrar: @registrar %> + +<%= render 'mailers/shared/signatures/signature.en.html' %> +
+ +

Уважаемый регистрант/административный контакт домена .ee

+ +

Целевому учреждению Eesti Internet (EIS) стало известно, что контактные данные домена <%= @registrant.reg_no %> неверны - электронная почта <%= @domain.contact_emails_verification_failed %>.

+ +

Так как это является нарушением Правил домена .ee, <%= @delete_period_length %>-дневный процесс удаления начат для доменного имени <%= @domain.name %>. В течение первых <%= @expire_warning_period %> дней домен будет доступен в интернете.

+ +

Для уточнения контактных данных, пожалуйста, свяжитесь с регистратором <%= @registrar.name %>, либо воспользуйтесь порталом для регистрантов

+ +

Если контактные данные не будут исправлены в течение <%= @delete_period_length %> дней, домен <%= @domain.name %> отправится <%= @domain.force_delete_date %> на доменный аукцион в аукционной среде.ee. Если в течение 24 часов в отношении домена <%= @domain.name %> е поступит предложений, домен освободится и станет доступным для всех желающих по принципу «кто раньше». О других возможных результатах аукциона читайте здесь.

+ +

В случае возникновения дополнительных вопросов свяжитесь, пожалуйста, со своим регистратором: + <%= render 'mailers/shared/registrar/registrar.ru.html', registrar: @registrar %>

+ +<%= render 'mailers/shared/signatures/signature.ru.html' %> diff --git a/app/views/mailers/domain_expire_mailer/expired_soft.html.erb b/app/views/mailers/domain_expire_mailer/expired_soft.html.erb new file mode 100644 index 000000000..0bcfc6acd --- /dev/null +++ b/app/views/mailers/domain_expire_mailer/expired_soft.html.erb @@ -0,0 +1,48 @@ +

Domeen <%= @domain.name %> on aegunud ning suunatud kustutusmenetlusse kuna oleme tuvastanud domeeniga seotud kontaktides olulisi puudusi.

+ +

Lugupeetud .ee domeeni registreerija/halduskontakt

+ +

Domeeninimi <%= @domain.name %> on aegunud ja ei ole alates <%= @domain.on_hold_date %> internetis kättesaadav. Domeeniga on seotud puudulikke kontakti objekte, millest tulenevalt on Eesti Interneti SA blokeerinud domeeni pikendamise ja registripidaja vahetuse, kuniks kontaktandmed korrastatakse. Andmete korrastamiseks ja registreeringu pikendamiseks pöörduge palun oma registripidaja poole.

+ +

<%= @domain.name %> pikendamata jätmisel domeen kustub ja läheb <%= @domain.delete_date %> oksjonile .ee oksjonikeskkonda. Domeenioksjonite kohta loe lähemalt siit.

+ +

Domeeni <%= @domain.name %> registripidaja:

+<%= render 'mailers/shared/registrar/registrar.et.html', registrar: @registrar %> + +

Ülevaate kõikidest endaga seotud domeenidest saate registreerija portaalist.

+ +<%= render 'mailers/shared/signatures/signature.et.html' %> + +
+ +

Domain <%= @domain.name %> has expired

+ +

Dear registrant/administrative contact of .ee domain,

+ +

The domain name <%= @domain.name %> has expired and since <%= @domain.on_hold_date %> is no longer available on the Internet. Domain registration has invalid contact data. Renewal and registrar transfer is therefore prohibited until contact data has been fixed. To correct the data and renew your domain registration, please contact your registrar.

+ +

If you do not renew the <%= @domain.name %> domain registration, it is deleted and put on auction to .ee domain auction environment at <%= @domain.delete_date %>. Read more about .ee domain auctions here.

+ +

Registrar of the <%= @domain.name %>:

+<%= render 'mailers/shared/registrar/registrar.en.html', registrar: @registrar %> + +

You can find an overview of all your domains at the registrant's portal.

+ +<%= render 'mailers/shared/signatures/signature.en.html' %> + +
+ +

Срок действия домена <%= @domain.name %> истек

+ +

Уважаемый регистрант/административный контакт домена .ee

+ +

Срок действия доменного имени <%= @domain.name %> истек, и с <%= @domain.on_hold_date %> оно больше не доступно в интернете. У домена указаны неверные контактные данные. Обновление и перенос к другому регистратору заблокированы до исправления контактных данных. Для исправления контактных данных и обновления регистрации вашего домена, пожалуйста, обратитесь в вашему регистратору.

+ +

Если доменное имя не продлено, домен <%= @domain.name %> будет удален и <%= @domain.delete_date %> идет на аукцион в .ee среду аукциона. О проведении доменных аукционов читайте здесь.

+ +

Pегистратор домена <%= @domain.name %>:

+<%= render 'mailers/shared/registrar/registrar.ru.html', registrar: @registrar %> + +

Обзор всех связанных с вами доменов можете получить на портале регистратора.

+ +<%= render 'mailers/shared/signatures/signature.ru.html' %> diff --git a/app/views/mailers/domain_expire_mailer/expired_soft.text.erb b/app/views/mailers/domain_expire_mailer/expired_soft.text.erb new file mode 100644 index 000000000..7be32f73b --- /dev/null +++ b/app/views/mailers/domain_expire_mailer/expired_soft.text.erb @@ -0,0 +1,48 @@ +Domeen <%= @domain.name %> on aegunud ning suunatud kustutusmenetlusse kuna oleme tuvastanud domeeniga seotud kontaktides olulisi puudusi. + +Lugupeetud .ee domeeni registreerija/halduskontakt + +Domeeninimi <%= @domain.name %> on aegunud ja ei ole alates <%= @domain.on_hold_date %> internetis kättesaadav. Domeeniga on seotud puudulikke kontakti objekte, millest tulenevalt on Eesti Interneti SA blokeerinud domeeni pikendamise ja registripidaja vahetuse, kuniks kontaktandmed korrastatakse. Andmete korrastamiseks ja registreeringu pikendamiseks pöörduge palun oma registripidaja poole. + +<%= @domain.name %> pikendamata jätmisel domeen kustub ja läheb <%= @domain.delete_date %> oksjonile .ee oksjonikeskkonda. Domeenioksjonite kohta loe lähemalt siit https://www.internet.ee/domeenioksjonid. + +Domeeni <%= @domain.name %> registripidaja: +<%= render 'mailers/shared/registrar/registrar.et.html', registrar: @registrar %> + +Ülevaate kõikidest endaga seotud domeenidest saate registreerija portaalist https://registrant.internet.ee/registrant/. + +<%= render 'mailers/shared/signatures/signature.et.html' %> + +-------------------------------------- + +Domain <%= @domain.name %> has expired + +Dear registrant/administrative contact of .ee domain, + +The domain name <%= @domain.name %> has expired and since <%= @domain.on_hold_date %> is no longer available on the Internet. Domain registration has invalid contact data. Renewal and registrar transfer is therefore prohibited until contact data has been fixed. To correct the data and renew your domain registration, please contact your registrar. + +If you do not renew the <%= @domain.name %> domain registration, it is deleted and put on auction to .ee domain auction environment at <%= @domain.delete_date %>. Read more about .ee domain auctions here https://www.internet.ee/domains/auction-environment-user-agreement#3-terms-and-conditions-for-participation-in-the-auction-of-the-auction-environment. + +Registrar of the <%= @domain.name %>: +<%= render 'mailers/shared/registrar/registrar.en.html', registrar: @registrar %> + +You can find an overview of all your domains at the registrant's portal https://registrant.internet.ee/registrant/. + +<%= render 'mailers/shared/signatures/signature.en.html' %> + +-------------------------------------- + +Срок действия домена <%= @domain.name %> истек + +Уважаемый регистрант/административный контакт домена .ee + +Срок действия доменного имени <%= @domain.name %> истек, и с <%= @domain.on_hold_date %> оно больше не доступно в интернете. У домена указаны неверные контактные данные. Обновление и перенос к другому регистратору заблокированы до исправления контактных данных. Для исправления контактных данных и обновления регистрации вашего домена, пожалуйста, обратитесь в вашему регистратору. + +Если доменное имя не продлено, домен <%= @domain.name %> будет удален и <%= @domain.delete_date %> идет на аукцион в .ee среду аукциона. О проведении доменных аукционов читайте здесь https://www.internet.ee/domeny/dogovor-pol-zovatelya-aukcionnoj-sredy#3-usloviya-uchastiya-v-aukcione. + +Pегистратор домена <%= @domain.name %>: +<%= render 'mailers/shared/registrar/registrar.ru.html', registrar: @registrar %> + +Обзор всех связанных с вами доменов можете получить на портале регистратора https://registrant.internet.ee/registrant/. + +<%= render 'mailers/shared/signatures/signature.ru.html' %> diff --git a/app/views/registrar/bulk_change/_nameserver_form.html.erb b/app/views/registrar/bulk_change/_nameserver_form.html.erb index e3f4a0214..782076358 100644 --- a/app/views/registrar/bulk_change/_nameserver_form.html.erb +++ b/app/views/registrar/bulk_change/_nameserver_form.html.erb @@ -1,4 +1,4 @@ -<%= form_tag registrar_nameservers_path, method: :patch, class: 'form-horizontal' do %> +<%= form_tag registrar_nameservers_path, multipart: true, method: :patch, class: 'form-horizontal' do %> <%= render 'registrar/domain_transfers/form/api_errors' %>
@@ -44,6 +44,16 @@
+
+
+ <%= label_tag 'List of domains' %> +
+
+ <%= file_field_tag :puny_file, required: false, accept: 'text/csv' %> + CSV format, must have domain_name field. List of domains that nameserver change should be scoped to. +
+
+