diff --git a/.codeclimate.yml b/.codeclimate.yml index 2bc90b200..d079d891f 100644 --- a/.codeclimate.yml +++ b/.codeclimate.yml @@ -20,6 +20,9 @@ plugins: channel: eslint-5 fixme: enabled: true + checks: + TODO: + enabled: false rubocop: enabled: true channel: rubocop-0-74 diff --git a/.github/workflows/ruby.yml b/.github/workflows/ruby.yml new file mode 100644 index 000000000..eea0ccc03 --- /dev/null +++ b/.github/workflows/ruby.yml @@ -0,0 +1,117 @@ +name: Github Testing +on: [push] + + +jobs: + test: + services: + postgres: + image: postgres:12 + ports: ["5432:5432"] + env: + POSTGRES_PASSWORD: password + POSTGRES_USERNAME: postgres + options: --health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 5 + + strategy: + fail-fast: false + matrix: + os: [ubuntu-18.04] + ruby: [2.6, 2.7 ] + runs-on: ${{ matrix.os }} + continue-on-error: ${{ endsWith(matrix.ruby, 'head') || matrix.ruby == 'debug' }} + steps: + + - uses: actions/checkout@v2 + - uses: ruby/setup-ruby@v1 + with: + ruby-version: ${{ matrix.ruby }} + bundler-cache: true # runs 'bundle install' and caches installed gems automatically + - name: config bundler + run: | + bundle config set without 'development staging production' + bundle config set deployment '[secure]' + bundle env + head -n1 $(which bundle) + + - name: Set ENV for codeclimate (pull_request) + run: | + git fetch --no-tags --prune --depth=1 origin +refs/heads/$GITHUB_HEAD_REF:refs/remotes/origin/$GITHUB_HEAD_REF + echo "GIT_BRANCH=$GITHUB_HEAD_REF" >> $GITHUB_ENV + echo "GIT_COMMIT_SHA=$(git rev-parse origin/$GITHUB_HEAD_REF)" >> $GITHUB_ENV + if: github.event_name == 'pull_request' + + - name: Set ENV for codeclimate (push) + run: | + echo "GIT_BRANCH=$GITHUB_REF" >> $GITHUB_ENV + echo "GIT_COMMIT_SHA=$GITHUB_SHA" >> $GITHUB_ENV + if: github.event_name == 'push' + + - name: Prepare CodeClimate + env: + CC_TEST_REPORTER_ID: ${{ secrets.CC_TEST_REPORTER_ID }} + run: | + curl -LSs 'https://codeclimate.com/downloads/test-reporter/test-reporter-latest-linux-amd64' >./cc-test-reporter; + chmod +x ./cc-test-reporter + ./cc-test-reporter before-build + + - name: Run Tests + env: + PG_DATABASE: postgres + PG_HOST: localhost + PG_USER: postgres + PG_PASSWORD: password + PG_PORT: ${{ job.services.postgres.ports[5432] }} + RAILS_ENV: test + COVERAGE: true + DISABLE_SPRING: 1 + run: | + cp config/application.yml.sample config/application.yml + cp config/database_travis.yml config/database.yml + echo "openssl_config_path: 'test/fixtures/files/test_ca/openssl.cnf'" >> config/application.yml + echo "crl_dir: 'test/fixtures/files/test_ca/crl'" >> config/application.yml + echo "crl_path: 'test/fixtures/files/test_ca/crl/crl.pem'" >> config/application.yml + echo "ca_cert_path: 'test/fixtures/files/test_ca/certs/ca.crt.pem'" >> config/application.yml + echo "ca_key_path: 'test/fixtures/files/test_ca/private/ca.key.pem'" >> config/application.yml + echo "ca_key_password: 'password'" >> config/application.yml + bundle exec rake db:setup:all + bundle exec rails test test/* + - name: Save coverage + run: ./cc-test-reporter format-coverage --output coverage/codeclimate.${{ matrix.ruby }}.json + + - uses: actions/upload-artifact@v1 + with: + name: coverage-${{ matrix.ruby }} + path: coverage/codeclimate.${{ matrix.ruby }}.json + + upload_coverage: + runs-on: ubuntu-18.04 + + env: + CC_TEST_REPORTER_ID: ${{ secrets.CC_TEST_REPORTER_ID }} + CC_TEST_REPORTER_URL: https://codeclimate.com/downloads/test-reporter/test-reporter-latest-linux-amd64 + + needs: test + + steps: + - name: Download test coverage reporter + run: curl -L $CC_TEST_REPORTER_URL > cc-test-reporter + + - name: Give test coverage reporter executable permissions + run: chmod +x cc-test-reporter + + - uses: actions/download-artifact@v1 + with: + name: coverage-2.6 + path: coverage + + - uses: actions/download-artifact@v1 + with: + name: coverage-2.7 + path: coverage + + - name: Aggregate & upload results to Code Climate + run: | + ./cc-test-reporter sum-coverage coverage/codeclimate.*.json + ./cc-test-reporter upload-coverage + diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 23d4ab6b1..000000000 --- a/.travis.yml +++ /dev/null @@ -1,35 +0,0 @@ -language: ruby -cache: bundler -env: - - DB=postgresql -before_install: - - "wget -N http://chromedriver.storage.googleapis.com/2.43/chromedriver_linux64.zip -P ~/" - - "unzip ~/chromedriver_linux64.zip -d ~/" - - "rm ~/chromedriver_linux64.zip" - - "sudo mv -f ~/chromedriver /usr/local/share/" - - "sudo chmod +x /usr/local/share/chromedriver" - - "sudo ln -s /usr/local/share/chromedriver /usr/local/bin/chromedriver" - - "bundle config set without 'development staging production'" - - "bundle config set deployment '[secure]'" -before_script: - - "cp config/application.yml.sample config/application.yml" - - "echo \"openssl_config_path: 'test/fixtures/files/test_ca/openssl.cnf'\" >> config/application.yml" - - "echo \"crl_dir: 'test/fixtures/files/test_ca/crl'\" >> config/application.yml" - - "echo \"crl_path: 'test/fixtures/files/test_ca/crl/crl.pem'\" >> config/application.yml" - - "echo \"ca_cert_path: 'test/fixtures/files/test_ca/certs/ca.crt.pem'\" >> config/application.yml" - - "echo \"ca_key_path: 'test/fixtures/files/test_ca/private/ca.key.pem'\" >> config/application.yml" - - "echo \"ca_key_password: 'password'\" >> config/application.yml" - - "cp config/database_travis.yml config/database.yml" - - "bundle exec rake db:setup:all" - - "curl -L https://codeclimate.com/downloads/test-reporter/test-reporter-latest-linux-amd64 > ./cc-test-reporter" - - "chmod +x ./cc-test-reporter" - - "./cc-test-reporter before-build" -after_script: - - "./cc-test-reporter after-build --exit-code $TRAVIS_TEST_RESULT" -script: - - "bundle exec rails test test/*" -services: - - postgresql -addons: - postgresql: "9.4" - chrome: stable diff --git a/CHANGELOG.md b/CHANGELOG.md index ebbc3d905..e9135d83f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,42 @@ +23.12.2020 +* fix for REPP logging and registrar portal communication [#1782](https://github.com/internetee/registry/pull/1782) + +22.12.2020 +* SSL CA verification fix for Bulk renew [#1778](https://github.com/internetee/registry/pull/1778) + +21.12.2020 +* Bulk renew for REPP and registrar [#1763](https://github.com/internetee/registry/issues/1763) + +17.12.2020 +* New API for registering bounced emails [#1687](https://github.com/internetee/registry/pull/1687) + +16.12.2020 +* Refactored domain delete confirmation for interactors [#1769](https://github.com/internetee/registry/issues/1769) + +15.12.2020 +* Improved logic for domain list request in registrant API [#1750](https://github.com/internetee/registry/pull/1750) +* Refactored Whois update job for interactors [#1771](https://github.com/internetee/registry/issues/1771) + +14.12.2020 +* Refactored domain cron jobs for interactors [#1767](https://github.com/internetee/registry/issues/1767) + +09.12.2020 +* Refactored domain update confirm for interactors [#1760](https://github.com/internetee/registry/issues/1760) + +08.12.2020 +* Replaced Travis-CI with GitHub Actions [#1746](https://github.com/internetee/registry/pull/1746) +* Refactored domain delete for interactors [#1755](https://github.com/internetee/registry/issues/1755) + +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) diff --git a/Gemfile b/Gemfile index 7ae8d4ef6..9bbcba254 100644 --- a/Gemfile +++ b/Gemfile @@ -73,15 +73,11 @@ gem 'e_invoice', github: 'internetee/e_invoice', branch: :master gem 'lhv', github: 'internetee/lhv', branch: 'master' gem 'domain_name' gem 'haml', '~> 5.0' +gem 'rexml' gem 'wkhtmltopdf-binary', '~> 0.12.5.1' gem 'directo', github: 'internetee/directo', branch: 'master' -group :development do - # deploy - gem 'listen', '3.2.1' - gem 'mina', '0.3.1' # for fast deployment -end group :development, :test do gem 'pry', '0.10.1' diff --git a/Gemfile.lock b/Gemfile.lock index 72c716aae..2a0bb55b1 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -250,9 +250,6 @@ GEM kaminari-core (= 1.2.1) kaminari-core (1.2.1) libxml-ruby (3.2.0) - listen (3.2.1) - rb-fsevent (~> 0.10, >= 0.10.3) - rb-inotify (~> 0.9, >= 0.9.10) logger (1.4.2) loofah (2.7.0) crass (~> 1.0.2) @@ -266,9 +263,6 @@ GEM mime-types-data (~> 3.2015) mime-types-data (3.2020.0512) mimemagic (0.3.5) - mina (0.3.1) - open4 (~> 1.3.4) - rake mini_mime (1.0.2) mini_portile2 (2.4.0) minitest (5.14.2) @@ -296,7 +290,6 @@ GEM omniauth-rails_csrf_protection (0.1.2) actionpack (>= 4.2) omniauth (>= 1.3.1) - open4 (1.3.4) openid_connect (1.2.0) activemodel attr_required (>= 1.0.0) @@ -370,9 +363,6 @@ GEM activesupport (>= 5.2.1) i18n polyamorous (= 2.3.2) - rb-fsevent (0.10.4) - rb-inotify (0.10.1) - ffi (~> 1.0) rbtree3 (0.6.0) regexp_parser (1.8.0) request_store (1.5.0) @@ -385,6 +375,7 @@ GEM http-cookie (>= 1.0.2, < 2.0) mime-types (>= 1.16, < 4.0) netrc (~> 0.8) + rexml (3.2.4) ruby2_keywords (0.0.2) rubyzip (2.3.0) sass-rails (6.0.0) @@ -519,8 +510,6 @@ DEPENDENCIES jquery-ui-rails (= 5.0.5) kaminari lhv! - listen (= 3.2.1) - mina (= 0.3.1) minitest (~> 5.14) money-rails nokogiri @@ -537,6 +526,7 @@ DEPENDENCIES rails (~> 6.0) ransack (~> 2.3) rest-client + rexml sass-rails select2-rails (= 3.5.9.3) selectize-rails (= 0.12.1) diff --git a/app/controllers/admin/bounced_mail_addresses_controller.rb b/app/controllers/admin/bounced_mail_addresses_controller.rb new file mode 100644 index 000000000..1c59acaa4 --- /dev/null +++ b/app/controllers/admin/bounced_mail_addresses_controller.rb @@ -0,0 +1,30 @@ +module Admin + class BouncedMailAddressesController < BaseController + before_action :set_bounced_mail_address, only: %i[show destroy] + load_and_authorize_resource + + # GET /bounced_mail_addresses + def index + @bounced_mail_addresses = BouncedMailAddress.all.order(created_at: :desc) + end + + # GET /bounced_mail_addresses/1 + def show; end + + # DELETE /bounced_mail_addresses/1 + def destroy + @bounced_mail_address.destroy + redirect_to( + admin_bounced_mail_addresses_url, + notice: 'Bounced mail address was successfully destroyed.' + ) + end + + private + + # Use callbacks to share common setup or constraints between actions. + def set_bounced_mail_address + @bounced_mail_address = BouncedMailAddress.find(params[:id]) + end + end +end diff --git a/app/controllers/api/v1/base_controller.rb b/app/controllers/api/v1/base_controller.rb index 54930edf9..b62b3e063 100644 --- a/app/controllers/api/v1/base_controller.rb +++ b/app/controllers/api/v1/base_controller.rb @@ -10,6 +10,11 @@ module Api head :unauthorized unless ip_allowed end + def authenticate_shared_key + api_key = "Basic #{ENV['api_shared_key']}" + head(:unauthorized) unless api_key == request.authorization + end + def not_found_error uuid = params['uuid'] json = { error: 'Not Found', uuid: uuid, message: 'Record not found' } diff --git a/app/controllers/api/v1/bounces_controller.rb b/app/controllers/api/v1/bounces_controller.rb new file mode 100644 index 000000000..fd10a3398 --- /dev/null +++ b/app/controllers/api/v1/bounces_controller.rb @@ -0,0 +1,25 @@ +module Api + module V1 + class BouncesController < BaseController + before_action :authenticate_shared_key + + # POST api/v1/bounces/ + def create + return head(:bad_request) unless bounce_params[:bounce][:bouncedRecipients].any? + + BouncedMailAddress.record(bounce_params) + head(:created) + rescue ActionController::ParameterMissing + head(:bad_request) + end + + def bounce_params + params.require(:data).require(:bounce).require(:bouncedRecipients).each do |r| + r.require(:emailAddress) + end + + params.require(:data) + end + end + end +end diff --git a/app/controllers/api/v1/registrant/contacts_controller.rb b/app/controllers/api/v1/registrant/contacts_controller.rb index 10f9abacf..e11458ff8 100644 --- a/app/controllers/api/v1/registrant/contacts_controller.rb +++ b/app/controllers/api/v1/registrant/contacts_controller.rb @@ -19,15 +19,16 @@ module Api end contacts = current_user_contacts.limit(limit).offset(offset) - serialized_contacts = contacts.collect { |contact| serialize_contact(contact) } + serialized_contacts = contacts.collect { |contact| serialize_contact(contact, false) } render json: serialized_contacts end def show contact = current_user_contacts.find_by(uuid: params[:uuid]) + links = params[:links] == 'true' if contact - render json: serialize_contact(contact) + render json: serialize_contact(contact, links) else render json: { errors: [{ base: ['Contact not found'] }] }, status: :not_found end @@ -85,7 +86,7 @@ module Api contact.registrar.notify(action) end - render json: serialize_contact(contact) + render json: serialize_contact(contact, false) end private @@ -96,8 +97,8 @@ module Api current_registrant_user.direct_contacts end - def serialize_contact(contact) - Serializers::RegistrantApi::Contact.new(contact).to_json + def serialize_contact(contact, links) + Serializers::RegistrantApi::Contact.new(contact, links).to_json end end end diff --git a/app/controllers/api/v1/registrant/domains_controller.rb b/app/controllers/api/v1/registrant/domains_controller.rb index b985ef390..73b534598 100644 --- a/app/controllers/api/v1/registrant/domains_controller.rb +++ b/app/controllers/api/v1/registrant/domains_controller.rb @@ -7,6 +7,7 @@ module Api def index limit = params[:limit] || 200 offset = params[:offset] || 0 + simple = params[:simple] == 'true' || false if limit.to_i > 200 || limit.to_i < 1 render(json: { errors: [{ limit: ['parameter is out of range'] }] }, @@ -18,21 +19,20 @@ module Api status: :bad_request) && return end - @domains = current_user_domains.limit(limit).offset(offset) - - serialized_domains = @domains.map do |item| - serializer = Serializers::RegistrantApi::Domain.new(item) + domains = current_user_domains + serialized_domains = domains.limit(limit).offset(offset).map do |item| + serializer = Serializers::RegistrantApi::Domain.new(item, simplify: simple) serializer.to_json end - render json: serialized_domains + render json: { count: domains.count, domains: serialized_domains } end def show @domain = current_user_domains.find_by(uuid: params[:uuid]) if @domain - serializer = Serializers::RegistrantApi::Domain.new(@domain) + serializer = Serializers::RegistrantApi::Domain.new(@domain, simplify: false) render json: serializer.to_json else render json: { errors: [{ base: ['Domain not found'] }] }, status: :not_found diff --git a/app/controllers/registrar/bulk_change_controller.rb b/app/controllers/registrar/bulk_change_controller.rb index 441127f6c..801ab0516 100644 --- a/app/controllers/registrar/bulk_change_controller.rb +++ b/app/controllers/registrar/bulk_change_controller.rb @@ -4,11 +4,38 @@ class Registrar def new authorize! :manage, :repp + @expire_date = Time.zone.now.to_date render file: 'registrar/bulk_change/new', locals: { active_tab: default_tab } end + def bulk_renew + authorize! :manage, :repp + set_form_data + + if ready_to_renew? + res = ReppApi.bulk_renew(domain_ids_for_bulk_renew, params[:period], + current_registrar_user) + + flash_message(JSON.parse(res)) + else + flash[:notice] = nil + end + + render file: 'registrar/bulk_change/new', locals: { active_tab: :bulk_renew } + end + private + def ready_to_renew? + domain_ids_for_bulk_renew.present? && params[:renew].present? + end + + def set_form_data + @expire_date = params[:expire_date].to_date + @domains = domains_by_date(@expire_date) + @period = params[:period] + end + def available_contacts current_registrar_user.registrar.contacts.order(:name).pluck(:name, :code) end @@ -16,5 +43,27 @@ class Registrar def default_tab :technical_contact end + + def domains_scope + current_registrar_user.registrar.domains + end + + def domains_by_date(date) + domains_scope.where('valid_to <= ?', date) + end + + def domain_ids_for_bulk_renew + params.dig('domain_ids')&.reject { |id| id.blank? } + end + + def renew_task(domains) + Domains::BulkRenew::Start.run(domains: domains, + period_element: @period, + registrar: current_registrar_user.registrar) + end + + def flash_message(res) + flash[:notice] = res['code'] == 1000 ? t(:bulk_renew_completed) : res['message'] + end end end diff --git a/app/controllers/registrar/domain_transfers_controller.rb b/app/controllers/registrar/domain_transfers_controller.rb index ca08c73cf..584a50d33 100644 --- a/app/controllers/registrar/domain_transfers_controller.rb +++ b/app/controllers/registrar/domain_transfers_controller.rb @@ -55,10 +55,12 @@ class Registrar parsed_response = JSON.parse(response.body, symbolize_names: true) if response.code == '200' - flash[:notice] = t '.transferred', count: parsed_response[:data].size + failed = parsed_response[:data][:failed].each(&:domain_name).join(', ') + flash[:notice] = t('.transferred', count: parsed_response[:data][:success].size, + failed: failed) redirect_to registrar_domains_url else - @api_errors = parsed_response[:errors] + @api_errors = parsed_response[:message] render file: 'registrar/bulk_change/new', locals: { active_tab: :bulk_transfer } end else diff --git a/app/controllers/registrar/nameservers_controller.rb b/app/controllers/registrar/nameservers_controller.rb index c8c88c8ca..2a22476be 100644 --- a/app/controllers/registrar/nameservers_controller.rb +++ b/app/controllers/registrar/nameservers_controller.rb @@ -49,12 +49,13 @@ class Registrar if response.code == '200' notices = [t('.replaced')] - notices << "#{t('.affected_domains')}: #{parsed_response[:affected_domains].join(', ')}" + notices << "#{t('.affected_domains')}: " \ + "#{parsed_response[:data][:affected_domains].join(', ')}" - flash[:notice] = notices + flash[:notice] = notices.join(', ') redirect_to registrar_domains_url else - @api_errors = parsed_response[:errors] + @api_errors = parsed_response[:message] render file: 'registrar/bulk_change/new', locals: { active_tab: :nameserver } end end diff --git a/app/controllers/registrar/sessions_controller.rb b/app/controllers/registrar/sessions_controller.rb index 2fa69ca90..e413c4feb 100644 --- a/app/controllers/registrar/sessions_controller.rb +++ b/app/controllers/registrar/sessions_controller.rb @@ -62,6 +62,7 @@ class Registrar def find_user_by_idc_and_allowed(idc) return User.new unless idc + possible_users = ApiUser.where(identity_code: idc) || User.new possible_users.each do |selected_user| if selected_user.registrar.white_ips.registrar_area.include_ip?(request.ip) diff --git a/app/controllers/registrar/tech_contacts_controller.rb b/app/controllers/registrar/tech_contacts_controller.rb index 1d459ef0f..001651250 100644 --- a/app/controllers/registrar/tech_contacts_controller.rb +++ b/app/controllers/registrar/tech_contacts_controller.rb @@ -43,16 +43,18 @@ class Registrar if response.code == '200' notices = [t('.replaced')] - notices << "#{t('.affected_domains')}: #{parsed_response[:affected_domains].join(', ')}" + notices << "#{t('.affected_domains')}: " \ + "#{parsed_response[:data][:affected_domains].join(', ')}" - if parsed_response[:skipped_domains] - notices << "#{t('.skipped_domains')}: #{parsed_response[:skipped_domains].join(', ')}" + if parsed_response[:data][:skipped_domains] + notices << "#{t('.skipped_domains')}: " \ + "#{parsed_response[:data][:skipped_domains].join(', ')}" end - flash[:notice] = notices + flash[:notice] = notices.join(', ') redirect_to registrar_domains_url else - @error = parsed_response[:error] + @error = response.code == '404' ? 'Contact(s) not found' : parsed_response[:message] render file: 'registrar/bulk_change/new', locals: { active_tab: :technical_contact } end end diff --git a/app/controllers/repp/v1/base_controller.rb b/app/controllers/repp/v1/base_controller.rb index ca9ef6fb5..2814ce2da 100644 --- a/app/controllers/repp/v1/base_controller.rb +++ b/app/controllers/repp/v1/base_controller.rb @@ -3,6 +3,7 @@ module Repp class BaseController < ActionController::API rescue_from ActiveRecord::RecordNotFound, with: :not_found_error before_action :authenticate_user + before_action :validate_webclient_ca before_action :check_ip_restriction attr_reader :current_user @@ -93,15 +94,33 @@ module Repp end def check_ip_restriction - allowed = @current_user.registrar.api_ip_white?(request.ip) - - return if allowed + return if webclient_request? + return if @current_user.registrar.api_ip_white?(request.ip) @response = { code: 2202, message: I18n.t('registrar.authorization.ip_not_allowed', ip: request.ip) } render(json: @response, status: :unauthorized) end + def webclient_request? + return if Rails.env.test? + + ENV['webclient_ips'].split(',').map(&:strip).include?(request.ip) + end + + def validate_webclient_ca + return unless webclient_request? + + request_name = request.env['HTTP_SSL_CLIENT_S_DN_CN'] + webclient_cn = ENV['webclient_cert_common_name'] || 'webclient' + return if request_name == webclient_cn + + @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) diff --git a/app/controllers/repp/v1/domains/renews_controller.rb b/app/controllers/repp/v1/domains/renews_controller.rb new file mode 100644 index 000000000..6b016dd86 --- /dev/null +++ b/app/controllers/repp/v1/domains/renews_controller.rb @@ -0,0 +1,65 @@ +module Repp + module V1 + module Domains + class RenewsController < BaseController + before_action :validate_renew_period, only: [:bulk_renew] + before_action :select_renewable_domains, only: [:bulk_renew] + + def bulk_renew + renew = run_bulk_renew_task(@domains, bulk_renew_params[:renew_period]) + return render_success(data: { updated_domains: @domains.map(&:name) }) if renew.valid? + + @epp_errors << { code: 2002, + msg: renew.errors.keys.map { |k, _v| renew.errors[k] }.join(', ') } + handle_errors + end + + private + + def validate_renew_period + @epp_errors ||= [] + periods = Depp::Domain::PERIODS.map { |p| p[1] } + return if periods.include? bulk_renew_params[:renew_period] + + @epp_errors << { code: 2005, msg: 'Invalid renew period' } + end + + def select_renewable_domains + @epp_errors ||= [] + + if bulk_renew_params[:domains].instance_of?(Array) + @domains = bulk_renew_domains + else + @epp_errors << { code: 2005, msg: 'Domains attribute must be an array' } + end + + return handle_errors if @epp_errors.any? + end + + def run_bulk_renew_task(domains, period) + ::Domains::BulkRenew::Start.run(domains: domains, period_element: period, + registrar: current_user.registrar) + end + + def bulk_renew_params + params do + params.require(%i[domains renew_period]) + params.permit(:domains, :renew_period) + end + end + + def bulk_renew_domains + @epp_errors ||= [] + domains = [] + bulk_renew_params[:domains].each do |idn| + domain = Epp::Domain.find_by(name: idn) + domains << domain if domain + @epp_errors << { code: 2304, msg: "Object does not exist: #{idn}" } unless domain + end + + domains + end + end + end + end +end diff --git a/app/interactions/cancel_force_delete_interaction/base.rb b/app/interactions/cancel_force_delete_interaction/base.rb deleted file mode 100644 index 595279d87..000000000 --- a/app/interactions/cancel_force_delete_interaction/base.rb +++ /dev/null @@ -1,7 +0,0 @@ -module CancelForceDeleteInteraction - class Base < ActiveInteraction::Base - object :domain, - class: Domain, - description: 'Domain to cancel ForceDelete on' - end -end diff --git a/app/interactions/cancel_force_delete_interaction/cancel_force_delete.rb b/app/interactions/cancel_force_delete_interaction/cancel_force_delete.rb deleted file mode 100644 index 2f45bf0e4..000000000 --- a/app/interactions/cancel_force_delete_interaction/cancel_force_delete.rb +++ /dev/null @@ -1,10 +0,0 @@ -module CancelForceDeleteInteraction - class CancelForceDelete < Base - def execute - compose(RemoveForceDeleteStatuses, inputs) - compose(RestoreStatusesBeforeForceDelete, inputs) - compose(ClearForceDeleteData, inputs) - compose(NotifyRegistrar, inputs) - end - end -end diff --git a/app/interactions/cancel_force_delete_interaction/clear_force_delete_data.rb b/app/interactions/cancel_force_delete_interaction/clear_force_delete_data.rb deleted file mode 100644 index ccc0714f7..000000000 --- a/app/interactions/cancel_force_delete_interaction/clear_force_delete_data.rb +++ /dev/null @@ -1,10 +0,0 @@ -module CancelForceDeleteInteraction - 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 diff --git a/app/interactions/cancel_force_delete_interaction/notify_registrar.rb b/app/interactions/cancel_force_delete_interaction/notify_registrar.rb deleted file mode 100644 index 3ded4c489..000000000 --- a/app/interactions/cancel_force_delete_interaction/notify_registrar.rb +++ /dev/null @@ -1,8 +0,0 @@ -module CancelForceDeleteInteraction - class NotifyRegistrar < Base - def execute - domain.registrar.notifications.create!(text: I18n.t('force_delete_cancelled', - domain_name: domain.name)) - end - end -end diff --git a/app/interactions/cancel_force_delete_interaction/remove_force_delete_statuses.rb b/app/interactions/cancel_force_delete_interaction/remove_force_delete_statuses.rb deleted file mode 100644 index 1cc4989e5..000000000 --- a/app/interactions/cancel_force_delete_interaction/remove_force_delete_statuses.rb +++ /dev/null @@ -1,11 +0,0 @@ -module CancelForceDeleteInteraction - 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 diff --git a/app/interactions/cancel_force_delete_interaction/restore_statuses_before_force_delete.rb b/app/interactions/cancel_force_delete_interaction/restore_statuses_before_force_delete.rb deleted file mode 100644 index 06b6bbd18..000000000 --- a/app/interactions/cancel_force_delete_interaction/restore_statuses_before_force_delete.rb +++ /dev/null @@ -1,9 +0,0 @@ -module CancelForceDeleteInteraction - 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 diff --git a/app/interactions/domains/bulk_renew/single_domain_renew.rb b/app/interactions/domains/bulk_renew/single_domain_renew.rb new file mode 100644 index 000000000..66c6244b3 --- /dev/null +++ b/app/interactions/domains/bulk_renew/single_domain_renew.rb @@ -0,0 +1,62 @@ +module Domains + module BulkRenew + class SingleDomainRenew < ActiveInteraction::Base + object :domain, + class: Epp::Domain + integer :period + string :unit + object :registrar + + def execute + in_transaction_with_retries do + success = domain.renew(domain.valid_to, period, unit) + if success + check_balance + reduce_balance + else + add_error + end + end + end + + def check_balance + compose(Domains::CheckBalance::SingleDomain, + domain: domain, + operation: 'renew', + period: period, + unit: unit) + end + + def reduce_balance + domain_pricelist = domain.pricelist('renew', period, unit) + registrar.debit!(sum: domain_pricelist.price.amount, + description: "#{I18n.t('renew')} #{domain.name}", + activity_type: AccountActivity::RENEW, + price: domain_pricelist) + end + + def in_transaction_with_retries + if Rails.env.test? + yield + else + transaction_wrapper { yield } + end + rescue ActiveRecord::StatementInvalid + sleep rand / 100 + retry + end + + def transaction_wrapper + ActiveRecord::Base.transaction(isolation: :serializable) do + yield if block_given? + end + end + + private + + def add_error + errors.add(:domain, I18n.t('domain_renew_error_for_domain', domain: domain.name)) + end + end + end +end diff --git a/app/interactions/domains/bulk_renew/start.rb b/app/interactions/domains/bulk_renew/start.rb new file mode 100644 index 000000000..f758b52cd --- /dev/null +++ b/app/interactions/domains/bulk_renew/start.rb @@ -0,0 +1,56 @@ +module Domains + module BulkRenew + class Start < ActiveInteraction::Base + array :domains do + object class: Epp::Domain + end + string :period_element + object :registrar + + def execute + if renewable? + domains.each do |domain| + task = run_task(domain) + manage_errors(task) + end + else + manage_errors(mass_check_balance) + end + end + + private + + def renewable? + mass_check_balance.valid? && mass_check_balance.result + end + + def period + period_element.to_i.zero? ? 1 : period_element.to_i + end + + def unit + period_element[-1] || 'y' + end + + def mass_check_balance + Domains::CheckBalance::Mass.run(domains: domains, + operation: 'renew', + period: period, + unit: unit, + balance: registrar.balance) + end + + def manage_errors(task) + task.errors.each { |k, v| errors.add(k, v) } unless task.valid? + errors.add(:domain, I18n.t('not_enough_funds')) unless task.result + end + + def run_task(domain) + Domains::BulkRenew::SingleDomainRenew.run(domain: domain, + period: period, + unit: unit, + registrar: registrar) + end + end + 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/check_balance/mass.rb b/app/interactions/domains/check_balance/mass.rb new file mode 100644 index 000000000..58af25a9f --- /dev/null +++ b/app/interactions/domains/check_balance/mass.rb @@ -0,0 +1,33 @@ +module Domains + module CheckBalance + class Mass < ActiveInteraction::Base + array :domains do + object class: Epp::Domain + end + string :operation + integer :period + string :unit + float :balance + + attr_accessor :total_price + + def execute + calculate_total_price + + balance >= @total_price + end + + def calculate_total_price + @total_price = 0 + domains.each do |domain| + task = Domains::CheckBalance::SingleDomain.run(domain: domain, + operation: 'renew', + period: period, + unit: unit) + + task.valid? ? @total_price += task.result : errors.merge!(task.errors) + end + end + end + end +end diff --git a/app/interactions/domains/check_balance/single_domain.rb b/app/interactions/domains/check_balance/single_domain.rb new file mode 100644 index 000000000..166edc4e3 --- /dev/null +++ b/app/interactions/domains/check_balance/single_domain.rb @@ -0,0 +1,26 @@ +module Domains + module CheckBalance + class SingleDomain < ActiveInteraction::Base + object :domain, + class: Epp::Domain + + string :operation + integer :period + string :unit + + def execute + return domain_pricelist.price.amount if domain_pricelist.try(:price) + + errors.add(:domain, I18n.t(:active_price_missing_for_operation_with_domain, + domain: domain.name)) + false + end + + private + + def domain_pricelist + domain.pricelist(operation, period.try(:to_i), unit) + 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/base.rb b/app/interactions/domains/delete/base.rb new file mode 100644 index 000000000..434712f72 --- /dev/null +++ b/app/interactions/domains/delete/base.rb @@ -0,0 +1,9 @@ +module Domains + module Delete + class Base < ActiveInteraction::Base + object :domain, + class: Domain, + description: 'Domain to delete' + end + end +end diff --git a/app/interactions/domains/delete/do_delete.rb b/app/interactions/domains/delete/do_delete.rb new file mode 100644 index 000000000..202c36938 --- /dev/null +++ b/app/interactions/domains/delete/do_delete.rb @@ -0,0 +1,13 @@ +module Domains + module Delete + class DoDelete < Base + def execute + ::PaperTrail.request.whodunnit = "interaction - #{self.class.name}" + WhoisRecord.where(domain_id: domain.id).destroy_all + + domain.destroy + compose(Domains::Delete::NotifyRegistrar, inputs) + end + end + end +end diff --git a/app/interactions/domains/delete/notify_registrar.rb b/app/interactions/domains/delete/notify_registrar.rb new file mode 100644 index 000000000..d6af00fd8 --- /dev/null +++ b/app/interactions/domains/delete/notify_registrar.rb @@ -0,0 +1,14 @@ +module Domains + module Delete + class NotifyRegistrar < Base + def execute + bye_bye = domain.versions.last + domain.registrar.notifications.create!( + text: "#{I18n.t(:domain_deleted)}: #{domain.name}", + attached_obj_id: bye_bye.id, + attached_obj_type: bye_bye.class.to_s + ) + end + end + end +end diff --git a/app/interactions/domains/delete_confirm/base.rb b/app/interactions/domains/delete_confirm/base.rb new file mode 100644 index 000000000..5bd07f40f --- /dev/null +++ b/app/interactions/domains/delete_confirm/base.rb @@ -0,0 +1,53 @@ +module Domains + module DeleteConfirm + class Base < ActiveInteraction::Base + object :domain, + class: Domain, + description: 'Domain to confirm release' + string :action + string :initiator, + default: nil + + validates :domain, :action, presence: true + validates :action, inclusion: { in: [RegistrantVerification::CONFIRMED, + RegistrantVerification::REJECTED] } + + def raise_errors!(domain) + return unless domain.errors.any? + + message = "domain #{domain.name} failed with errors #{domain.errors.full_messages}" + throw message + end + + def notify_registrar(message_key) + domain.registrar.notifications.create!( + text: "#{I18n.t(message_key)}: #{domain.name}", + attached_obj_id: domain.id, + attached_obj_type: domain.class.to_s + ) + end + + def preclean_pendings + domain.registrant_verification_token = nil + domain.registrant_verification_asked_at = nil + end + + def clean_pendings! + domain.is_admin = true + domain.registrant_verification_token = nil + domain.registrant_verification_asked_at = nil + domain.pending_json = {} + clear_statuses + domain.save + end + + def clear_statuses + domain.statuses.delete(DomainStatus::PENDING_DELETE_CONFIRMATION) + domain.statuses.delete(DomainStatus::PENDING_UPDATE) + domain.statuses.delete(DomainStatus::PENDING_DELETE) + domain.status_notes[DomainStatus::PENDING_UPDATE] = '' + domain.status_notes[DomainStatus::PENDING_DELETE] = '' + end + end + end +end diff --git a/app/interactions/domains/delete_confirm/process_action.rb b/app/interactions/domains/delete_confirm/process_action.rb new file mode 100644 index 000000000..59f23de67 --- /dev/null +++ b/app/interactions/domains/delete_confirm/process_action.rb @@ -0,0 +1,17 @@ +module Domains + module DeleteConfirm + class ProcessAction < Base + def execute + ::PaperTrail.request.whodunnit = "interaction - #{self.class.name} - #{action} by"\ + " #{initiator}" + + case action + when RegistrantVerification::CONFIRMED + compose(ProcessDeleteConfirmed, inputs) + when RegistrantVerification::REJECTED + compose(ProcessDeleteRejected, inputs) + end + end + end + end +end diff --git a/app/interactions/domains/delete_confirm/process_delete_confirmed.rb b/app/interactions/domains/delete_confirm/process_delete_confirmed.rb new file mode 100644 index 000000000..1f3068107 --- /dev/null +++ b/app/interactions/domains/delete_confirm/process_delete_confirmed.rb @@ -0,0 +1,50 @@ +module Domains + module DeleteConfirm + class ProcessDeleteConfirmed < Base + def execute + notify_registrar(:poll_pending_delete_confirmed_by_registrant) + domain.apply_pending_delete! + raise_errors!(domain) + end + + def apply_pending_delete! + preclean_pendings + clean_pendings! + DomainDeleteMailer.accepted(domain).deliver_now + domain.set_pending_delete! + end + + def set_pending_delete! + unless domain.pending_deletable? + add_epp_error + return + end + + domain.delete_date = delete_date + domain.statuses << DomainStatus::PENDING_DELETE + set_server_hold if server_holdable? + domain.save(validate: false) + end + + def set_server_hold + domain.statuses << DomainStatus::SERVER_HOLD + domain.outzone_at = Time.current + end + + def server_holdable? + return false if domain.statuses.include?(DomainStatus::SERVER_HOLD) + return false if domain.statuses.include?(DomainStatus::SERVER_MANUAL_INZONE) + + true + end + + def delete_date + Time.zone.today + Setting.redemption_grace_period.days + 1.day + end + + def add_epp_error + domain.add_epp_error('2304', nil, nil, I18n.t(:object_status_prohibits_operation)) + end + end + end +end diff --git a/app/interactions/domains/delete_confirm/process_delete_rejected.rb b/app/interactions/domains/delete_confirm/process_delete_rejected.rb new file mode 100644 index 000000000..25b491660 --- /dev/null +++ b/app/interactions/domains/delete_confirm/process_delete_rejected.rb @@ -0,0 +1,32 @@ +module Domains + module DeleteConfirm + class ProcessDeleteRejected < Base + def execute + domain.cancel_pending_delete + notify_registrar(:poll_pending_delete_rejected_by_registrant) + domain.save(validate: false) + raise_errors!(domain) + + send_domain_delete_rejected_email + end + + def send_domain_delete_rejected_email + if domain.registrant_verification_token.blank? + warn "EMAIL NOT DELIVERED: registrant_verification_token is missing for #{domain.name}" + elsif domain.registrant_verification_asked_at.blank? + warn "EMAIL NOT DELIVERED: registrant_verification_asked_at is missing for #{domain.name}" + else + send_email + end + end + + def warn(message) + Rails.logger.warn(message) + end + + def send_email + DomainDeleteMailer.rejected(domain).deliver_now + end + end + end +end diff --git a/app/interactions/domains/delete_confirm_email/send_request.rb b/app/interactions/domains/delete_confirm_email/send_request.rb new file mode 100644 index 000000000..7070423a6 --- /dev/null +++ b/app/interactions/domains/delete_confirm_email/send_request.rb @@ -0,0 +1,24 @@ +module Domains + module DeleteConfirmEmail + 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/expire_period/base.rb b/app/interactions/domains/expire_period/base.rb new file mode 100644 index 000000000..eab8171bf --- /dev/null +++ b/app/interactions/domains/expire_period/base.rb @@ -0,0 +1,10 @@ +module Domains + module ExpirePeriod + 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/expire_period/process_expired.rb b/app/interactions/domains/expire_period/process_expired.rb new file mode 100644 index 000000000..595de1228 --- /dev/null +++ b/app/interactions/domains/expire_period/process_expired.rb @@ -0,0 +1,28 @@ +module Domains + module ExpirePeriod + class ProcessExpired < Base + object :domain, + class: Domain, + description: 'Domain to set expiration' + + def execute + set_graceful_expired + to_stdout("start_expire_period: ##{domain.id} (#{domain.name}) #{domain.changes}") + + saved = domain.save(validate: false) + + DomainExpireEmailJob.enqueue(domain.id, run_at: send_time) if saved + end + + def set_graceful_expired + domain.outzone_at = domain.expire_time + Domain.expire_warning_period + domain.delete_date = domain.outzone_at + Domain.redemption_grace_period + domain.statuses |= [DomainStatus::EXPIRED] + end + + def send_time + domain.valid_to + Setting.expiration_reminder_mail.to_i.days + end + end + end +end diff --git a/app/interactions/domains/expire_period/start.rb b/app/interactions/domains/expire_period/start.rb new file mode 100644 index 000000000..1ed2342f1 --- /dev/null +++ b/app/interactions/domains/expire_period/start.rb @@ -0,0 +1,19 @@ +module Domains + module ExpirePeriod + class Start < Base + def execute + ::PaperTrail.request.whodunnit = "cron - #{self.class.name}" + count = 0 + + Domain.expired.each do |domain| + next unless domain.expirable? + + count += 1 + Domains::ExpirePeriod::ProcessExpired.run(domain: domain) + end + + to_stdout("Successfully expired #{count}") + end + end + end +end diff --git a/app/interactions/domains/expired_pendings/base.rb b/app/interactions/domains/expired_pendings/base.rb new file mode 100644 index 000000000..7faa32050 --- /dev/null +++ b/app/interactions/domains/expired_pendings/base.rb @@ -0,0 +1,10 @@ +module Domains + module ExpiredPendings + 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/expired_pendings/clean_all.rb b/app/interactions/domains/expired_pendings/clean_all.rb new file mode 100644 index 000000000..1dbab266c --- /dev/null +++ b/app/interactions/domains/expired_pendings/clean_all.rb @@ -0,0 +1,35 @@ +module Domains + module ExpiredPendings + class CleanAll < Base + def execute + to_stdout('Clean expired domain pendings') + + ::PaperTrail.request.whodunnit = "cron - #{self.class.name}" + + count = 0 + expired_pending_domains.each do |domain| + log_error(domain) && next unless need_to_be_cleared?(domain) + count += 1 + Domains::ExpiredPendings::ProcessClean.run(domain: domain) + end + to_stdout("Successfully cancelled #{count} domain pendings") + end + + private + + def need_to_be_cleared?(domain) + domain.pending_update? || domain.pending_delete? || domain.pending_delete_confirmation? + end + + def log_error(domain) + to_stdout("ISSUE: DOMAIN #{domain.id}: #{domain.name} IS IN EXPIRED PENDING LIST, "\ + 'but no pendingDelete/pendingUpdate state present!') + end + + def expired_pending_domains + expire_at = Setting.expire_pending_confirmation.hours.ago + Domain.where('registrant_verification_asked_at <= ?', expire_at) + end + end + end +end diff --git a/app/interactions/domains/expired_pendings/process_clean.rb b/app/interactions/domains/expired_pendings/process_clean.rb new file mode 100644 index 000000000..c6277c3c5 --- /dev/null +++ b/app/interactions/domains/expired_pendings/process_clean.rb @@ -0,0 +1,60 @@ +module Domains + module ExpiredPendings + class ProcessClean < Base + object :domain, + class: Domain + + def execute + check_notify + clean_pendings + + to_stdout("DomainCron.clean_expired_pendings: ##{domain.id} (#{domain.name})") + UpdateWhoisRecordJob.enqueue domain.name, 'domain' + end + + private + + def notify_pending_update + RegistrantChangeMailer.expired(domain: domain, + registrar: domain.registrar, + registrant: domain.registrant).deliver_later + end + + def notify_pending_delete + DomainDeleteMailer.expired(domain).deliver_later + end + + def clean_pendings + clean_verification_data + domain.pending_json = {} + clean_statuses + domain.save + end + + def statuses_to_clean + [DomainStatus::PENDING_DELETE_CONFIRMATION, + DomainStatus::PENDING_UPDATE, + DomainStatus::PENDING_DELETE] + end + + def clean_statuses + domain.statuses = domain.statuses - statuses_to_clean + domain.status_notes[DomainStatus::PENDING_UPDATE] = '' + domain.status_notes[DomainStatus::PENDING_DELETE] = '' + end + + def clean_verification_data + domain.registrant_verification_token = nil + domain.registrant_verification_asked_at = nil + end + + def check_notify + notify_pending_update if domain.pending_update? + + return unless domain.pending_delete? || domain.pending_delete_confirmation? + + notify_pending_delete + 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/interactions/domains/redemption_grace_period/base.rb b/app/interactions/domains/redemption_grace_period/base.rb new file mode 100644 index 000000000..5d1ede289 --- /dev/null +++ b/app/interactions/domains/redemption_grace_period/base.rb @@ -0,0 +1,10 @@ +module Domains + module RedemptionGracePeriod + 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/redemption_grace_period/process_grace_period.rb b/app/interactions/domains/redemption_grace_period/process_grace_period.rb new file mode 100644 index 000000000..0f120a996 --- /dev/null +++ b/app/interactions/domains/redemption_grace_period/process_grace_period.rb @@ -0,0 +1,20 @@ +module Domains + module RedemptionGracePeriod + class ProcessGracePeriod < Base + object :domain, + class: Domain + + def execute + domain.statuses << DomainStatus::SERVER_HOLD + to_stdout(process_msg) + domain.save(validate: false) + end + + private + + def process_msg + "start_redemption_grace_period: #{domain.id} (#{domain.name}) #{domain.changes}" + end + end + end +end diff --git a/app/interactions/domains/redemption_grace_period/start.rb b/app/interactions/domains/redemption_grace_period/start.rb new file mode 100644 index 000000000..ef7f42e58 --- /dev/null +++ b/app/interactions/domains/redemption_grace_period/start.rb @@ -0,0 +1,20 @@ +module Domains + module RedemptionGracePeriod + class Start < Base + def execute + to_stdout('Setting server_hold to domains') + + ::PaperTrail.request.whodunnit = "cron - #{self.class.name}" + count = 0 + + Domain.outzone_candidates.each do |domain| + next unless domain.server_holdable? + + count += 1 + Domains::RedemptionGracePeriod::ProcessGracePeriod.run(domain: domain) + end + to_stdout("Successfully set server_hold to #{count} of domains") + end + end + end +end diff --git a/app/interactions/domains/update_confirm/base.rb b/app/interactions/domains/update_confirm/base.rb new file mode 100644 index 000000000..0e1fa81d3 --- /dev/null +++ b/app/interactions/domains/update_confirm/base.rb @@ -0,0 +1,53 @@ +module Domains + module UpdateConfirm + class Base < ActiveInteraction::Base + object :domain, + class: Domain, + description: 'Domain to confirm update' + string :action + string :initiator, + default: nil + + validates :domain, :action, presence: true + validates :action, inclusion: { in: [RegistrantVerification::CONFIRMED, + RegistrantVerification::REJECTED] } + + def raise_errors!(domain) + return unless domain.errors.any? + + message = "domain #{domain.name} failed with errors #{domain.errors.full_messages}" + throw message + end + + def notify_registrar(message_key) + domain.registrar.notifications.create!( + text: "#{I18n.t(message_key)}: #{domain.name}", + attached_obj_id: domain.id, + attached_obj_type: domain.class.to_s + ) + end + + def preclean_pendings + domain.registrant_verification_token = nil + domain.registrant_verification_asked_at = nil + end + + def clean_pendings! + domain.is_admin = true + domain.registrant_verification_token = nil + domain.registrant_verification_asked_at = nil + domain.pending_json = {} + clear_statuses + domain.save + end + + def clear_statuses + domain.statuses.delete(DomainStatus::PENDING_DELETE_CONFIRMATION) + domain.statuses.delete(DomainStatus::PENDING_UPDATE) + domain.statuses.delete(DomainStatus::PENDING_DELETE) + domain.status_notes[DomainStatus::PENDING_UPDATE] = '' + domain.status_notes[DomainStatus::PENDING_DELETE] = '' + end + end + end +end diff --git a/app/interactions/domains/update_confirm/process_action.rb b/app/interactions/domains/update_confirm/process_action.rb new file mode 100644 index 000000000..6ef8d0fe6 --- /dev/null +++ b/app/interactions/domains/update_confirm/process_action.rb @@ -0,0 +1,17 @@ +module Domains + module UpdateConfirm + class ProcessAction < Base + def execute + ::PaperTrail.request.whodunnit = "interaction - #{self.class.name} - #{action} by"\ + " #{initiator}" + + case action + when RegistrantVerification::CONFIRMED + compose(ProcessUpdateConfirmed, inputs) + when RegistrantVerification::REJECTED + compose(ProcessUpdateRejected, inputs) + end + end + end + end +end diff --git a/app/interactions/domains/update_confirm/process_update_confirmed.rb b/app/interactions/domains/update_confirm/process_update_confirmed.rb new file mode 100644 index 000000000..cf7c82a7a --- /dev/null +++ b/app/interactions/domains/update_confirm/process_update_confirmed.rb @@ -0,0 +1,37 @@ +module Domains + module UpdateConfirm + class ProcessUpdateConfirmed < Base + def execute + ActiveRecord::Base.transaction do + old_registrant = domain.registrant + notify_registrar(:poll_pending_update_confirmed_by_registrant) + + apply_pending_update! + raise_errors!(domain) + RegistrantChange.new(domain: domain, old_registrant: old_registrant).confirm + end + end + + def apply_pending_update! + preclean_pendings + update_domain + clean_pendings! + + WhoisRecord.find_by(domain_id: domain.id).save # need to reload model + end + + def update_domain + user = ApiUser.find(domain.pending_json['current_user_id']) + frame = domain.pending_json['frame'] ? domain.pending_json['frame'].with_indifferent_access : {} + + #self.statuses.delete(DomainStatus::PENDING_UPDATE) + domain.upid = user.registrar.id if user.registrar + domain.up_date = Time.zone.now + + Actions::DomainUpdate.new(domain, frame, true).call + + #save! + end + end + end +end diff --git a/app/interactions/domains/update_confirm/process_update_rejected.rb b/app/interactions/domains/update_confirm/process_update_rejected.rb new file mode 100644 index 000000000..1d7b75b0e --- /dev/null +++ b/app/interactions/domains/update_confirm/process_update_rejected.rb @@ -0,0 +1,18 @@ +module Domains + module UpdateConfirm + class ProcessUpdateRejected < Base + def execute + ActiveRecord::Base.transaction do + RegistrantChangeMailer.rejected(domain: domain, + registrar: domain.registrar, + registrant: domain.registrant).deliver_now + + notify_registrar(:poll_pending_update_rejected_by_registrant) + + preclean_pendings + clean_pendings! + end + end + end + end +end diff --git a/app/interactions/force_delete_interaction/base.rb b/app/interactions/force_delete_interaction/base.rb deleted file mode 100644 index 4764ce8fe..000000000 --- a/app/interactions/force_delete_interaction/base.rb +++ /dev/null @@ -1,16 +0,0 @@ -module ForceDeleteInteraction - 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 - diff --git a/app/interactions/force_delete_interaction/check_discarded.rb b/app/interactions/force_delete_interaction/check_discarded.rb deleted file mode 100644 index e0c893294..000000000 --- a/app/interactions/force_delete_interaction/check_discarded.rb +++ /dev/null @@ -1,11 +0,0 @@ -module ForceDeleteInteraction - 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 - diff --git a/app/interactions/force_delete_interaction/notify_by_email.rb b/app/interactions/force_delete_interaction/notify_by_email.rb deleted file mode 100644 index 541df258d..000000000 --- a/app/interactions/force_delete_interaction/notify_by_email.rb +++ /dev/null @@ -1,21 +0,0 @@ -module ForceDeleteInteraction - 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 diff --git a/app/interactions/force_delete_interaction/notify_registrar.rb b/app/interactions/force_delete_interaction/notify_registrar.rb deleted file mode 100644 index 691e54f7e..000000000 --- a/app/interactions/force_delete_interaction/notify_registrar.rb +++ /dev/null @@ -1,10 +0,0 @@ -module ForceDeleteInteraction - 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 diff --git a/app/interactions/force_delete_interaction/post_set_process.rb b/app/interactions/force_delete_interaction/post_set_process.rb deleted file mode 100644 index e51acf9b7..000000000 --- a/app/interactions/force_delete_interaction/post_set_process.rb +++ /dev/null @@ -1,17 +0,0 @@ -module ForceDeleteInteraction - 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 diff --git a/app/interactions/force_delete_interaction/prepare_domain.rb b/app/interactions/force_delete_interaction/prepare_domain.rb deleted file mode 100644 index 97f364145..000000000 --- a/app/interactions/force_delete_interaction/prepare_domain.rb +++ /dev/null @@ -1,13 +0,0 @@ -module ForceDeleteInteraction - 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 diff --git a/app/interactions/force_delete_interaction/set_force_delete.rb b/app/interactions/force_delete_interaction/set_force_delete.rb deleted file mode 100644 index 4608b5127..000000000 --- a/app/interactions/force_delete_interaction/set_force_delete.rb +++ /dev/null @@ -1,12 +0,0 @@ -module ForceDeleteInteraction - 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 diff --git a/app/interactions/force_delete_interaction/set_status.rb b/app/interactions/force_delete_interaction/set_status.rb deleted file mode 100644 index 7d104aeee..000000000 --- a/app/interactions/force_delete_interaction/set_status.rb +++ /dev/null @@ -1,38 +0,0 @@ -module ForceDeleteInteraction - 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 diff --git a/app/interactions/whois/delete_record.rb b/app/interactions/whois/delete_record.rb new file mode 100644 index 000000000..62e416d2f --- /dev/null +++ b/app/interactions/whois/delete_record.rb @@ -0,0 +1,48 @@ +module Whois + class DeleteRecord < ActiveInteraction::Base + string :name + string :type + + validates :type, inclusion: { in: %w[reserved blocked domain disputed zone] } + + def execute + send "delete_#{type}", name + end + + # 1. deleting own + # 2. trying to regenerate reserved in order domain is still in the list + def delete_domain(name) + WhoisRecord.where(name: name).destroy_all + + BlockedDomain.find_by(name: name).try(:generate_data) + ReservedDomain.find_by(name: name).try(:generate_data) + Dispute.active.find_by(domain_name: name).try(:generate_data) + end + + def delete_reserved(name) + remove_status_from_whois(domain_name: name, domain_status: 'Reserved') + end + + def delete_blocked(name) + delete_reserved(name) + end + + def delete_disputed(name) + return if Dispute.active.find_by(domain_name: name).present? + + remove_status_from_whois(domain_name: name, domain_status: 'disputed') + end + + def delete_zone(name) + WhoisRecord.where(name: name).destroy_all + Whois::Record.where(name: name).destroy_all + end + + def remove_status_from_whois(domain_name:, domain_status:) + Whois::Record.where(name: domain_name).each do |r| + r.json['status'] = r.json['status'].delete_if { |status| status == domain_status } + r.json['status'].blank? ? r.destroy : r.save + end + end + end +end diff --git a/app/interactions/whois/update.rb b/app/interactions/whois/update.rb new file mode 100644 index 000000000..bd37b4576 --- /dev/null +++ b/app/interactions/whois/update.rb @@ -0,0 +1,39 @@ +module Whois + class Update < ActiveInteraction::Base + array :names + string :type + + validates :type, inclusion: { in: %w[reserved blocked domain disputed zone] } + + def execute + ::PaperTrail.request.whodunnit = "job - #{self.class.name} - #{type}" + + klass = determine_class + + Array(names).each do |name| + record = find_record(klass, name) + if record + Whois::UpdateRecord.run(record: record, type: type) + else + Whois::DeleteRecord.run(name: name, type: type) + end + end + end + + private + + def determine_class + case type + when 'reserved' then ReservedDomain + when 'blocked' then BlockedDomain + when 'domain' then Domain + when 'disputed' then Dispute.active + else DNS::Zone + end + end + + def find_record(klass, name) + klass == DNS::Zone ? klass.find_by(origin: name) : klass.find_by(name: name) + end + end +end diff --git a/app/interactions/whois/update_record.rb b/app/interactions/whois/update_record.rb new file mode 100644 index 000000000..6fc838968 --- /dev/null +++ b/app/interactions/whois/update_record.rb @@ -0,0 +1,32 @@ +module Whois + class UpdateRecord < ActiveInteraction::Base + interface :record + string :type + + validates :type, inclusion: { in: %w[reserved blocked domain disputed zone] } + + def execute + send "update_#{type}", record + end + + def update_domain(domain) + domain.whois_record ? domain.whois_record.save : domain.create_whois_record + end + + def update_reserved(record) + record.generate_data + end + + def update_blocked(record) + update_reserved(record) + end + + def update_disputed(record) + update_reserved(record) + end + + def update_zone(record) + update_reserved(record) + end + end +end diff --git a/app/jobs/application_job.rb b/app/jobs/application_job.rb new file mode 100644 index 000000000..a009ace51 --- /dev/null +++ b/app/jobs/application_job.rb @@ -0,0 +1,2 @@ +class ApplicationJob < ActiveJob::Base +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_delete_confirm_job.rb b/app/jobs/domain_delete_confirm_job.rb index e94f2432c..afea3da59 100644 --- a/app/jobs/domain_delete_confirm_job.rb +++ b/app/jobs/domain_delete_confirm_job.rb @@ -1,39 +1,11 @@ -class DomainDeleteConfirmJob < Que::Job - def run(domain_id, action, initiator = nil) - ::PaperTrail.request.whodunnit = "job - #{self.class.name} - #{action} by #{initiator}" - # it's recommended to keep transaction against job table as short as possible. - ActiveRecord::Base.transaction do - domain = Epp::Domain.find(domain_id) +class DomainDeleteConfirmJob < ApplicationJob + queue_as :default - case action - when RegistrantVerification::CONFIRMED - domain.notify_registrar(:poll_pending_delete_confirmed_by_registrant) - domain.apply_pending_delete! - raise_errors!(domain) + def perform(domain_id, action, initiator = nil) + domain = Epp::Domain.find(domain_id) - when RegistrantVerification::REJECTED - domain.statuses.delete(DomainStatus::PENDING_DELETE_CONFIRMATION) - domain.notify_registrar(:poll_pending_delete_rejected_by_registrant) - - domain.cancel_pending_delete - domain.save(validate: false) - raise_errors!(domain) - - if domain.registrant_verification_token.blank? - Rails.logger.warn "EMAIL NOT DELIVERED: registrant_verification_token is missing for #{domain.name}" - elsif domain.registrant_verification_asked_at.blank? - Rails.logger.warn "EMAIL NOT DELIVERED: registrant_verification_asked_at is missing for #{domain.name}" - else - DomainDeleteMailer.rejected(domain).deliver_now - end - end - - destroy # it's best to destroy the job in the same transaction - end - end - - - def raise_errors!(domain) - throw "domain #{domain.name} failed with errors #{domain.errors.full_messages}" if domain.errors.any? + Domains::DeleteConfirm::ProcessAction.run(domain: domain, + action: action, + initiator: initiator) end end diff --git a/app/jobs/domain_delete_job.rb b/app/jobs/domain_delete_job.rb index 43e0bb844..5577908c6 100644 --- a/app/jobs/domain_delete_job.rb +++ b/app/jobs/domain_delete_job.rb @@ -3,15 +3,6 @@ class DomainDeleteJob < Que::Job def run(domain_id) domain = Domain.find(domain_id) - ::PaperTrail.request.whodunnit = "job - #{self.class.name}" - WhoisRecord.where(domain_id: domain.id).destroy_all - - domain.destroy - bye_bye = domain.versions.last - domain.registrar.notifications.create!( - text: "#{I18n.t(:domain_deleted)}: #{domain.name}", - attached_obj_id: bye_bye.id, - attached_obj_type: bye_bye.class.to_s - ) + Domains::Delete::DoDelete.run(domain: domain) end end diff --git a/app/jobs/domain_update_confirm_job.rb b/app/jobs/domain_update_confirm_job.rb index f3665f1e8..403318ca6 100644 --- a/app/jobs/domain_update_confirm_job.rb +++ b/app/jobs/domain_update_confirm_job.rb @@ -1,37 +1,10 @@ -class DomainUpdateConfirmJob < Que::Job - def run(domain_id, action, initiator = nil) - ::PaperTrail.request.whodunnit = "job - #{self.class.name} - #{action} by #{initiator}" - # it's recommended to keep transaction against job table as short as possible. - ActiveRecord::Base.transaction do - domain = Epp::Domain.find(domain_id) - domain.is_admin = true - case action - when RegistrantVerification::CONFIRMED - old_registrant = domain.registrant - domain.notify_registrar(:poll_pending_update_confirmed_by_registrant) - raise_errors!(domain) +class DomainUpdateConfirmJob < ApplicationJob + queue_as :default - domain.apply_pending_update! - raise_errors!(domain) - - domain.clean_pendings! - raise_errors!(domain) - RegistrantChange.new(domain: domain, old_registrant: old_registrant).confirm - when RegistrantVerification::REJECTED - RegistrantChangeMailer.rejected(domain: domain, - registrar: domain.registrar, - registrant: domain.registrant).deliver_now - - domain.notify_registrar(:poll_pending_update_rejected_by_registrant) - - domain.preclean_pendings - domain.clean_pendings! - end - destroy # it's best to destroy the job in the same transaction - end - end - - def raise_errors!(domain) - throw "domain #{domain.name} failed with errors #{domain.errors.full_messages}" if domain.errors.any? + def perform(domain_id, action, initiator = nil) + domain = Epp::Domain.find(domain_id) + Domains::UpdateConfirm::ProcessAction.run(domain: domain, + action: action, + initiator: initiator) end end diff --git a/app/jobs/update_whois_record_job.rb b/app/jobs/update_whois_record_job.rb index 2973d5a6b..d067807a0 100644 --- a/app/jobs/update_whois_record_job.rb +++ b/app/jobs/update_whois_record_job.rb @@ -1,87 +1,5 @@ class UpdateWhoisRecordJob < Que::Job - def run(names, type) - ::PaperTrail.request.whodunnit = "job - #{self.class.name} - #{type}" - - klass = determine_class(type) - - Array(names).each do |name| - record = find_record(klass, name) - if record - send "update_#{type}", record - else - send "delete_#{type}", name - end - end - end - - def find_record(klass, name) - klass == DNS::Zone ? klass.find_by(origin: name) : klass.find_by(name: name) - end - - def determine_class(type) - case type - when 'reserved' then ReservedDomain - when 'blocked' then BlockedDomain - when 'domain' then Domain - when 'disputed' then Dispute.active - when 'zone' then DNS::Zone - end - end - - def update_domain(domain) - domain.whois_record ? domain.whois_record.save : domain.create_whois_record - end - - def update_reserved(record) - record.generate_data - end - - def update_blocked(record) - update_reserved(record) - end - - def update_disputed(record) - update_reserved(record) - end - - def update_zone(record) - update_reserved(record) - end - - # 1. deleting own - # 2. trying to regenerate reserved in order domain is still in the list - def delete_domain(name) - WhoisRecord.where(name: name).destroy_all - - BlockedDomain.find_by(name: name).try(:generate_data) - ReservedDomain.find_by(name: name).try(:generate_data) - Dispute.active.find_by(domain_name: name).try(:generate_data) - end - - def delete_reserved(name) - remove_status_from_whois(domain_name: name, domain_status: 'Reserved') - end - - def delete_blocked(name) - delete_reserved(name) - end - - def delete_disputed(name) - return if Dispute.active.find_by(domain_name: name).present? - - remove_status_from_whois(domain_name: name, domain_status: 'disputed') - end - - def delete_zone(name) - WhoisRecord.where(name: name).destroy_all - Whois::Record.where(name: name).destroy_all - end - - def remove_status_from_whois(domain_name:, domain_status:) - Whois::Record.where(name: domain_name).each do |r| - r.json['status'] = r.json['status'].delete_if { |status| status == domain_status } - r.json['status'].blank? ? r.destroy : r.save - end + Whois::Update.run(names: [names].flatten, type: type) end end diff --git a/app/models/ability.rb b/app/models/ability.rb index dce8a515b..31637b8ea 100644 --- a/app/models/ability.rb +++ b/app/models/ability.rb @@ -109,6 +109,7 @@ class Ability can :destroy, :pending can :create, :zonefile can :access, :settings_menu + can :manage, BouncedMailAddress end def static_registrant diff --git a/app/models/actions/domain_update.rb b/app/models/actions/domain_update.rb index 26fae80b0..63a604335 100644 --- a/app/models/actions/domain_update.rb +++ b/app/models/actions/domain_update.rb @@ -169,16 +169,19 @@ module Actions invalid = false params[:statuses].each do |s| unless DomainStatus::CLIENT_STATUSES.include?(s[:status]) - domain.add_epp_error('2303', 'status', s[:status], %i[domain_statuses not_found]) + domain.add_epp_error('2303', 'status', s[:status], %i[statuses not_found]) invalid = true end end + return if invalid + params[:statuses].select { |s| s[:action] == 'rem' }.each do |s| if domain.statuses.include?(s[:status]) rem << s[:status] else - domain.add_epp_error('2303', 'status', s[:status], %i[domain_statuses not_found]) + STDOUT << 'AAAAAH' + domain.add_epp_error('2303', 'status', s[:status], %i[statuses not_found]) invalid = true end end diff --git a/app/models/bounced_mail_address.rb b/app/models/bounced_mail_address.rb new file mode 100644 index 000000000..73c6a0941 --- /dev/null +++ b/app/models/bounced_mail_address.rb @@ -0,0 +1,28 @@ +class BouncedMailAddress < ApplicationRecord + validates :email, :message_id, :bounce_type, :bounce_subtype, :action, :status, presence: true + + def bounce_reason + "#{action} (#{status} #{diagnostic})" + end + + def self.record(json) + bounced_records = json['bounce']['bouncedRecipients'] + bounced_records.each do |record| + bounce_record = BouncedMailAddress.new(params_from_json(json, record)) + + bounce_record.save + end + end + + def self.params_from_json(json, bounced_record) + { + email: bounced_record['emailAddress'], + message_id: json['mail']['messageId'], + bounce_type: json['bounce']['bounceType'], + bounce_subtype: json['bounce']['bounceSubType'], + action: bounced_record['action'], + status: bounced_record['status'], + diagnostic: bounced_record['diagnosticCode'], + } + end +end diff --git a/app/models/concerns/domain/deletable.rb b/app/models/concerns/domain/deletable.rb index bd01ab1c9..81518c739 100644 --- a/app/models/concerns/domain/deletable.rb +++ b/app/models/concerns/domain/deletable.rb @@ -20,6 +20,6 @@ module Concerns::Domain::Deletable end def deletion_deadline - delete_date + 24.hours + (delete_date || Time.zone.now) + 24.hours end -end \ No newline at end of file +end diff --git a/app/models/concerns/domain/force_delete.rb b/app/models/concerns/domain/force_delete.rb index ff869fc0a..e06da25cc 100644 --- a/app/models/concerns/domain/force_delete.rb +++ b/app/models/concerns/domain/force_delete.rb @@ -33,33 +33,14 @@ module Concerns::Domain::ForceDelete # rubocop:disable Metrics/ModuleLength 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, notify_by_email: false) - ForceDeleteInteraction::SetForceDelete.run(domain: self, - type: type, - notify_by_email: notify_by_email) + Domains::ForceDelete::SetForceDelete.run(domain: self, + type: type, + notify_by_email: notify_by_email) end def cancel_force_delete - CancelForceDeleteInteraction::CancelForceDelete.run(domain: self) + Domains::CancelForceDelete::CancelForceDelete.run(domain: self) end def outzone_date 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 8a154c50c..e30312b4a 100644 --- a/app/models/contact.rb +++ b/app/models/contact.rb @@ -347,19 +347,24 @@ class Contact < ApplicationRecord @desc = {} registrant_domains.each do |dom| - @desc[dom.name] ||= [] - @desc[dom.name] << :registrant + @desc[dom.name] ||= { id: dom.uuid, roles: [] } + @desc[dom.name][:roles] << :registrant end domain_contacts.each do |dc| - @desc[dc.domain.name] ||= [] - @desc[dc.domain.name] << dc.name.downcase.to_sym + @desc[dc.domain.name] ||= { id: dc.domain.uuid, roles: [] } + @desc[dc.domain.name][:roles] << dc.name.downcase.to_sym @desc[dc.domain.name] = @desc[dc.domain.name].compact end @desc end + def related_domains + a = related_domain_descriptions + a.keys.map { |d| { name: d, id: a[d][:id], roles: a[d][:roles] } } + end + def status_notes_array=(notes) self.status_notes = {} notes ||= [] diff --git a/app/models/domain.rb b/app/models/domain.rb index b15bb7c55..49f18d9db 100644 --- a/app/models/domain.rb +++ b/app/models/domain.rb @@ -327,6 +327,7 @@ class Domain < ApplicationRecord end def notify_registrar(message_key) + # TODO: To be deleted with DomainDeleteConfirm refactoring registrar.notifications.create!( text: "#{I18n.t(message_key)}: #{name}", attached_obj_id: id, @@ -335,11 +336,13 @@ class Domain < ApplicationRecord end def preclean_pendings + # TODO: To be deleted with refactoring self.registrant_verification_token = nil self.registrant_verification_asked_at = nil end def clean_pendings! + # TODO: To be deleted with refactoring preclean_pendings self.pending_json = {} statuses.delete(DomainStatus::PENDING_DELETE_CONFIRMATION) @@ -418,7 +421,7 @@ class Domain < ApplicationRecord pending_delete_confirmation! save(validate: false) # should check if this did succeed - DomainDeleteConfirmEmailJob.enqueue(id) + Domains::DeleteConfirmEmail::SendRequest.run(domain: self) end def cancel_pending_delete @@ -482,12 +485,6 @@ class Domain < ApplicationRecord Registrant.find_by(id: pending_json['new_registrant_id']) end - def set_graceful_expired - self.outzone_at = expire_time + self.class.expire_warning_period - self.delete_date = outzone_at + self.class.redemption_grace_period - self.statuses |= [DomainStatus::EXPIRED] - end - def pending_update? statuses.include?(DomainStatus::PENDING_UPDATE) end diff --git a/app/models/domain_cron.rb b/app/models/domain_cron.rb index ad64456ca..e936c29e6 100644 --- a/app/models/domain_cron.rb +++ b/app/models/domain_cron.rb @@ -1,84 +1,17 @@ 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? - - ::PaperTrail.request.whodunnit = "cron - #{__method__}" - expire_at = Setting.expire_pending_confirmation.hours.ago - count = 0 - expired_pending_domains = Domain.where('registrant_verification_asked_at <= ?', expire_at) - expired_pending_domains.each do |domain| - unless domain.pending_update? || domain.pending_delete? || domain.pending_delete_confirmation? - msg = "#{Time.zone.now.utc} - ISSUE: DOMAIN #{domain.id}: #{domain.name} IS IN EXPIRED PENDING LIST, " \ - "but no pendingDelete/pendingUpdate state present!\n" - STDOUT << msg unless Rails.env.test? - next - end - count += 1 - if domain.pending_update? - RegistrantChangeExpiredEmailJob.enqueue(domain.id) - end - if domain.pending_delete? || domain.pending_delete_confirmation? - DomainDeleteMailer.expired(domain).deliver_now - end - - domain.preclean_pendings - domain.clean_pendings! - - unless Rails.env.test? - STDOUT << "#{Time.zone.now.utc} DomainCron.clean_expired_pendings: ##{domain.id} (#{domain.name})\n" - end - UpdateWhoisRecordJob.enqueue domain.name, 'domain' - end - STDOUT << "#{Time.zone.now.utc} - Successfully cancelled #{count} domain pendings\n" unless Rails.env.test? - count + Domains::ExpiredPendings::CleanAll.run! end def self.start_expire_period - ::PaperTrail.request.whodunnit = "cron - #{__method__}" - domains = Domain.expired - marked = 0 - real = 0 - - domains.each do |domain| - next unless domain.expirable? - real += 1 - domain.set_graceful_expired - STDOUT << "#{Time.zone.now.utc} DomainCron.start_expire_period: ##{domain.id} (#{domain.name}) #{domain.changes}\n" unless Rails.env.test? - - send_time = domain.valid_to + Setting.expiration_reminder_mail.to_i.days - saved = domain.save(validate: false) - - if saved - DomainExpireEmailJob.enqueue(domain.id, run_at: send_time) - marked += 1 - end - end - - STDOUT << "#{Time.zone.now.utc} - Successfully expired #{marked} of #{real} domains\n" unless Rails.env.test? + Domains::ExpirePeriod::Start.run! end def self.start_redemption_grace_period - STDOUT << "#{Time.zone.now.utc} - Setting server_hold to domains\n" unless Rails.env.test? + Domains::RedemptionGracePeriod::Start.run! + end - ::PaperTrail.request.whodunnit = "cron - #{__method__}" - - domains = Domain.outzone_candidates - marked = 0 - real = 0 - - domains.each do |domain| - next unless domain.server_holdable? - real += 1 - domain.statuses << DomainStatus::SERVER_HOLD - STDOUT << "#{Time.zone.now.utc} DomainCron.start_redemption_grace_period: ##{domain.id} (#{domain.name}) #{domain.changes}\n" unless Rails.env.test? - domain.save(validate: false) and marked += 1 - end - - STDOUT << "#{Time.zone.now.utc} - Successfully set server_hold to #{marked} of #{real} domains\n" unless Rails.env.test? - marked + def self.start_client_hold + Domains::ClientHold::SetClientHold.run! end end diff --git a/app/models/registrant_verification.rb b/app/models/registrant_verification.rb index 097f0cfa9..4e8e6afe9 100644 --- a/app/models/registrant_verification.rb +++ b/app/models/registrant_verification.rb @@ -18,24 +18,24 @@ class RegistrantVerification < ApplicationRecord def domain_registrant_change_confirm!(initiator) self.action_type = DOMAIN_REGISTRANT_CHANGE self.action = CONFIRMED - DomainUpdateConfirmJob.enqueue domain.id, CONFIRMED, initiator if save + DomainUpdateConfirmJob.perform_later domain.id, CONFIRMED, initiator if save end def domain_registrant_change_reject!(initiator) self.action_type = DOMAIN_REGISTRANT_CHANGE self.action = REJECTED - DomainUpdateConfirmJob.run domain.id, REJECTED, initiator if save + DomainUpdateConfirmJob.perform_later domain.id, REJECTED, initiator if save end def domain_registrant_delete_confirm!(initiator) self.action_type = DOMAIN_DELETE self.action = CONFIRMED - DomainDeleteConfirmJob.enqueue domain.id, CONFIRMED, initiator if save + DomainDeleteConfirmJob.perform_later domain.id, CONFIRMED, initiator if save end def domain_registrant_delete_reject!(initiator) self.action_type = DOMAIN_DELETE self.action = REJECTED - DomainDeleteConfirmJob.enqueue domain.id, REJECTED, initiator if save + DomainDeleteConfirmJob.perform_later domain.id, REJECTED, initiator if save end end diff --git a/app/models/repp_api.rb b/app/models/repp_api.rb new file mode 100644 index 000000000..a752a15af --- /dev/null +++ b/app/models/repp_api.rb @@ -0,0 +1,33 @@ +class ReppApi + def self.bulk_renew(domains, period, registrar) + payload = { domains: domains, renew_period: period } + uri = URI.parse("#{ENV['repp_url']}domains/renew/bulk") + req = Net::HTTP::Post.new(uri, 'Content-Type' => 'application/json') + req.body = payload.to_json + + ReppApi.request(req, uri, registrar: registrar).body + end + + def self.request(request, uri, registrar:) + request.basic_auth(registrar.username, registrar.plain_text_password) if registrar + client_cert = Rails.env.test? ? nil : File.read(ENV['cert_path']) + client_key = Rails.env.test? ? nil : File.read(ENV['key_path']) + params = ReppApi.compose_ca_auth_params(uri, client_cert, client_key) + + Net::HTTP.start(uri.hostname, uri.port, params) do |http| + http.request(request) + end + end + + def self.compose_ca_auth_params(uri, client_cert, client_key) + params = { use_ssl: (uri.scheme == 'https') } + params[:verify_mode] = OpenSSL::SSL::VERIFY_NONE if Rails.env.test? || Rails.env.development? + + unless Rails.env.test? + params[:cert] = OpenSSL::X509::Certificate.new(client_cert) + params[:key] = OpenSSL::PKey::RSA.new(client_key) + end + + params + end +end diff --git a/app/models/tech_domain_contact.rb b/app/models/tech_domain_contact.rb index 04f36c4e4..92799061c 100644 --- a/app/models/tech_domain_contact.rb +++ b/app/models/tech_domain_contact.rb @@ -5,19 +5,20 @@ class TechDomainContact < DomainContact skipped_domains = [] tech_contacts = where(contact: current_contact) - transaction do - tech_contacts.each do |tech_contact| - if tech_contact.domain.discarded? - skipped_domains << tech_contact.domain.name - next - end - + tech_contacts.each do |tech_contact| + if tech_contact.domain.discarded? + skipped_domains << tech_contact.domain.name + next + end + begin tech_contact.contact = new_contact tech_contact.save! affected_domains << tech_contact.domain.name + rescue ActiveRecord::RecordNotUnique + skipped_domains << tech_contact.domain.name end end - return affected_domains.sort, skipped_domains.sort + [affected_domains.sort, skipped_domains.sort] end end diff --git a/app/views/admin/base/_menu.haml b/app/views/admin/base/_menu.haml index a327419fd..5853bd3e6 100644 --- a/app/views/admin/base/_menu.haml +++ b/app/views/admin/base/_menu.haml @@ -33,6 +33,7 @@ %li= link_to t('.blocked_domains'), admin_blocked_domains_path %li= link_to t('.reserved_domains'), admin_reserved_domains_path %li= link_to t('.disputed_domains'), admin_disputes_path + %li= link_to t('.bounced_email_addresses'), admin_bounced_mail_addresses_path %li= link_to t('.epp_log'), admin_epp_logs_path(created_after: 'today') %li= link_to t('.repp_log'), admin_repp_logs_path(created_after: 'today') %li= link_to t('.que'), '/admin/que' diff --git a/app/views/admin/bounced_mail_addresses/index.html.erb b/app/views/admin/bounced_mail_addresses/index.html.erb new file mode 100644 index 000000000..913cbd19d --- /dev/null +++ b/app/views/admin/bounced_mail_addresses/index.html.erb @@ -0,0 +1,36 @@ +

Bounced Mail Addresses

+ +
+
+
+
+ + + + + + + + + + + + + + <% @bounced_mail_addresses.each do |mail_addr| %> + + + + + + + + + + <% end %> + +
EmailActionStatusDiagnosticTrackedActions
<%= mail_addr.email %><%= mail_addr.action %><%= mail_addr.status %><%= mail_addr.diagnostic %><%= mail_addr.created_at %><%= link_to 'Detailed', admin_bounced_mail_address_path(mail_addr) %><%= link_to 'Destroy', admin_bounced_mail_address_path(mail_addr), method: :delete, data: { confirm: 'Are you sure?' } %>
+
+
+
+
diff --git a/app/views/admin/bounced_mail_addresses/show.html.erb b/app/views/admin/bounced_mail_addresses/show.html.erb new file mode 100644 index 000000000..5183ae5a1 --- /dev/null +++ b/app/views/admin/bounced_mail_addresses/show.html.erb @@ -0,0 +1,27 @@ +

+ Email: + <%= @bounced_mail_address.email %> +

+ +

+ Bounced message ID: + <%= @bounced_mail_address.message_id %> +

+ +

+ Overall bounce type: + <%= @bounced_mail_address.bounce_type %> (<%= @bounced_mail_address.bounce_subtype %> ) +

+ +

+ Bounced recipient status: + <%= @bounced_mail_address.action %> (<%= @bounced_mail_address.status %>) +

+ +

+ Bounced recipient diagnostic: +

<%= @bounced_mail_address.diagnostic %>
+

+ +<%= link_to 'Back', admin_bounced_mail_addresses_path %> +<%= link_to 'Destroy', admin_bounced_mail_address_path(@bounced_mail_address), method: :delete, data: { confirm: 'Are you sure?' } %> diff --git a/app/views/registrar/bulk_change/_api_errors.html.erb b/app/views/registrar/bulk_change/_api_errors.html.erb new file mode 100644 index 000000000..8d8862959 --- /dev/null +++ b/app/views/registrar/bulk_change/_api_errors.html.erb @@ -0,0 +1,13 @@ +<% if @api_errors %> +
+ +
+<% end %> diff --git a/app/views/registrar/bulk_change/_bulk_renew_form.html.erb b/app/views/registrar/bulk_change/_bulk_renew_form.html.erb new file mode 100644 index 000000000..5db40365f --- /dev/null +++ b/app/views/registrar/bulk_change/_bulk_renew_form.html.erb @@ -0,0 +1,57 @@ +<%= form_with url: registrar_bulk_renew_path, multipart: true, class: 'form-horizontal' do |f|%> + <%= render 'api_errors' %> + +
+
+ <%= f.label :current_balance, t('.current_balance') %> +
+
+ <%= number_to_currency current_registrar_user.registrar.balance %> +
+
+ + +
+
+ <%= f.label :expire_date, t('.expire_date') %> +
+
+ <%= f.text_field :expire_date, value: @expire_date, + class: 'form-control js-datepicker'%> +
+
+ +
+
+ <%= f.label :period, t('.period'), class: 'required' %> +
+
+ <%= select_tag 'period', + options_for_select(Depp::Domain::PERIODS, @period), { class: 'form-control' } %> +
+
+ + <% if @domains.present? %> +
+
+ <%= f.label :domain_ids, t('.domain_ids') %> +
+
+ <%= f.collection_check_boxes :domain_ids, @domains, :name, :name, + checked: @domains.map(&:name) do |b|%> +
+ <%= b.check_box %> + <%= b.label %> +
+ <% end %> +
+
+ <% end %> + +
+ <%= f.submit "#{t '.filter_btn'}", name: 'filter', class: 'btn btn-warning' %> + <% if @domains.present? %> + <%= f.submit "#{t '.renew_btn'}", name: 'renew', class: 'btn btn-primary' %> + <% end %> +
+<% end %> diff --git a/app/views/registrar/bulk_change/_tech_contact_form.html.erb b/app/views/registrar/bulk_change/_tech_contact_form.html.erb index dc0693599..2848e3634 100644 --- a/app/views/registrar/bulk_change/_tech_contact_form.html.erb +++ b/app/views/registrar/bulk_change/_tech_contact_form.html.erb @@ -1,7 +1,7 @@ <%= form_tag registrar_tech_contacts_path, method: :patch, class: 'form-horizontal' do %> <% if @error %>
- <%= @error[:message] %> + <%= @error %>
<% end %> diff --git a/app/views/registrar/bulk_change/new.html.erb b/app/views/registrar/bulk_change/new.html.erb index da85899ff..e61270b6d 100644 --- a/app/views/registrar/bulk_change/new.html.erb +++ b/app/views/registrar/bulk_change/new.html.erb @@ -19,6 +19,10 @@
  • <%= t '.bulk_transfer' %>
  • + +
  • + <%= t '.bulk_renew' %> +
  • @@ -34,4 +38,8 @@
    <%= render 'bulk_transfer_form' %>
    + +
    + <%= render 'bulk_renew_form' %> +
    diff --git a/app/views/registrar/domain_transfers/form/_api_errors.html.erb b/app/views/registrar/domain_transfers/form/_api_errors.html.erb index 56bf8c404..8d8862959 100644 --- a/app/views/registrar/domain_transfers/form/_api_errors.html.erb +++ b/app/views/registrar/domain_transfers/form/_api_errors.html.erb @@ -1,9 +1,13 @@ <% if @api_errors %>
    <% end %> diff --git a/config/application.yml.sample b/config/application.yml.sample index 82b45feb9..5885c47a2 100644 --- a/config/application.yml.sample +++ b/config/application.yml.sample @@ -87,6 +87,9 @@ sk_digi_doc_service_name: 'Testimine' registrant_api_base_url: registrant_api_auth_allowed_ips: '127.0.0.1, 0.0.0.0' #ips, separated with commas +# Bounces API +api_shared_key: testkey + # Base URL (inc. https://) of REST registrant portal # Leave blank to use internal registrant portal registrant_portal_verifications_base_url: '' diff --git a/config/database_travis.yml b/config/database_travis.yml index b79e2c453..48ffd5e27 100644 --- a/config/database_travis.yml +++ b/config/database_travis.yml @@ -4,7 +4,7 @@ default: &default encoding: unicode pool: 5 username: postgres - password: + password: password test: <<: *default diff --git a/config/locales/admin/menu.en.yml b/config/locales/admin/menu.en.yml index 617341c6a..cb1060e6f 100644 --- a/config/locales/admin/menu.en.yml +++ b/config/locales/admin/menu.en.yml @@ -14,6 +14,7 @@ en: blocked_domains: Blocked domains reserved_domains: Reserved domains disputed_domains: Disputed domains + bounced_email_addresses: Bounced emails epp_log: EPP log repp_log: REPP log que: Que diff --git a/config/locales/en.yml b/config/locales/en.yml index 8b7b4f0fa..22f29a6e3 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -588,6 +588,9 @@ en: create_bank_transaction: 'Create bank transaction' create_new_invoice: 'Create new invoice' billing_failure_credit_balance_low: 'Billing failure - credit balance low' + billing_failure_credit_balance_low_for_domain: 'Billing failure - credit balance low for %{domain}' + domain_renew_error_for_domain: 'Domain renew error for %{domain}' + not_enough_funds: 'Not enough funds for renew domains' create: 'Create' activity_type: 'Activity type' receipt_date_from: 'Receipt date from' @@ -602,6 +605,7 @@ en: notes: Notes active_price_for_this_operation_is: 'Active price for this operation is %{price}' active_price_missing_for_this_operation: 'Active price missing for this operation!' + active_price_missing_for_operation_with_domain: 'Active price missing for operation with %{domain}' valid_to_from: 'Valid to from' valid_to_until: 'Valid to until' registrant_ident: 'Registrant ident' @@ -662,3 +666,4 @@ en: iban: IBAN sign_in: "Sign in" signed_in_successfully: "Signed in successfully" + bulk_renew_completed: "Bulk renew for domains completed" diff --git a/config/locales/registrar/bulk_change.en.yml b/config/locales/registrar/bulk_change.en.yml index 7c1f11da9..75becfada 100644 --- a/config/locales/registrar/bulk_change.en.yml +++ b/config/locales/registrar/bulk_change.en.yml @@ -6,6 +6,7 @@ en: technical_contact: Technical contact nameserver: Nameserver bulk_transfer: Bulk transfer + bulk_renew: Bulk renew tech_contact_form: current_contact_id: Current contact ID @@ -29,3 +30,11 @@ en: submit_btn: Transfer help_btn: Toggle help help: Transfer domains in the csv file with correct transfer code to this registrar + + bulk_renew_form: + filter_btn: Filter + renew_btn: Renew + expire_date: Expire date until + domain_ids: Domains for bulk renewal + current_balance: Current balance + period: Period diff --git a/config/locales/registrar/domain_transfers.en.yml b/config/locales/registrar/domain_transfers.en.yml index c146a5fd1..fd39e54f7 100644 --- a/config/locales/registrar/domain_transfers.en.yml +++ b/config/locales/registrar/domain_transfers.en.yml @@ -6,7 +6,7 @@ en: create: header: Domain transfer - transferred: "%{count} domains have been successfully transferred" + transferred: "%{count} domains have been successfully transferred. Failed domains: %{failed}" form: submit_btn: Transfer diff --git a/config/routes.rb b/config/routes.rb index e2e5bccd5..3e0a84334 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -64,6 +64,7 @@ Rails.application.routes.draw do get ':id/transfer_info', to: 'domains#transfer_info', constraints: { id: /.*/ } post 'transfer', to: 'domains#transfer' patch 'contacts', to: 'domains/contacts#update' + post 'renew/bulk', to: 'domains/renews#bulk_renew' end end end @@ -90,7 +91,7 @@ Rails.application.routes.draw do end resources :auctions, only: %i[index show update], param: :uuid - + resources :bounces, only: %i[create] end match '*all', controller: 'cors', action: 'cors_preflight_check', via: [:options], @@ -133,6 +134,7 @@ Rails.application.routes.draw do end resources :domain_transfers, only: %i[new create] resource :bulk_change, controller: :bulk_change, only: :new + post '/bulk_renew/new', to: 'bulk_change#bulk_renew', as: :bulk_renew resource :tech_contacts, only: :update resource :nameservers, only: :update resources :contacts, constraints: {:id => /[^\/]+(?=#{ ActionController::Renderers::RENDERERS.map{|e| "\\.#{e}\\z"}.join("|") })|[^\/]+/} do @@ -320,6 +322,7 @@ Rails.application.routes.draw do resources :delayed_jobs resources :epp_logs resources :repp_logs + resources :bounced_mail_addresses, only: %i[index show destroy] authenticate :admin_user do mount Que::Web, at: 'que' diff --git a/db/migrate/20200916125326_create_bounced_mail_addresses.rb b/db/migrate/20200916125326_create_bounced_mail_addresses.rb new file mode 100644 index 000000000..e1744cc9a --- /dev/null +++ b/db/migrate/20200916125326_create_bounced_mail_addresses.rb @@ -0,0 +1,15 @@ +class CreateBouncedMailAddresses < ActiveRecord::Migration[6.0] + def change + create_table :bounced_mail_addresses do |t| + t.string :email, null: false + t.string :message_id, null: false + t.string :bounce_type, null: false + t.string :bounce_subtype, null: false + t.string :action, null: false + t.string :status, null: false + t.string :diagnostic, null: true + + t.timestamps + end + end +end diff --git a/db/structure.sql b/db/structure.sql index 6a30fbc84..acf134a55 100644 --- a/db/structure.sql +++ b/db/structure.sql @@ -475,6 +475,43 @@ CREATE SEQUENCE public.blocked_domains_id_seq ALTER SEQUENCE public.blocked_domains_id_seq OWNED BY public.blocked_domains.id; +-- +-- Name: bounced_mail_addresses; Type: TABLE; Schema: public; Owner: -; Tablespace: +-- + +CREATE TABLE public.bounced_mail_addresses ( + id bigint NOT NULL, + email character varying NOT NULL, + message_id character varying NOT NULL, + bounce_type character varying NOT NULL, + bounce_subtype character varying NOT NULL, + action character varying NOT NULL, + status character varying NOT NULL, + diagnostic character varying, + created_at timestamp(6) without time zone NOT NULL, + updated_at timestamp(6) without time zone NOT NULL +); + + +-- +-- Name: bounced_mail_addresses_id_seq; Type: SEQUENCE; Schema: public; Owner: - +-- + +CREATE SEQUENCE public.bounced_mail_addresses_id_seq + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + + +-- +-- Name: bounced_mail_addresses_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: - +-- + +ALTER SEQUENCE public.bounced_mail_addresses_id_seq OWNED BY public.bounced_mail_addresses.id; + + -- -- Name: certificates; Type: TABLE; Schema: public; Owner: -; Tablespace: -- @@ -2658,6 +2695,13 @@ ALTER TABLE ONLY public.bank_transactions ALTER COLUMN id SET DEFAULT nextval('p ALTER TABLE ONLY public.blocked_domains ALTER COLUMN id SET DEFAULT nextval('public.blocked_domains_id_seq'::regclass); +-- +-- Name: id; Type: DEFAULT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.bounced_mail_addresses ALTER COLUMN id SET DEFAULT nextval('public.bounced_mail_addresses_id_seq'::regclass); + + -- -- Name: id; Type: DEFAULT; Schema: public; Owner: - -- @@ -2876,14 +2920,14 @@ ALTER TABLE ONLY public.log_payment_orders ALTER COLUMN id SET DEFAULT nextval(' -- --- Name: log_prices id; Type: DEFAULT; Schema: public; Owner: - +-- Name: id; Type: DEFAULT; Schema: public; Owner: - -- ALTER TABLE ONLY public.log_prices ALTER COLUMN id SET DEFAULT nextval('public.log_prices_id_seq'::regclass); -- --- Name: log_registrant_verifications id; Type: DEFAULT; Schema: public; Owner: - +-- Name: id; Type: DEFAULT; Schema: public; Owner: - -- ALTER TABLE ONLY public.log_registrant_verifications ALTER COLUMN id SET DEFAULT nextval('public.log_registrant_verifications_id_seq'::regclass); @@ -3100,6 +3144,14 @@ ALTER TABLE ONLY public.blocked_domains ADD CONSTRAINT blocked_domains_pkey PRIMARY KEY (id); +-- +-- Name: bounced_mail_addresses_pkey; Type: CONSTRAINT; Schema: public; Owner: -; Tablespace: +-- + +ALTER TABLE ONLY public.bounced_mail_addresses + ADD CONSTRAINT bounced_mail_addresses_pkey PRIMARY KEY (id); + + -- -- Name: certificates_pkey; Type: CONSTRAINT; Schema: public; Owner: -; Tablespace: -- @@ -3349,7 +3401,7 @@ ALTER TABLE ONLY public.log_payment_orders -- --- Name: log_prices log_prices_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- Name: log_prices_pkey; Type: CONSTRAINT; Schema: public; Owner: - -- ALTER TABLE ONLY public.log_prices @@ -3357,7 +3409,7 @@ ALTER TABLE ONLY public.log_prices -- --- Name: log_registrant_verifications log_registrant_verifications_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- Name: log_registrant_verifications_pkey; Type: CONSTRAINT; Schema: public; Owner: - -- ALTER TABLE ONLY public.log_registrant_verifications @@ -4906,5 +4958,7 @@ INSERT INTO "schema_migrations" (version) VALUES ('20200902131603'), ('20200908131554'), ('20200910085157'), -('20200910102028'); +('20200910102028'), +('20200916125326'); + diff --git a/lib/serializers/registrant_api/contact.rb b/lib/serializers/registrant_api/contact.rb index dd36b4400..023544174 100644 --- a/lib/serializers/registrant_api/contact.rb +++ b/lib/serializers/registrant_api/contact.rb @@ -1,14 +1,15 @@ module Serializers module RegistrantApi class Contact - attr_reader :contact + attr_reader :contact, :links - def initialize(contact) + def initialize(contact, links) @contact = contact + @links = links end - def to_json - { + def to_json(_obj = nil) + obj = { id: contact.uuid, name: contact.name, code: contact.code, @@ -31,6 +32,10 @@ module Serializers statuses: contact.statuses, disclosed_attributes: contact.disclosed_attributes, } + + obj[:links] = contact.related_domains if @links + + obj end end end diff --git a/lib/serializers/registrant_api/domain.rb b/lib/serializers/registrant_api/domain.rb index 542f2d0de..2359d77c9 100644 --- a/lib/serializers/registrant_api/domain.rb +++ b/lib/serializers/registrant_api/domain.rb @@ -3,11 +3,14 @@ module Serializers class Domain attr_reader :domain - def initialize(domain) + def initialize(domain, simplify: false) @domain = domain + @simplify = simplify end - def to_json + def to_json(_obj = nil) + return simple_object if @simplify + { id: domain.uuid, name: domain.name, @@ -49,6 +52,17 @@ module Serializers private + def simple_object + { + id: domain.uuid, name: domain.name, registered_at: domain.registered_at, + valid_to: domain.valid_to, outzone_at: domain.outzone_at, statuses: domain.statuses, + registrant_verification_asked_at: domain.registrant_verification_asked_at, + registrar: { name: domain.registrar.name, website: domain.registrar.website }, + registrant: { name: domain.registrant.name, id: domain.registrant.uuid, + phone: domain.registrant.phone, email: domain.registrant.email } + } + end + def dnssec_keys domain.dnskeys.map do |key| "#{key.flags} #{key.protocol} #{key.alg} #{key.public_key}" diff --git a/test/fixtures/bounced_mail_addresses.yml b/test/fixtures/bounced_mail_addresses.yml new file mode 100644 index 000000000..cbfdff680 --- /dev/null +++ b/test/fixtures/bounced_mail_addresses.yml @@ -0,0 +1,10 @@ +one: + email: bounced@registry.test + message_id: 010f0174a0c7d348-ea6e2fc1-0854-4073-b71f-5cecf9b0d0b2-000000 + bounce_type: Permanent + bounce_subtype: General + action: failed + status: '5.1.1' + diagnostic: 'smtp; 550 5.1.1 user unknown' + created_at: <%= Time.zone.parse('2010-07-05').to_s(:db) %> + updated_at: <%= Time.zone.parse('2010-07-05').to_s(:db) %> diff --git a/test/integration/api/registrant/registrant_api_domains_test.rb b/test/integration/api/registrant/registrant_api_domains_test.rb index 22516fecc..61d635e5f 100644 --- a/test/integration/api/registrant/registrant_api_domains_test.rb +++ b/test/integration/api/registrant/registrant_api_domains_test.rb @@ -50,10 +50,10 @@ class RegistrantApiDomainsTest < ApplicationIntegrationTest assert_equal(200, response.status) response_json = JSON.parse(response.body, symbolize_names: true) - array_of_domain_names = response_json.map { |x| x[:name] } + array_of_domain_names = response_json[:domains].map { |x| x[:name] } assert(array_of_domain_names.include?('hospital.test')) - array_of_domain_registrars = response_json.map { |x| x[:registrar] } + array_of_domain_registrars = response_json[:domains].map { |x| x[:registrar] } assert(array_of_domain_registrars.include?({name: 'Good Names', website: nil})) end @@ -63,12 +63,12 @@ class RegistrantApiDomainsTest < ApplicationIntegrationTest response_json = JSON.parse(response.body, symbolize_names: true) assert_equal(200, response.status) - assert_equal(2, response_json.count) + assert_equal(2, response_json[:domains].count) get '/api/v1/registrant/domains', headers: @auth_headers response_json = JSON.parse(response.body, symbolize_names: true) - assert_equal(4, response_json.count) + assert_equal(4, response_json[:domains].count) end def test_root_does_not_accept_limit_higher_than_200 diff --git a/test/integration/api/v1/bounces/create_test.rb b/test/integration/api/v1/bounces/create_test.rb new file mode 100644 index 000000000..899b6c5c7 --- /dev/null +++ b/test/integration/api/v1/bounces/create_test.rb @@ -0,0 +1,75 @@ +require 'test_helper' + +class BouncesApiV1CreateTest < ActionDispatch::IntegrationTest + def setup + @api_key = "Basic #{ENV['api_shared_key']}" + @headers = { "Authorization": "#{@api_key}" } + @json_body = { "data": valid_bounce_request }.as_json + end + + def test_authorizes_api_request + post api_v1_bounces_path, params: @json_body, headers: @headers + assert_response :created + + invalid_headers = { "Authorization": "Basic invalid_api_key" } + post api_v1_bounces_path, params: @json_body, headers: invalid_headers + assert_response :unauthorized + end + + def test_returns_bad_request_if_invalid_payload + invalid_json_body = @json_body.dup + invalid_json_body['data']['bounce']['bouncedRecipients'] = nil + + post api_v1_bounces_path, params: invalid_json_body, headers: @headers + assert_response :bad_request + + invalid_json_body = 'aaaa' + post api_v1_bounces_path, params: invalid_json_body, headers: @headers + assert_response :bad_request + end + + def test_saves_new_bounce_object + request_body = @json_body.dup + random_mail = "#{rand(10000..99999)}@registry.test" + request_body['data']['bounce']['bouncedRecipients'][0]['emailAddress'] = random_mail + + post api_v1_bounces_path, params: request_body, headers: @headers + assert_response :created + + bounced_mail = BouncedMailAddress.last + assert bounced_mail.email = random_mail + assert '5.1.1', bounced_mail.status + assert 'failed', bounced_mail.action + end + + def valid_bounce_request + { + "notificationType": "Bounce", + "mail": { + "source": "noreply@registry.test", + "sourceIp": "195.43.86.5", + "messageId": "010f0174a0c7d348-ea6e2fc1-0854-4073-b71f-5cecf9b0d0b2-000000", + "sourceArn": "arn:aws:ses:us-east-2:65026820000:identity/noreply@registry.test", + "timestamp": "2020-09-18T10:34:44.000Z", + "destination": [ "bounced@registry.test" ], + "sendingAccountId": "650268220000" + }, + "bounce": { + "timestamp": "2020-09-18T10:34:44.911Z", + "bounceType": "Permanent", + "feedbackId": "010f0174a0c7d4f9-27d59756-6111-4d5f-xxxx-26bee0d55fa2-000000", + "remoteMtaIp": "127.0.01", + "reportingMTA": "dsn; xxx.amazonses.com", + "bounceSubType": "General", + "bouncedRecipients": [ + { + "action": "failed", + "status": "5.1.1", + "emailAddress": "bounced@registry.test", + "diagnosticCode": "smtp; 550 5.1.1 user unknown" + } + ] + } + }.as_json + end +end diff --git a/test/integration/epp/domain/delete/base_test.rb b/test/integration/epp/domain/delete/base_test.rb index bfdfa9f75..56a3cc31e 100644 --- a/test/integration/epp/domain/delete/base_test.rb +++ b/test/integration/epp/domain/delete/base_test.rb @@ -35,7 +35,6 @@ class EppDomainDeleteBaseTest < EppTestCase XML post epp_delete_path, params: { frame: request_xml }, headers: { 'HTTP_COOKIE' => 'session=api_bestnames' } - # binding.pry assert_includes Domain.find_by(name: 'invalid.test').statuses, DomainStatus::PENDING_DELETE_CONFIRMATION assert_epp_response :completed_successfully_action_pending end @@ -90,7 +89,9 @@ class EppDomainDeleteBaseTest < EppTestCase XML - post epp_delete_path, params: { frame: request_xml }, headers: { 'HTTP_COOKIE' => 'session=api_bestnames' } + perform_enqueued_jobs do + post epp_delete_path, params: { frame: request_xml }, headers: { 'HTTP_COOKIE' => 'session=api_bestnames' } + end @domain.reload assert @domain.registrant_verification_asked? @@ -121,7 +122,9 @@ class EppDomainDeleteBaseTest < EppTestCase XML - post epp_delete_path, params: { frame: request_xml }, headers: { 'HTTP_COOKIE' => 'session=api_bestnames' } + perform_enqueued_jobs do + post epp_delete_path, params: { frame: request_xml }, headers: { 'HTTP_COOKIE' => 'session=api_bestnames' } + end @domain.reload assert_not @domain.registrant_verification_asked? @@ -152,7 +155,9 @@ class EppDomainDeleteBaseTest < EppTestCase XML - post epp_delete_path, params: { frame: request_xml }, headers: { 'HTTP_COOKIE' => 'session=api_bestnames' } + perform_enqueued_jobs do + post epp_delete_path, params: { frame: request_xml }, headers: { 'HTTP_COOKIE' => 'session=api_bestnames' } + end @domain.reload assert_not @domain.registrant_verification_asked? diff --git a/test/integration/epp/domain/update/base_test.rb b/test/integration/epp/domain/update/base_test.rb index 6ce455948..14e806fca 100644 --- a/test/integration/epp/domain/update/base_test.rb +++ b/test/integration/epp/domain/update/base_test.rb @@ -503,6 +503,30 @@ class EppDomainUpdateBaseTest < EppTestCase assert_not_includes(@domain.statuses, DomainStatus::CLIENT_HOLD) end + def test_update_domain_returns_error_when_removing_unassigned_status + assert_not_includes(@domain.statuses, DomainStatus::CLIENT_HOLD) + request_xml = <<-XML + + + + + + #{@domain.name} + + + + + + + + XML + + post epp_update_path, params: { frame: request_xml }, + headers: { 'HTTP_COOKIE' => 'session=api_bestnames' } + @domain.reload + assert_epp_response :object_does_not_exist + end + private def assert_verification_and_notification_emails diff --git a/test/integration/repp/v1/domains/bulk_renew_test.rb b/test/integration/repp/v1/domains/bulk_renew_test.rb new file mode 100644 index 000000000..4cec91914 --- /dev/null +++ b/test/integration/repp/v1/domains/bulk_renew_test.rb @@ -0,0 +1,91 @@ +require 'test_helper' + +class ReppV1DomainsBulkRenewTest < ActionDispatch::IntegrationTest + def setup + travel_to Time.zone.parse('2010-07-05 10:30') + @user = users(:api_bestnames) + token = Base64.encode64("#{@user.username}:#{@user.plain_text_password}") + token = "Basic #{token}" + + @auth_headers = { 'Authorization' => token } + end + + def test_renews_domains + payload = { + "domains": [ + 'shop.test', + 'airport.test', + 'library.test' + ], + "renew_period": "1y" + } + + assert_changes -> { Domain.find_by(name: 'shop.test').valid_to } do + assert_changes -> { Domain.find_by(name: 'airport.test').valid_to } do + assert_changes -> { Domain.find_by(name: 'library.test').valid_to } do + post "/repp/v1/domains/renew/bulk", headers: @auth_headers, params: payload + json = JSON.parse(response.body, symbolize_names: true) + + assert_response :ok + assert_equal 1000, json[:code] + assert_equal 'Command completed successfully', json[:message] + assert json[:data][:updated_domains].include? 'shop.test' + assert json[:data][:updated_domains].include? 'airport.test' + assert json[:data][:updated_domains].include? 'library.test' + end + end + end + end + + def test_throws_error_when_domain_not_renewable + payload = { + "domains": [ + 'invalid.test', + ], + "renew_period": "1y" + } + assert_no_changes -> { Domain.find_by(name: 'invalid.test').valid_to } do + post "/repp/v1/domains/renew/bulk", headers: @auth_headers, params: payload + json = JSON.parse(response.body, symbolize_names: true) + + assert_response :bad_request + assert_equal 2002, json[:code] + assert_equal 'Domain renew error for invalid.test', json[:message] + end + end + + def test_throws_error_when_not_enough_balance + billing_prices(:renew_one_year).update(price_cents: 99999999) + payload = { + "domains": [ + 'invalid.test', + ], + "renew_period": "1y" + } + + assert_no_changes -> { Domain.find_by(name: 'invalid.test').valid_to } do + post "/repp/v1/domains/renew/bulk", headers: @auth_headers, params: payload + json = JSON.parse(response.body, symbolize_names: true) + + assert_response :bad_request + assert_equal 2002, json[:code] + assert_equal 'Not enough funds for renew domains', json[:message] + end + end + + def test_throws_error_if_invalid_renew_period + payload = { + "domains": [ + 'shop.test' + ], + "renew_period": "nope" + } + + post "/repp/v1/domains/renew/bulk", headers: @auth_headers, params: payload + json = JSON.parse(response.body, symbolize_names: true) + + assert_response :bad_request + assert_equal 2005, json[:code] + assert_equal 'Invalid renew period', json[:message] + end +end diff --git a/test/interactions/domain_delete_interaction/domain_delete_test.rb b/test/interactions/domain_delete_interaction/domain_delete_test.rb new file mode 100644 index 000000000..9582fae03 --- /dev/null +++ b/test/interactions/domain_delete_interaction/domain_delete_test.rb @@ -0,0 +1,25 @@ +require 'test_helper' + +class DomainDeleteTest < ActiveSupport::TestCase + setup do + @domain = domains(:shop) + end + + def test_discards_domains_with_past_delete_date + @domain.update!(delete_date: '2010-07-04') + travel_to Time.zone.parse('2010-07-05') + + Domains::Delete::DoDelete.run(domain: @domain) + + assert @domain.destroyed? + end + + def test_sends_notification + @domain.update!(delete_date: '2010-07-04') + travel_to Time.zone.parse('2010-07-05') + + assert_difference '@domain.registrar.notifications.count', 1 do + Domains::Delete::DoDelete.run(domain: @domain) + end + end +end diff --git a/test/interactions/expire_period/start_test.rb b/test/interactions/expire_period/start_test.rb new file mode 100644 index 000000000..168b255bb --- /dev/null +++ b/test/interactions/expire_period/start_test.rb @@ -0,0 +1,28 @@ +require 'test_helper' + +class StartTest < ActiveSupport::TestCase + include ActionMailer::TestHelper + + setup do + @domain = domains(:shop) + @domain.update(expire_time: Time.zone.now - 1.day) + ActionMailer::Base.deliveries.clear + end + + def test_sets_expired + job_count = lambda do + QueJob.where("args->>0 = '#{@domain.id}'", job_class: DomainExpireEmailJob.name).count + end + + assert_difference job_count, 1 do + perform_enqueued_jobs do + DomainCron.start_expire_period + end + end + + @domain.reload + assert @domain.statuses.include?(DomainStatus::EXPIRED) + assert_equal @domain.outzone_at, @domain.expire_time + Domain.expire_warning_period + assert_equal @domain.delete_date, (@domain.outzone_at + Domain.redemption_grace_period).to_date + end +end diff --git a/test/interactions/redemption_grace_period/start_test.rb b/test/interactions/redemption_grace_period/start_test.rb new file mode 100644 index 000000000..8958030f0 --- /dev/null +++ b/test/interactions/redemption_grace_period/start_test.rb @@ -0,0 +1,25 @@ +require 'test_helper' + +class StartTest < ActiveSupport::TestCase + + setup do + @domain = domains(:shop) + @domain.update(outzone_time: Time.zone.now - 1.day) + end + + def test_sets_server_hold + DomainCron.start_redemption_grace_period + + @domain.reload + assert @domain.statuses.include?(DomainStatus::SERVER_HOLD) + end + + def test_doesnt_sets_server_hold_if_not_outzone + @domain.update(outzone_time: nil) + @domain.reload + DomainCron.start_redemption_grace_period + + @domain.reload + assert_not @domain.statuses.include?(DomainStatus::SERVER_HOLD) + end +end diff --git a/test/jobs/domain_delete_confirm_job_test.rb b/test/jobs/domain_delete_confirm_job_test.rb index b999bd3c7..cbe4b87f1 100644 --- a/test/jobs/domain_delete_confirm_job_test.rb +++ b/test/jobs/domain_delete_confirm_job_test.rb @@ -18,7 +18,7 @@ class DomainDeleteConfirmJobTest < ActiveSupport::TestCase new_registrant_email: @new_registrant.email, current_user_id: @user.id }) - DomainDeleteConfirmJob.enqueue(@domain.id, RegistrantVerification::REJECTED) + DomainDeleteConfirmJob.perform_now(@domain.id, RegistrantVerification::REJECTED) last_registrar_notification = @domain.registrar.notifications.last assert_equal(last_registrar_notification.attached_obj_id, @domain.id) @@ -31,7 +31,7 @@ class DomainDeleteConfirmJobTest < ActiveSupport::TestCase new_registrant_email: @new_registrant.email, current_user_id: @user.id }) - DomainDeleteConfirmJob.enqueue(@domain.id, RegistrantVerification::CONFIRMED) + DomainDeleteConfirmJob.perform_now(@domain.id, RegistrantVerification::CONFIRMED) last_registrar_notification = @domain.registrar.notifications.last assert_equal(last_registrar_notification.attached_obj_id, @domain.id) @@ -51,7 +51,7 @@ class DomainDeleteConfirmJobTest < ActiveSupport::TestCase assert @domain.registrant_delete_confirmable?(@domain.registrant_verification_token) assert_equal @user.id, @domain.pending_json['current_user_id'] - DomainDeleteConfirmJob.enqueue(@domain.id, RegistrantVerification::CONFIRMED) + DomainDeleteConfirmJob.perform_now(@domain.id, RegistrantVerification::CONFIRMED) @domain.reload assert @domain.statuses.include? DomainStatus::PENDING_DELETE @@ -72,7 +72,7 @@ class DomainDeleteConfirmJobTest < ActiveSupport::TestCase assert @domain.registrant_delete_confirmable?(@domain.registrant_verification_token) assert_equal @user.id, @domain.pending_json['current_user_id'] - DomainDeleteConfirmJob.enqueue(@domain.id, RegistrantVerification::REJECTED) + DomainDeleteConfirmJob.perform_now(@domain.id, RegistrantVerification::REJECTED) @domain.reload assert_equal ['ok'], @domain.statuses diff --git a/test/jobs/domain_update_confirm_job_test.rb b/test/jobs/domain_update_confirm_job_test.rb index a452daf0e..eee8ba578 100644 --- a/test/jobs/domain_update_confirm_job_test.rb +++ b/test/jobs/domain_update_confirm_job_test.rb @@ -21,7 +21,7 @@ class DomainUpdateConfirmJobTest < ActiveSupport::TestCase end def test_rejected_registrant_verification_notifies_registrar - DomainUpdateConfirmJob.enqueue(@domain.id, RegistrantVerification::REJECTED) + DomainUpdateConfirmJob.perform_now(@domain.id, RegistrantVerification::REJECTED) last_registrar_notification = @domain.registrar.notifications.last assert_equal(last_registrar_notification.attached_obj_id, @domain.id) @@ -29,7 +29,7 @@ class DomainUpdateConfirmJobTest < ActiveSupport::TestCase end def test_accepted_registrant_verification_notifies_registrar - DomainUpdateConfirmJob.enqueue(@domain.id, RegistrantVerification::CONFIRMED) + DomainUpdateConfirmJob.perform_now(@domain.id, RegistrantVerification::CONFIRMED) last_registrar_notification = @domain.registrar.notifications.last assert_equal(last_registrar_notification.attached_obj_id, @domain.id) @@ -47,10 +47,11 @@ class DomainUpdateConfirmJobTest < ActiveSupport::TestCase @domain.update(pending_json: @domain.pending_json) @domain.reload - DomainUpdateConfirmJob.enqueue(@domain.id, RegistrantVerification::CONFIRMED) + DomainUpdateConfirmJob.perform_now(@domain.id, RegistrantVerification::CONFIRMED) @domain.reload assert_equal @domain.registrant.code, @new_registrant.code + assert @domain.statuses.include? DomainStatus::OK end def test_clears_pending_update_after_denial @@ -63,10 +64,92 @@ class DomainUpdateConfirmJobTest < ActiveSupport::TestCase @domain.pending_json['frame'] = parsed_frame @domain.update(pending_json: @domain.pending_json) - DomainUpdateConfirmJob.enqueue(@domain.id, RegistrantVerification::REJECTED) + DomainUpdateConfirmJob.perform_now(@domain.id, RegistrantVerification::REJECTED) @domain.reload assert_not @domain.statuses.include? DomainStatus::PENDING_DELETE_CONFIRMATION assert_not @domain.statuses.include? DomainStatus::PENDING_DELETE end + + def test_protects_statuses_after_denial + epp_xml = "\n\n \n \n \n #{@domain.name}\n" \ + " \n #{@new_registrant.code}\n \n \n \n \n \n" \ + " \n #{@legal_doc_path}\n \n" \ + " \n 20alla-1594199756\n \n\n" + parsed_frame = Deserializers::Xml::DomainUpdate.new(Nokogiri::XML(epp_xml), @domain.registrar.id).call + + @domain.pending_json['frame'] = parsed_frame + @domain.update(pending_json: @domain.pending_json) + @domain.update(statuses: [DomainStatus::DELETE_CANDIDATE, DomainStatus::DISPUTED]) + + DomainUpdateConfirmJob.perform_now(@domain.id, RegistrantVerification::REJECTED) + @domain.reload + + assert_not @domain.statuses.include? DomainStatus::PENDING_DELETE_CONFIRMATION + assert_not @domain.statuses.include? DomainStatus::PENDING_DELETE + assert @domain.statuses.include? DomainStatus::DELETE_CANDIDATE + assert @domain.statuses.include? DomainStatus::DISPUTED + end + + def test_protects_statuses_after_confirm + epp_xml = "\n\n \n \n \n #{@domain.name}\n" \ + " \n #{@new_registrant.code}\n \n \n \n \n \n" \ + " \n #{@legal_doc_path}\n \n" \ + " \n 20alla-1594199756\n \n\n" + parsed_frame = Deserializers::Xml::DomainUpdate.new(Nokogiri::XML(epp_xml), @domain.registrar.id).call + + @domain.pending_json['frame'] = parsed_frame + @domain.update(pending_json: @domain.pending_json) + @domain.update(statuses: [DomainStatus::DELETE_CANDIDATE, DomainStatus::DISPUTED]) + + DomainUpdateConfirmJob.perform_now(@domain.id, RegistrantVerification::CONFIRMED) + @domain.reload + + assert_not @domain.statuses.include? DomainStatus::PENDING_DELETE_CONFIRMATION + assert_not @domain.statuses.include? DomainStatus::PENDING_DELETE + assert @domain.statuses.include? DomainStatus::DELETE_CANDIDATE + assert @domain.statuses.include? DomainStatus::DISPUTED + end + + def test_clears_pending_update_and_inactive_after_denial + epp_xml = "\n\n \n \n \n #{@domain.name}\n" \ + " \n #{@new_registrant.code}\n \n \n \n \n \n" \ + " \n #{@legal_doc_path}\n \n" \ + " \n 20alla-1594199756\n \n\n" + parsed_frame = Deserializers::Xml::DomainUpdate.new(Nokogiri::XML(epp_xml), @domain.registrar.id).call + + @domain.pending_json['frame'] = parsed_frame + @domain.update(pending_json: @domain.pending_json) + @domain.update(statuses: [DomainStatus::PENDING_UPDATE]) + @domain.nameservers.destroy_all + @domain.reload + + DomainUpdateConfirmJob.perform_now(@domain.id, RegistrantVerification::REJECTED) + @domain.reload + + assert_not @domain.statuses.include? DomainStatus::PENDING_DELETE_CONFIRMATION + assert_not @domain.statuses.include? DomainStatus::PENDING_DELETE + assert_not @domain.statuses.include? DomainStatus::PENDING_UPDATE + assert @domain.statuses.include? DomainStatus::INACTIVE + end + + def test_clears_pending_update_and_sets_ok_after_denial + epp_xml = "\n\n \n \n \n #{@domain.name}\n" \ + " \n #{@new_registrant.code}\n \n \n \n \n \n" \ + " \n #{@legal_doc_path}\n \n" \ + " \n 20alla-1594199756\n \n\n" + parsed_frame = Deserializers::Xml::DomainUpdate.new(Nokogiri::XML(epp_xml), @domain.registrar.id).call + + @domain.pending_json['frame'] = parsed_frame + @domain.update(pending_json: @domain.pending_json) + @domain.update(statuses: [DomainStatus::OK, DomainStatus::PENDING_UPDATE]) + + DomainUpdateConfirmJob.perform_now(@domain.id, RegistrantVerification::REJECTED) + @domain.reload + + assert_not @domain.statuses.include? DomainStatus::PENDING_DELETE_CONFIRMATION + assert_not @domain.statuses.include? DomainStatus::PENDING_DELETE + assert_not @domain.statuses.include? DomainStatus::PENDING_UPDATE + assert @domain.statuses.include? DomainStatus::OK + end end diff --git a/test/lib/serializers/registrant_api/contact_test.rb b/test/lib/serializers/registrant_api/contact_test.rb index 165c91e00..8b84abd38 100644 --- a/test/lib/serializers/registrant_api/contact_test.rb +++ b/test/lib/serializers/registrant_api/contact_test.rb @@ -4,7 +4,7 @@ require 'serializers/registrant_api/contact' class SerializersRegistrantApiContactTest < ActiveSupport::TestCase def setup @contact = contacts(:william) - @serializer = Serializers::RegistrantApi::Contact.new(@contact) + @serializer = Serializers::RegistrantApi::Contact.new(@contact, false) @json = @serializer.to_json end diff --git a/test/models/bounced_mail_address_test.rb b/test/models/bounced_mail_address_test.rb new file mode 100644 index 000000000..4af401711 --- /dev/null +++ b/test/models/bounced_mail_address_test.rb @@ -0,0 +1,104 @@ +require 'test_helper' + +class BouncedMailAddressTest < ActiveSupport::TestCase + include ActionMailer::TestHelper + + def setup + @bounced_mail = BouncedMailAddress.new + @bounced_mail.email = 'recipient@registry.test' + @bounced_mail.message_id = '010f0174a0c7d348-ea6e2fc1-0854-4073-b71f-5cecf9b0d0b2-000000' + @bounced_mail.bounce_type = 'Permanent' + @bounced_mail.bounce_subtype = 'General' + @bounced_mail.action = 'failed' + @bounced_mail.status = '5.1.1' + @bounced_mail.diagnostic = 'smtp; 550 5.1.1 user unknown' + end + + def test_email_is_required + assert @bounced_mail.valid? + @bounced_mail.email = nil + assert @bounced_mail.invalid? + end + + def test_message_id_is_required + assert @bounced_mail.valid? + @bounced_mail.message_id = nil + assert @bounced_mail.invalid? + end + + def test_bounce_type_is_required + assert @bounced_mail.valid? + @bounced_mail.bounce_type = nil + assert @bounced_mail.invalid? + end + + def test_bounce_subtype_is_required + assert @bounced_mail.valid? + @bounced_mail.bounce_subtype = nil + assert @bounced_mail.invalid? + end + + def test_action_is_required + assert @bounced_mail.valid? + @bounced_mail.action = nil + assert @bounced_mail.invalid? + end + + def test_status_is_required + assert @bounced_mail.valid? + @bounced_mail.status = nil + assert @bounced_mail.invalid? + end + + def test_diagnostic_is_not_required + assert @bounced_mail.valid? + @bounced_mail.diagnostic = nil + assert @bounced_mail.valid? + end + + def test_bounce_reason_is_determined_dynamically + assert @bounced_mail.valid? + assert_equal 'failed (5.1.1 smtp; 550 5.1.1 user unknown)', @bounced_mail.bounce_reason + end + + def test_creates_objects_from_sns_json + BouncedMailAddress.record(sns_bounce_payload) + + bounced_mail = BouncedMailAddress.last + assert_equal domains(:shop).registrant.email, bounced_mail.email + assert_equal 'failed', bounced_mail.action + assert_equal '5.1.1', bounced_mail.status + assert_equal 'smtp; 550 5.1.1 user unknown', bounced_mail.diagnostic + end + + def sns_bounce_payload + { + "notificationType": "Bounce", + "mail": { + "source": "noreply@registry.test", + "sourceIp": "195.43.86.5", + "messageId": "010f0174a0c7d348-ea6e2fc1-0854-4073-b71f-5cecf9b0d0b2-000000", + "sourceArn": "arn:aws:ses:us-east-2:65026820000:identity/noreply@registry.test", + "timestamp": "2020-09-18T10:34:44.000Z", + "destination": [ "#{domains(:shop).registrant.email}" ], + "sendingAccountId": "650268220000" + }, + "bounce": { + "timestamp": "2020-09-18T10:34:44.911Z", + "bounceType": "Permanent", + "feedbackId": "010f0174a0c7d4f9-27d59756-6111-4d5f-xxxx-26bee0d55fa2-000000", + "remoteMtaIp": "127.0.01", + "reportingMTA": "dsn; xxx.amazonses.com", + "bounceSubType": "General", + "bouncedRecipients": [ + { + "action": "failed", + "status": "5.1.1", + "emailAddress": "#{domains(:shop).registrant.email}", + "diagnosticCode": "smtp; 550 5.1.1 user unknown" + } + ] + } + }.as_json + end +end diff --git a/test/models/domain/force_delete_test.rb b/test/models/domain/force_delete_test.rb index 7c205fa74..2bb1b5b8f 100644 --- a/test/models/domain/force_delete_test.rb +++ b/test/models/domain/force_delete_test.rb @@ -113,7 +113,7 @@ class ForceDeleteTest < ActionMailer::TestCase def test_force_delete_cannot_be_scheduled_when_a_domain_is_discarded @domain.update!(statuses: [DomainStatus::DELETE_CANDIDATE]) - result = ForceDeleteInteraction::SetForceDelete.run(domain: @domain, type: :fast_track) + result = Domains::ForceDelete::SetForceDelete.run(domain: @domain, type: :fast_track) assert_not result.valid? assert_not @domain.force_delete_scheduled? @@ -206,9 +206,10 @@ class ForceDeleteTest < ActionMailer::TestCase @domain.schedule_force_delete(type: :soft) travel_to Time.zone.parse('2010-08-21') - DomainCron.start_client_hold + Domains::ClientHold::SetClientHold.run! @domain.reload + assert_emails 1 assert_equal(@domain.purge_date.to_date, @domain.force_delete_date.to_date) assert_equal(@domain.outzone_date.to_date, @domain.force_delete_start.to_date + Setting.expire_warning_period.days) @@ -226,8 +227,10 @@ class ForceDeleteTest < ActionMailer::TestCase @domain.schedule_force_delete(type: :soft) travel_to Time.zone.parse('2010-08-21') - DomainCron.start_client_hold + Domains::ClientHold::SetClientHold.run! @domain.reload + + assert_emails 1 assert_includes(@domain.statuses, asserted_status) end @@ -241,7 +244,7 @@ class ForceDeleteTest < ActionMailer::TestCase @domain.schedule_force_delete(type: :soft) travel_to Time.zone.parse('2010-07-06') - DomainCron.start_client_hold + Domains::ClientHold::SetClientHold.run! @domain.reload assert_not_includes(@domain.statuses, asserted_status) @@ -256,7 +259,7 @@ class ForceDeleteTest < ActionMailer::TestCase @domain.schedule_force_delete(type: :fast_track) travel_to Time.zone.parse('2010-07-25') - DomainCron.start_client_hold + Domains::ClientHold::SetClientHold.run! @domain.reload assert_includes(@domain.statuses, asserted_status) @@ -272,7 +275,7 @@ class ForceDeleteTest < ActionMailer::TestCase @domain.schedule_force_delete(type: :fast_track) travel_to Time.zone.parse('2010-07-06') - DomainCron.start_client_hold + Domains::ClientHold::SetClientHold.run! @domain.reload assert_not_includes(@domain.statuses, asserted_status) diff --git a/test/models/domain_cron_test.rb b/test/models/domain_cron_test.rb index 0224b1a61..c417df04f 100644 --- a/test/models/domain_cron_test.rb +++ b/test/models/domain_cron_test.rb @@ -19,7 +19,9 @@ class DomainCronTest < ActiveSupport::TestCase registrant_verification_token: 'test', statuses: [DomainStatus::PENDING_DELETE_CONFIRMATION]) - DomainCron.clean_expired_pendings + perform_enqueued_jobs do + DomainCron.clean_expired_pendings + end assert_emails 1 end @@ -84,7 +86,9 @@ class DomainCronTest < ActiveSupport::TestCase assert @domain.pending_update? @domain.reload - DomainCron.clean_expired_pendings + perform_enqueued_jobs do + DomainCron.clean_expired_pendings + end @domain.reload assert_not @domain.pending_update? diff --git a/test/models/domain_test.rb b/test/models/domain_test.rb index cc88cf35f..ae12f4a1e 100644 --- a/test/models/domain_test.rb +++ b/test/models/domain_test.rb @@ -414,7 +414,7 @@ class DomainTest < ActiveSupport::TestCase force_delete_date: nil) @domain.update(template_name: 'legal_person') travel_to Time.zone.parse('2010-07-05') - ForceDeleteInteraction::SetForceDelete.run!(domain: @domain, type: :fast_track) + Domains::ForceDelete::SetForceDelete.run!(domain: @domain, type: :fast_track) assert(@domain.force_delete_scheduled?) other_registrant = Registrant.find_by(code: 'jane-001') @domain.pending_json['new_registrant_id'] = other_registrant.id diff --git a/test/models/whois/record_test.rb b/test/models/whois/record_test.rb index e900a4965..d06b23cae 100644 --- a/test/models/whois/record_test.rb +++ b/test/models/whois/record_test.rb @@ -70,6 +70,6 @@ class Whois::RecordTest < ActiveSupport::TestCase end def registration_deadline - Time.zone.now + 10.days + @registration_deadline ||= Time.zone.now + 10.days end end diff --git a/test/system/admin_area/bounced_mail_addresses_test.rb b/test/system/admin_area/bounced_mail_addresses_test.rb new file mode 100644 index 000000000..36b81f0f8 --- /dev/null +++ b/test/system/admin_area/bounced_mail_addresses_test.rb @@ -0,0 +1,40 @@ +require 'application_system_test_case' + +class AdminBouncedMailAddressesTest < ApplicationSystemTestCase + include ActionView::Helpers::NumberHelper + + def setup + @bounced_mail = bounced_mail_addresses(:one) + @original_default_language = Setting.default_language + sign_in users(:admin) + end + + def teardown + Setting.default_language = @original_default_language + end + + def test_shows_bounced_emails + visit admin_bounced_mail_addresses_path + assert_text @bounced_mail.status + assert_text @bounced_mail.action + assert_text @bounced_mail.diagnostic + assert_text @bounced_mail.email + end + + def test_shows_detailed_bounced_email + visit admin_bounced_mail_address_path(@bounced_mail) + assert_text @bounced_mail.status + assert_text @bounced_mail.action + assert_text @bounced_mail.diagnostic + assert_text @bounced_mail.email + + assert_text @bounced_mail.message_id + end + + def test_deletes_registrar + visit admin_bounced_mail_address_path(@bounced_mail) + click_on 'Destroy' + + assert_text 'Bounced mail address was successfully destroyed.' + end +end diff --git a/test/system/registrant_area/domains/domain_delete_confirms_test.rb b/test/system/registrant_area/domains/domain_delete_confirms_test.rb index 0eb61ada8..765cd0149 100644 --- a/test/system/registrant_area/domains/domain_delete_confirms_test.rb +++ b/test/system/registrant_area/domains/domain_delete_confirms_test.rb @@ -1,6 +1,7 @@ require 'application_system_test_case' class DomainDeleteConfirmsTest < ApplicationSystemTestCase + include ActionMailer::TestHelper setup do @user = users(:registrant) sign_in @user @@ -13,7 +14,9 @@ class DomainDeleteConfirmsTest < ApplicationSystemTestCase def test_enqueues_approve_job_after_verification visit registrant_domain_delete_confirm_url(@domain.id, token: @domain.registrant_verification_token) - click_on 'Confirm domain delete' + perform_enqueued_jobs do + click_on 'Confirm domain delete' + end assert_text 'Domain registrant change has successfully received.' @domain.reload @@ -23,7 +26,9 @@ class DomainDeleteConfirmsTest < ApplicationSystemTestCase def test_enqueues_reject_job_after_verification visit registrant_domain_delete_confirm_url(@domain.id, token: @domain.registrant_verification_token) - click_on 'Reject domain delete' + perform_enqueued_jobs do + click_on 'Reject domain delete' + end assert_text 'Domain registrant change has been rejected successfully.' @domain.reload diff --git a/test/system/registrar_area/bulk_change/bulk_renew_test.rb b/test/system/registrar_area/bulk_change/bulk_renew_test.rb new file mode 100644 index 000000000..5e1704afd --- /dev/null +++ b/test/system/registrar_area/bulk_change/bulk_renew_test.rb @@ -0,0 +1,70 @@ +require 'application_system_test_case' + +class BulkRenewTest < ApplicationSystemTestCase + setup do + @registrar = users(:api_bestnames).registrar + @price = billing_prices(:renew_one_year) + end + + def test_shows_domain_list + sign_in users(:api_bestnames) + travel_to Time.zone.parse('2010-07-05 10:30') + + visit new_registrar_bulk_change_url + click_link('Bulk renew') + assert_text 'Current balance' + page.has_css?('#registrar_balance', text: + ApplicationController.helpers.number_to_currency(@registrar.balance)) + + select '1 year', from: 'Period' + click_button 'Filter' + + @registrar.domains.pluck(:name).each do |domain_name| + assert_text domain_name + end + end + + def test_makes_bulk_renew + sign_in users(:api_bestnames) + travel_to Time.zone.parse('2010-07-05 10:30') + + req_body = { domains: ["shop.test", "airport.test", "library.test", "invalid.test"], renew_period: "1y" } + stub_request(:post, "#{ENV['repp_url']}domains/renew/bulk").with(body: req_body) + .to_return(status: 400, body: { + code: 2304, + message: "Domain renew error for invalid.test", + data: {} + }.to_json) + + visit new_registrar_bulk_change_url + click_link('Bulk renew') + select '1 year', from: 'Period' + click_button 'Filter' + click_button 'Renew' + + assert_text 'Domain renew error for invalid.test' + end + + def test_bulk_renew_checks_balance + sign_in users(:api_bestnames) + @price.update(price_cents: 99999999) + travel_to Time.zone.parse('2010-07-05 10:30') + + req_body = { domains: ["shop.test", "airport.test", "library.test", "invalid.test"], renew_period: "1y" } + stub_request(:post, "#{ENV['repp_url']}domains/renew/bulk").with(body: req_body) + .to_return(status: 400, body: { + code: 2304, + message: "Not enough funds for renew domains", + data: {} + }.to_json) + + visit new_registrar_bulk_change_url + click_link('Bulk renew') + select '1 year', from: 'Period' + click_button 'Filter' + click_button 'Renew' + + assert_text 'Not enough funds for renew domains' + + end +end diff --git a/test/system/registrar_area/bulk_change/bulk_transfer_test.rb b/test/system/registrar_area/bulk_change/bulk_transfer_test.rb index a531ff4dc..820b1cf96 100644 --- a/test/system/registrar_area/bulk_change/bulk_transfer_test.rb +++ b/test/system/registrar_area/bulk_change/bulk_transfer_test.rb @@ -11,9 +11,9 @@ class RegistrarAreaBulkTransferTest < ApplicationSystemTestCase request_stub = stub_request(:post, /domains\/transfer/).with(body: request_body, headers: headers, basic_auth: ['test_goodnames', 'testtest']) - .to_return(body: { data: [{ - type: 'domain_transfer' - }] }.to_json, status: 200) + .to_return(body: { data: { success: [{ type: 'domain_transfer', domain_name: 'shop.test' }], + failed: [] + } }.to_json, status: 200) visit registrar_domains_url click_link 'Bulk change' @@ -27,7 +27,7 @@ class RegistrarAreaBulkTransferTest < ApplicationSystemTestCase end def test_fail_gracefully - body = { errors: [{ title: 'epic fail' }] }.to_json + body = { message: 'epic fail' }.to_json headers = { 'Content-type' => Mime[:json] } stub_request(:post, /domains\/transfer/).to_return(status: 400, body: body, headers: headers) diff --git a/test/system/registrar_area/bulk_change/nameserver_test.rb b/test/system/registrar_area/bulk_change/nameserver_test.rb index 3d4b4dc70..48806df7a 100644 --- a/test/system/registrar_area/bulk_change/nameserver_test.rb +++ b/test/system/registrar_area/bulk_change/nameserver_test.rb @@ -15,10 +15,12 @@ class RegistrarAreaNameserverBulkChangeTest < ApplicationSystemTestCase request_stub = stub_request(:put, /registrar\/nameservers/).with(body: request_body, headers: { 'Content-type' => Mime[:json] }, basic_auth: ['test_goodnames', 'testtest']) - .to_return(body: { data: [{ - type: 'nameserver', - id: 'new-ns.bestnames.test'}], - affected_domains: ["airport.test", "shop.test"]}.to_json, status: 200) + .to_return(body: { data: { + type: 'nameserver', + id: 'new-ns.bestnames.test', + affected_domains: ["airport.test", "shop.test"] + } + }.to_json, status: 200) visit registrar_domains_url click_link 'Bulk change' @@ -38,7 +40,7 @@ class RegistrarAreaNameserverBulkChangeTest < ApplicationSystemTestCase def test_fails_gracefully stub_request(:put, /registrar\/nameservers/).to_return(status: 400, - body: { errors: [{ title: 'epic fail' }] }.to_json, + body: { message: 'epic fail' }.to_json, headers: { 'Content-type' => Mime[:json] }) visit registrar_domains_url diff --git a/test/system/registrar_area/bulk_change/tech_contact_test.rb b/test/system/registrar_area/bulk_change/tech_contact_test.rb index c678e8f34..e08457f60 100644 --- a/test/system/registrar_area/bulk_change/tech_contact_test.rb +++ b/test/system/registrar_area/bulk_change/tech_contact_test.rb @@ -9,8 +9,8 @@ class RegistrarAreaTechContactBulkChangeTest < ApplicationSystemTestCase request_stub = stub_request(:patch, /domains\/contacts/) .with(body: { current_contact_id: 'william-001', new_contact_id: 'john-001' }, basic_auth: ['test_bestnames', 'testtest']) - .to_return(body: { affected_domains: %w[foo.test bar.test], - skipped_domains: %w[baz.test qux.test] }.to_json, + .to_return(body: { data: { affected_domains: %w[foo.test bar.test], + skipped_domains: %w[baz.test qux.test] } }.to_json, status: 200) visit registrar_domains_url @@ -30,7 +30,7 @@ class RegistrarAreaTechContactBulkChangeTest < ApplicationSystemTestCase def test_fails_gracefully stub_request(:patch, /domains\/contacts/) .to_return(status: 400, - body: { error: { message: 'epic fail' } }.to_json, + body: { message: 'epic fail' }.to_json, headers: { 'Content-type' => Mime[:json] }) visit registrar_domains_url diff --git a/test/test_helper.rb b/test/test_helper.rb index 459d4f8f5..a1634f717 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -6,6 +6,7 @@ if ENV['COVERAGE'] add_filter '/lib/core_monkey_patches/' add_filter '/lib/daemons/' add_filter '/lib/gem_monkey_patches/' + add_filter '/lib/tasks/' end end