diff --git a/app/api/repp/api.rb b/app/api/repp/api.rb index 9c12470a0..7858cd625 100644 --- a/app/api/repp/api.rb +++ b/app/api/repp/api.rb @@ -59,5 +59,6 @@ module Repp mount Repp::AccountV1 mount Repp::DomainTransfersV1 mount Repp::NameserversV1 + mount Repp::DomainContactsV1 end end diff --git a/app/api/repp/domain_contacts_v1.rb b/app/api/repp/domain_contacts_v1.rb new file mode 100644 index 000000000..7f3e323ac --- /dev/null +++ b/app/api/repp/domain_contacts_v1.rb @@ -0,0 +1,47 @@ +module Repp + class DomainContactsV1 < Grape::API + version 'v1', using: :path + + resource :domains do + resource :contacts do + patch '/' do + current_contact = current_user.registrar.contacts + .find_by(code: params[:current_contact_id]) + new_contact = current_user.registrar.contacts.find_by(code: params[:new_contact_id]) + + unless current_contact + error!({ error: { type: 'invalid_request_error', + param: 'current_contact_id', + message: "No such contact: #{params[:current_contact_id]}"} }, + :bad_request) + end + + unless new_contact + error!({ error: { type: 'invalid_request_error', + param: 'new_contact_id', + message: "No such contact: #{params[:new_contact_id]}" } }, + :bad_request) + end + + if new_contact.invalid? + error!({ error: { type: 'invalid_request_error', + param: 'new_contact_id', + message: 'New contact must be valid' } }, + :bad_request) + end + + if current_contact == new_contact + error!({ error: { type: 'invalid_request_error', + message: 'New contact ID must be different from current' \ + ' contact ID' } }, + :bad_request) + end + + affected_domains, skipped_domains = TechDomainContact + .replace(current_contact, new_contact) + @response = { affected_domains: affected_domains, skipped_domains: skipped_domains } + end + end + end + end +end diff --git a/app/assets/images/every_pay.png b/app/assets/images/every_pay.png new file mode 100644 index 000000000..fcd4a2c67 Binary files /dev/null and b/app/assets/images/every_pay.png differ diff --git a/app/assets/javascripts/popover.js b/app/assets/javascripts/popover.js new file mode 100644 index 000000000..2f8ed57b5 --- /dev/null +++ b/app/assets/javascripts/popover.js @@ -0,0 +1,9 @@ +(function() { + function initPopover() { + $(function () { + $('[data-toggle="popover"]').popover(); + }) + } + + initPopover(); +})(); diff --git a/app/assets/javascripts/registrar-manifest.coffee b/app/assets/javascripts/registrar-manifest.coffee index 5c4a58df6..f83d9f76e 100644 --- a/app/assets/javascripts/registrar-manifest.coffee +++ b/app/assets/javascripts/registrar-manifest.coffee @@ -7,6 +7,8 @@ #= require select2 #= require datepicker #= require spell_check +#= require popover +#= require text_field_trimmer #= require shared/general #= require registrar/autocomplete #= require registrar/application diff --git a/app/assets/javascripts/text_field_trimmer.js b/app/assets/javascripts/text_field_trimmer.js new file mode 100644 index 000000000..71cbc5295 --- /dev/null +++ b/app/assets/javascripts/text_field_trimmer.js @@ -0,0 +1,15 @@ +(function () { + function trimTextFields() { + let selector = 'input[type=text], input[type=search], input[type=email], textarea'; + let textFields = document.querySelectorAll(selector); + let listener = function () { + this.value = this.value.trim(); + }; + + for (let field of textFields) { + field.addEventListener('change', listener); + } + } + + trimTextFields(); +})(); diff --git a/app/controllers/registrar/bulk_change_controller.rb b/app/controllers/registrar/bulk_change_controller.rb new file mode 100644 index 000000000..562344a46 --- /dev/null +++ b/app/controllers/registrar/bulk_change_controller.rb @@ -0,0 +1,20 @@ +class Registrar + class BulkChangeController < DeppController + helper_method :available_contacts + + def new + authorize! :manage, :repp + render file: 'registrar/bulk_change/new', locals: { active_tab: default_tab } + end + + private + + def available_contacts + current_user.registrar.contacts.order(:name).pluck(:name, :code) + end + + def default_tab + :technical_contact + end + end +end diff --git a/app/controllers/registrar/domain_transfers_controller.rb b/app/controllers/registrar/domain_transfers_controller.rb index 65127155e..7c0925f03 100644 --- a/app/controllers/registrar/domain_transfers_controller.rb +++ b/app/controllers/registrar/domain_transfers_controller.rb @@ -1,5 +1,5 @@ class Registrar - class DomainTransfersController < DeppController + class DomainTransfersController < BulkChangeController before_action do authorize! :transfer, Depp::Domain end @@ -58,7 +58,7 @@ class Registrar redirect_to registrar_domains_url else @api_errors = parsed_response[:errors] - render :new + render file: 'registrar/bulk_change/new', locals: { active_tab: :bulk_transfer } end else params[:request] = true # EPP domain:transfer "op" attribute diff --git a/app/controllers/registrar/registrar_nameservers_controller.rb b/app/controllers/registrar/nameservers_controller.rb similarity index 94% rename from app/controllers/registrar/registrar_nameservers_controller.rb rename to app/controllers/registrar/nameservers_controller.rb index 1af3cde64..b6f7af829 100644 --- a/app/controllers/registrar/registrar_nameservers_controller.rb +++ b/app/controllers/registrar/nameservers_controller.rb @@ -1,9 +1,5 @@ class Registrar - class RegistrarNameserversController < DeppController - def edit - authorize! :manage, :repp - end - + class NameserversController < BulkChangeController def update authorize! :manage, :repp @@ -52,7 +48,7 @@ class Registrar redirect_to registrar_domains_url else @api_errors = parsed_response[:errors] - render :edit + render file: 'registrar/bulk_change/new', locals: { active_tab: :nameserver } end end end diff --git a/app/controllers/registrar/payments_controller.rb b/app/controllers/registrar/payments_controller.rb index 18c892ea7..57565b9c2 100644 --- a/app/controllers/registrar/payments_controller.rb +++ b/app/controllers/registrar/payments_controller.rb @@ -1,30 +1,34 @@ class Registrar class PaymentsController < BaseController - protect_from_forgery except: :back + protect_from_forgery except: [:back, :callback] skip_authorization_check # actually anyone can pay, no problems at all - skip_before_action :authenticate_user!, :check_ip_restriction, only: [:back] - before_action :check_bank + skip_before_action :authenticate_user!, :check_ip_restriction, only: [:back, :callback] + before_action :check_supported_payment_method - # to handle existing model we should - # get invoice_id and then get number - # build BankTransaction without connection with right reference number - # do not connect transaction and invoice def pay invoice = Invoice.find(params[:invoice_id]) - @bank_link = BankLink::Request.new(params[:bank], invoice, self) - @bank_link.make_transaction + bank = params[:bank] + opts = { + return_url: registrar_return_payment_with_url( + bank, invoice_id: invoice + ), + response_url: registrar_response_payment_with_url( + bank, invoice_id: invoice + ) + } + @payment = ::PaymentOrders.create_with_type(bank, invoice, opts) + @payment.create_transaction end - - # connect invoice and transaction - # both back and IPN def back - @bank_link = BankLink::Response.new(params[:bank], params) - if @bank_link.valid? && @bank_link.ok? - @bank_link.complete_payment + invoice = Invoice.find(params[:invoice_id]) + opts = { response: params } + @payment = ::PaymentOrders.create_with_type(params[:bank], invoice, opts) + if @payment.valid_response_from_intermediary? && @payment.settled_payment? + @payment.complete_transaction - if @bank_link.invoice.binded? + if invoice.binded? flash[:notice] = t(:pending_applied) else flash[:alert] = t(:something_wrong) @@ -32,17 +36,31 @@ class Registrar else flash[:alert] = t(:something_wrong) end - redirect_to registrar_invoice_path(@bank_link.invoice) + redirect_to registrar_invoice_path(invoice) + end + + def callback + invoice = Invoice.find(params[:invoice_id]) + opts = { response: params } + @payment = ::PaymentOrders.create_with_type(params[:bank], invoice, opts) + + if @payment.valid_response_from_intermediary? && @payment.settled_payment? + @payment.complete_transaction + end + + render status: 200, json: { status: 'ok' } end private - def banks - ENV['payments_banks'].split(",").map(&:strip) + def check_supported_payment_method + return if supported_payment_method? + raise StandardError.new("Not supported payment method") end - def check_bank - raise StandardError.new("Not Implemented bank") unless banks.include?(params[:bank]) + + def supported_payment_method? + PaymentOrders::PAYMENT_METHODS.include?(params[:bank]) end end end diff --git a/app/controllers/registrar/tech_contacts_controller.rb b/app/controllers/registrar/tech_contacts_controller.rb new file mode 100644 index 000000000..9d4568ad6 --- /dev/null +++ b/app/controllers/registrar/tech_contacts_controller.rb @@ -0,0 +1,59 @@ +class Registrar + class TechContactsController < BulkChangeController + def update + authorize! :manage, :repp + + uri = URI.parse("#{ENV['repp_url']}domains/contacts") + + request = Net::HTTP::Patch.new(uri) + request.set_form_data(current_contact_id: params[:current_contact_id], + new_contact_id: params[:new_contact_id]) + request.basic_auth(current_user.username, current_user.password) + + if Rails.env.test? + response = Net::HTTP.start(uri.hostname, uri.port, + use_ssl: (uri.scheme == 'https'), + verify_mode: OpenSSL::SSL::VERIFY_NONE) do |http| + http.request(request) + end + elsif Rails.env.development? + client_cert = File.read(ENV['cert_path']) + client_key = File.read(ENV['key_path']) + response = Net::HTTP.start(uri.hostname, uri.port, + use_ssl: (uri.scheme == 'https'), + verify_mode: OpenSSL::SSL::VERIFY_NONE, + cert: OpenSSL::X509::Certificate.new(client_cert), + key: OpenSSL::PKey::RSA.new(client_key)) do |http| + http.request(request) + end + else + client_cert = File.read(ENV['cert_path']) + client_key = File.read(ENV['key_path']) + response = Net::HTTP.start(uri.hostname, uri.port, + use_ssl: (uri.scheme == 'https'), + cert: OpenSSL::X509::Certificate.new(client_cert), + key: OpenSSL::PKey::RSA.new(client_key)) do |http| + http.request(request) + end + end + + parsed_response = JSON.parse(response.body, symbolize_names: true) + + if response.code == '200' + notices = [t('.replaced')] + + notices << "#{t('.affected_domains')}: #{parsed_response[:affected_domains].join(', ')}" + + if parsed_response[:skipped_domains] + notices << "#{t('.skipped_domains')}: #{parsed_response[:skipped_domains].join(', ')}" + end + + flash[:notice] = notices + redirect_to registrar_domains_url + else + @error = parsed_response[:error] + render file: 'registrar/bulk_change/new', locals: { active_tab: :technical_contact } + end + end + end +end diff --git a/app/models/bank_link.rb b/app/models/bank_link.rb deleted file mode 100644 index e388a0f8b..000000000 --- a/app/models/bank_link.rb +++ /dev/null @@ -1,158 +0,0 @@ -class BankLink - module Base - def prepend_size(value) - value = (value || "").to_s.strip - string = "" - string << sprintf("%03i", value.size) - string << value - end - end - - class Request - include Base - include ActionView::Helpers::NumberHelper - - # need controller here in order to handle random ports and domains - # I don't want to do it but has to - attr_accessor :type, :invoice, :controller - def initialize(type, invoice, controller) - @type, @invoice, @controller = type, invoice, controller - end - - def url - ENV["payments_#{type}_url"] - end - - def fields - @fields ||= (hash = {} - hash["VK_SERVICE"] = "1012" - hash["VK_VERSION"] = "008" - hash["VK_SND_ID"] = ENV["payments_#{type}_seller_account"] - hash["VK_STAMP"] = invoice.number - hash["VK_AMOUNT"] = number_with_precision(invoice.total, :precision => 2, :separator => ".") - hash["VK_CURR"] = invoice.currency - hash["VK_REF"] = "" - hash["VK_MSG"] = invoice.order - hash["VK_RETURN"] = controller.registrar_return_payment_with_url(type) - hash["VK_CANCEL"] = controller.registrar_return_payment_with_url(type) - hash["VK_DATETIME"] = Time.now.strftime("%Y-%m-%dT%H:%M:%S%z") - hash["VK_MAC"] = calc_mac(hash) - hash["VK_ENCODING"] = "UTF-8" - hash["VK_LANG"] = "ENG" - hash) - end - - def calc_mac(fields) - pars = %w(VK_SERVICE VK_VERSION VK_SND_ID VK_STAMP VK_AMOUNT VK_CURR VK_REF - VK_MSG VK_RETURN VK_CANCEL VK_DATETIME).freeze - data = pars.map{|e| prepend_size(fields[e]) }.join - - sign(data) - end - - def make_transaction - transaction = BankTransaction.where(description: fields["VK_MSG"]).first_or_initialize( - reference_no: invoice.reference_no, - currency: invoice.currency, - iban: invoice.seller_iban - ) - - transaction.save! - end - - private - def sign(data) - private_key = OpenSSL::PKey::RSA.new(File.read(ENV["payments_#{type}_seller_private"])) - - signed_data = private_key.sign(OpenSSL::Digest::SHA1.new, data) - signed_data = Base64.encode64(signed_data).gsub(/\n|\r/, '') - signed_data - end - end - - - - - class Response - include Base - include ActionView::Helpers::NumberHelper - - attr_accessor :type, :params, :invoice - def initialize(type, params) - @type, @params = type, params - - @invoice = Invoice.find_by(number: params["VK_STAMP"]) if params["VK_STAMP"].present? - end - - def valid? - !!validate - end - - def ok? - params["VK_SERVICE"] == "1111" - end - - def complete_payment - if valid? - transaction = BankTransaction.find_by(description: params["VK_MSG"]) - transaction.sum = BigDecimal.new(params["VK_AMOUNT"].to_s) - transaction.bank_reference = params['VK_T_NO'] - transaction.buyer_bank_code = params["VK_SND_ID"] - transaction.buyer_iban = params["VK_SND_ACC"] - transaction.buyer_name = params["VK_SND_NAME"] - transaction.paid_at = Time.parse(params["VK_T_DATETIME"]) - transaction.save! - - transaction.autobind_invoice - end - end - - - - def validate - case params["VK_SERVICE"] - when "1111" - validate_success && validate_amount && validate_currency - when "1911" - validate_cancel - else - false - end - end - - def validate_success - pars = %w(VK_SERVICE VK_VERSION VK_SND_ID VK_REC_ID VK_STAMP VK_T_NO VK_AMOUNT VK_CURR - VK_REC_ACC VK_REC_NAME VK_SND_ACC VK_SND_NAME VK_REF VK_MSG VK_T_DATETIME).freeze - - @validate_success ||= ( - data = pars.map{|e| prepend_size(params[e]) }.join - verify_mac(data, params["VK_MAC"]) - ) - end - - def validate_cancel - pars = %w(VK_SERVICE VK_VERSION VK_SND_ID VK_REC_ID VK_STAMP VK_REF VK_MSG).freeze - @validate_cancel ||= ( - data = pars.map{|e| prepend_size(params[e]) }.join - verify_mac(data, params["VK_MAC"]) - ) - end - - def validate_amount - source = number_with_precision(BigDecimal.new(params["VK_AMOUNT"].to_s), precision: 2, separator: ".") - target = number_with_precision(invoice.total, precision: 2, separator: ".") - - source == target - end - - def validate_currency - invoice.currency == params["VK_CURR"] - end - - - def verify_mac(data, mac) - bank_public_key = OpenSSL::X509::Certificate.new(File.read(ENV["payments_#{type}_bank_certificate"])).public_key - bank_public_key.verify(OpenSSL::Digest::SHA1.new, Base64.decode64(mac), data) - end - end -end diff --git a/app/models/payment_orders.rb b/app/models/payment_orders.rb new file mode 100644 index 000000000..921af0cd4 --- /dev/null +++ b/app/models/payment_orders.rb @@ -0,0 +1,15 @@ +module PaymentOrders + PAYMENT_INTERMEDIARIES = ENV['payments_intermediaries'].to_s.strip.split(', ').freeze + PAYMENT_BANKLINK_BANKS = ENV['payments_banks'].to_s.strip.split(', ').freeze + PAYMENT_METHODS = [PAYMENT_INTERMEDIARIES, PAYMENT_BANKLINK_BANKS].flatten.freeze + + def self.create_with_type(type, invoice, opts = {}) + raise ArgumentError unless PAYMENT_METHODS.include?(type) + + if PAYMENT_BANKLINK_BANKS.include?(type) + BankLink.new(type, invoice, opts) + elsif type == 'every_pay' + EveryPay.new(type, invoice, opts) + end + end +end diff --git a/app/models/payment_orders/bank_link.rb b/app/models/payment_orders/bank_link.rb new file mode 100644 index 000000000..e568da0df --- /dev/null +++ b/app/models/payment_orders/bank_link.rb @@ -0,0 +1,146 @@ +module PaymentOrders + class BankLink < Base + BANK_LINK_VERSION = '008' + + NEW_TRANSACTION_SERVICE_NUMBER = '1012' + SUCCESSFUL_PAYMENT_SERVICE_NUMBER = '1111' + CANCELLED_PAYMENT_SERVICE_NUMBER = '1911' + + NEW_MESSAGE_KEYS = %w(VK_SERVICE VK_VERSION VK_SND_ID VK_STAMP VK_AMOUNT + VK_CURR VK_REF VK_MSG VK_RETURN VK_CANCEL + VK_DATETIME).freeze + SUCCESS_MESSAGE_KEYS = %w(VK_SERVICE VK_VERSION VK_SND_ID VK_REC_ID VK_STAMP + VK_T_NO VK_AMOUNT VK_CURR VK_REC_ACC VK_REC_NAME + VK_SND_ACC VK_SND_NAME VK_REF VK_MSG + VK_T_DATETIME).freeze + CANCEL_MESSAGE_KEYS = %w(VK_SERVICE VK_VERSION VK_SND_ID VK_REC_ID VK_STAMP + VK_REF VK_MSG).freeze + + def form_fields + hash = {} + hash["VK_SERVICE"] = NEW_TRANSACTION_SERVICE_NUMBER + hash["VK_VERSION"] = BANK_LINK_VERSION + hash["VK_SND_ID"] = seller_account + hash["VK_STAMP"] = invoice.number + hash["VK_AMOUNT"] = number_with_precision(invoice.total, precision: 2, separator: ".") + hash["VK_CURR"] = invoice.currency + hash["VK_REF"] = "" + hash["VK_MSG"] = invoice.order + hash["VK_RETURN"] = return_url + hash["VK_CANCEL"] = return_url + hash["VK_DATETIME"] = Time.zone.now.strftime("%Y-%m-%dT%H:%M:%S%z") + hash["VK_MAC"] = calc_mac(hash) + hash["VK_ENCODING"] = "UTF-8" + hash["VK_LANG"] = "ENG" + hash + end + + def valid_response_from_intermediary? + return false unless response + + case response["VK_SERVICE"] + when SUCCESSFUL_PAYMENT_SERVICE_NUMBER + valid_successful_transaction? + when CANCELLED_PAYMENT_SERVICE_NUMBER + valid_cancel_notice? + else + false + end + end + + def complete_transaction + return unless valid_successful_transaction? + + transaction = BankTransaction.find_by( + description: invoice.order, + currency: invoice.currency, + iban: invoice.seller_iban + ) + + transaction.sum = response['VK_AMOUNT'] + transaction.bank_reference = response['VK_T_NO'] + transaction.buyer_bank_code = response["VK_SND_ID"] + transaction.buyer_iban = response["VK_SND_ACC"] + transaction.buyer_name = response["VK_SND_NAME"] + transaction.paid_at = Time.parse(response["VK_T_DATETIME"]) + + transaction.save! + transaction.autobind_invoice + end + + def settled_payment? + response["VK_SERVICE"] == SUCCESSFUL_PAYMENT_SERVICE_NUMBER + end + + private + + def valid_successful_transaction? + valid_success_notice? && valid_amount? && valid_currency? + end + + def valid_cancel_notice? + valid_mac?(response, CANCEL_MESSAGE_KEYS) + end + + def valid_success_notice? + valid_mac?(response, SUCCESS_MESSAGE_KEYS) + end + + def valid_amount? + source = number_with_precision( + BigDecimal.new(response["VK_AMOUNT"]), precision: 2, separator: "." + ) + target = number_with_precision( + invoice.total, precision: 2, separator: "." + ) + + source == target + end + + def valid_currency? + invoice.currency == response["VK_CURR"] + end + + def sign(data) + private_key = OpenSSL::PKey::RSA.new(File.read(seller_certificate)) + signed_data = private_key.sign(OpenSSL::Digest::SHA1.new, data) + signed_data = Base64.encode64(signed_data).gsub(/\n|\r/, '') + signed_data + end + + def calc_mac(fields) + pars = NEW_MESSAGE_KEYS + data = pars.map { |element| prepend_size(fields[element]) }.join + sign(data) + end + + def valid_mac?(hash, keys) + data = keys.map { |element| prepend_size(hash[element]) }.join + verify_mac(data, hash["VK_MAC"]) + end + + def verify_mac(data, mac) + bank_public_key = OpenSSL::X509::Certificate.new(File.read(bank_certificate)).public_key + bank_public_key.verify(OpenSSL::Digest::SHA1.new, Base64.decode64(mac), data) + end + + def prepend_size(value) + value = (value || "").to_s.strip + string = "" + string << format("%03i", value.size) + string << value + end + + def seller_account + ENV["payments_#{type}_seller_account"] + end + + def seller_certificate + ENV["payments_#{type}_seller_private"] + end + + def bank_certificate + ENV["payments_#{type}_bank_certificate"] + end + end +end diff --git a/app/models/payment_orders/base.rb b/app/models/payment_orders/base.rb new file mode 100644 index 000000000..cf0293025 --- /dev/null +++ b/app/models/payment_orders/base.rb @@ -0,0 +1,33 @@ +module PaymentOrders + class Base + include ActionView::Helpers::NumberHelper + + attr_reader :type, + :invoice, + :return_url, + :response_url, + :response + + def initialize(type, invoice, opts = {}) + @type = type + @invoice = invoice + @return_url = opts[:return_url] + @response_url = opts[:response_url] + @response = opts[:response] + end + + def create_transaction + transaction = BankTransaction.where(description: invoice.order).first_or_initialize( + reference_no: invoice.reference_no, + currency: invoice.currency, + iban: invoice.seller_iban + ) + + transaction.save! + end + + def form_url + ENV["payments_#{type}_url"] + end + end +end diff --git a/app/models/payment_orders/every_pay.rb b/app/models/payment_orders/every_pay.rb new file mode 100644 index 000000000..b4ddcdf29 --- /dev/null +++ b/app/models/payment_orders/every_pay.rb @@ -0,0 +1,84 @@ +module PaymentOrders + class EveryPay < Base + USER = ENV['payments_every_pay_api_user'].freeze + KEY = ENV['payments_every_pay_api_key'].freeze + ACCOUNT_ID = ENV['payments_every_pay_seller_account'].freeze + SUCCESSFUL_PAYMENT = %w(settled authorized).freeze + + def form_fields + base_json = base_params + base_json[:nonce] = SecureRandom.hex(15) + hmac_fields = (base_json.keys + ['hmac_fields']).sort.uniq! + + base_json[:hmac_fields] = hmac_fields.join(',') + hmac_string = hmac_fields.map { |key, _v| "#{key}=#{base_json[key]}" }.join('&') + hmac = OpenSSL::HMAC.hexdigest('sha1', KEY, hmac_string) + base_json[:hmac] = hmac + + base_json + end + + def valid_response_from_intermediary? + return false unless response + valid_hmac? && valid_amount? && valid_account? + end + + def settled_payment? + SUCCESSFUL_PAYMENT.include?(response[:payment_state]) + end + + def complete_transaction + return unless valid_response_from_intermediary? && settled_payment? + + transaction = BankTransaction.find_by( + description: invoice.order, + currency: invoice.currency, + iban: invoice.seller_iban + ) + + transaction.sum = response[:amount] + transaction.paid_at = Date.strptime(response[:timestamp], '%s') + transaction.buyer_name = response[:cc_holder_name] + + transaction.save! + transaction.autobind_invoice + end + + private + + def base_params + { + api_username: USER, + account_id: ACCOUNT_ID, + timestamp: Time.now.to_i.to_s, + callback_url: response_url, + customer_url: return_url, + amount: number_with_precision(invoice.total, precision: 2), + order_reference: SecureRandom.hex(15), + transaction_type: 'charge', + hmac_fields: '' + }.with_indifferent_access + end + + def valid_hmac? + hmac_fields = response[:hmac_fields].split(',') + hmac_hash = {} + hmac_fields.map do |field| + symbol = field.to_sym + hmac_hash[symbol] = response[symbol] + end + + hmac_string = hmac_hash.map { |key, _v| "#{key}=#{hmac_hash[key]}" }.join('&') + expected_hmac = OpenSSL::HMAC.hexdigest('sha1', KEY, hmac_string) + expected_hmac == response[:hmac] + end + + def valid_amount? + invoice.total == BigDecimal.new(response[:amount]) + end + + def valid_account? + response[:account_id] == ACCOUNT_ID + end + end +end diff --git a/app/models/tech_domain_contact.rb b/app/models/tech_domain_contact.rb index 68ae35dde..04f36c4e4 100644 --- a/app/models/tech_domain_contact.rb +++ b/app/models/tech_domain_contact.rb @@ -1,2 +1,23 @@ class TechDomainContact < DomainContact + # Audit log is needed, therefore no raw SQL + def self.replace(current_contact, new_contact) + affected_domains = [] + 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_contact.contact = new_contact + tech_contact.save! + affected_domains << tech_contact.domain.name + end + end + + return affected_domains.sort, skipped_domains.sort + end end diff --git a/app/views/admin/base/_flash_messages.html.erb b/app/views/application/_flash_messages.html.erb similarity index 77% rename from app/views/admin/base/_flash_messages.html.erb rename to app/views/application/_flash_messages.html.erb index 4a10df304..9b31f0d3f 100644 --- a/app/views/admin/base/_flash_messages.html.erb +++ b/app/views/application/_flash_messages.html.erb @@ -1,7 +1,11 @@ <% if flash[:notice] %>
-

<%= flash[:notice] %>

+ <% if flash[:notice].respond_to?(:join) %> +

<%= flash[:notice].join('
').html_safe %>

+ <% else %> +

<%= flash[:notice] %>

+ <% end %>
<% end %> diff --git a/app/views/layouts/registrant/application.html.erb b/app/views/layouts/registrant/application.html.erb index da081b05a..f38f8fcea 100644 --- a/app/views/layouts/registrant/application.html.erb +++ b/app/views/layouts/registrant/application.html.erb @@ -20,7 +20,7 @@
- <%= render 'shared/flash' %> + <%= render 'flash_messages' %> <% if depp_controller? %> <%= render 'registrar/shared/epp_results' %> <% end %> diff --git a/app/views/layouts/registrar/sessions.haml b/app/views/layouts/registrar/sessions.haml deleted file mode 100644 index 49c71ab2c..000000000 --- a/app/views/layouts/registrar/sessions.haml +++ /dev/null @@ -1,45 +0,0 @@ -!!! 5 -%html{lang: I18n.locale.to_s} - %head - %meta{charset: "utf-8"}/ - %meta{content: "IE=edge", "http-equiv" => "X-UA-Compatible"}/ - %meta{content: "width=device-width, initial-scale=1", name: "viewport"}/ - - if content_for? :head_title - = yield :head_title - - else - %title= t(:registrar_head_title) - = csrf_meta_tags - = stylesheet_link_tag 'registrar-manifest', media: 'all' - = javascript_include_tag 'registrar-manifest' - = favicon_link_tag 'favicon.ico' - %body - %nav.navbar.navbar-default.navbar-fixed-top - .container - .navbar-header - %button.navbar-toggle.collapsed{"aria-controls" => "navbar", "aria-expanded" => "false", "data-target" => "#navbar", "data-toggle" => "collapse", :type => "button"} - %span.sr-only Toggle navigation - %span.icon-bar - %span.icon-bar - %span.icon-bar - = link_to registrar_root_path, class: 'navbar-brand', id: 'registrar-home-btn' do - = t(:registrar_head_title) - - if unstable_env.present? - .text-center - %small{style: 'color: #0074B3;'}= unstable_env - - if current_user - = render 'navbar' - - .container - = render 'shared/flash' - - if depp_controller? - = render 'registrar/shared/epp_results' - = yield - - %footer.footer - .container - .row - .col-md-6 - = image_tag 'eis-logo-et.png' - .col-md-6.text-right - Version - = CURRENT_COMMIT_HASH diff --git a/app/views/layouts/registrar/sessions.html.erb b/app/views/layouts/registrar/sessions.html.erb new file mode 100644 index 000000000..904388155 --- /dev/null +++ b/app/views/layouts/registrar/sessions.html.erb @@ -0,0 +1,54 @@ + + + + + <% if content_for? :head_title %> + <%= yield :head_title %> + <% else %> + + <%= t(:registrar_head_title) %> + + <% end %> + <%= csrf_meta_tags %> + <%= stylesheet_link_tag 'registrar-manifest', media: 'all' %> + <%= javascript_include_tag 'registrar-manifest' %> + + + + +
+ <%= render 'flash_messages' %> + <%= yield %> +
+ + + + \ No newline at end of file diff --git a/app/views/registrar/bulk_change/_bulk_transfer_form.html.erb b/app/views/registrar/bulk_change/_bulk_transfer_form.html.erb new file mode 100644 index 000000000..0a953845b --- /dev/null +++ b/app/views/registrar/bulk_change/_bulk_transfer_form.html.erb @@ -0,0 +1,34 @@ +<%= form_tag registrar_domain_transfers_path, multipart: true, class: 'form-horizontal' do %> + <%= render 'registrar/domain_transfers/form/api_errors' %> + +
+
+ <%= label_tag :batch_file %> +
+
+ <%= file_field_tag :batch_file, required: true %> + <%= t '.file_field_hint' %> +
+
+ +
+
+ +
+
+ +
+
+ <%= t '.help_btn' %> + +
+
+ <%= t '.help' %> +
+
+
+
+<% end %> diff --git a/app/views/registrar/registrar_nameservers/_form.html.erb b/app/views/registrar/bulk_change/_nameserver_form.html.erb similarity index 61% rename from app/views/registrar/registrar_nameservers/_form.html.erb rename to app/views/registrar/bulk_change/_nameserver_form.html.erb index 3aca3e0e1..e3f4a0214 100644 --- a/app/views/registrar/registrar_nameservers/_form.html.erb +++ b/app/views/registrar/bulk_change/_nameserver_form.html.erb @@ -1,12 +1,13 @@ -<%= form_tag registrar_update_registrar_nameserver_path, method: :put, class: 'form-horizontal' do %> +<%= form_tag registrar_nameservers_path, method: :patch, class: 'form-horizontal' do %> + <%= render 'registrar/domain_transfers/form/api_errors' %> +
<%= label_tag :old_hostname %>
-
- <%= text_field_tag :old_hostname, params[:old_hostname], autofocus: true, - required: true, +
+ <%= text_field_tag :old_hostname, params[:old_hostname], required: true, class: 'form-control' %>
@@ -16,7 +17,7 @@ <%= label_tag :new_hostname %>
-
+
<%= text_field_tag :new_hostname, params[:new_hostname], required: true, class: 'form-control' %>
@@ -27,7 +28,7 @@ <%= label_tag :ipv4 %>
-
+
<%= text_area_tag :ipv4, params[:ipv4], class: 'form-control' %>
@@ -37,17 +38,30 @@ <%= label_tag :ipv6 %>
-
+
<%= text_area_tag :ipv6, params[:ipv6], class: 'form-control' %> <%= t '.ip_hint' %>
-
+
+ +
+
+ <%= t '.help_btn' %> + +
+
+ <%= t '.help' %> +
+
+
+
<% 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 new file mode 100644 index 000000000..dc0693599 --- /dev/null +++ b/app/views/registrar/bulk_change/_tech_contact_form.html.erb @@ -0,0 +1,60 @@ +<%= form_tag registrar_tech_contacts_path, method: :patch, class: 'form-horizontal' do %> + <% if @error %> +
+ <%= @error[:message] %> +
+ <% end %> + +
+
+ <%= label_tag :current_contact_id, t('.current_contact_id') %> +
+ +
+ <%= text_field_tag :current_contact_id, params[:current_contact_id], + list: :contacts, + required: true, + autofocus: true, + class: 'form-control' %> +
+
+ +
+
+ <%= label_tag :new_contact_id, t('.new_contact_id') %> +
+ +
+ <%= text_field_tag :new_contact_id, params[:new_contact_id], + list: :contacts, + required: true, + class: 'form-control' %> +
+
+ +
+
+ +
+
+ +
+
+ <%= t '.help_btn' %> +
+
+ <%= t '.help' %> +
+
+
+
+<% end %> + + + <% available_contacts.each do |data| %> + + <% end %> + diff --git a/app/views/registrar/bulk_change/new.html.erb b/app/views/registrar/bulk_change/new.html.erb new file mode 100644 index 000000000..da85899ff --- /dev/null +++ b/app/views/registrar/bulk_change/new.html.erb @@ -0,0 +1,37 @@ + + + + + + + +
+
+ <%= render 'tech_contact_form', available_contacts: available_contacts %> +
+ +
+ <%= render 'nameserver_form' %> +
+ +
+ <%= render 'bulk_transfer_form' %> +
+
diff --git a/app/views/registrar/domain_transfers/form/_single.html.erb b/app/views/registrar/domain_transfers/_form.html.erb similarity index 92% rename from app/views/registrar/domain_transfers/form/_single.html.erb rename to app/views/registrar/domain_transfers/_form.html.erb index 8a9488580..52dcca468 100644 --- a/app/views/registrar/domain_transfers/form/_single.html.erb +++ b/app/views/registrar/domain_transfers/_form.html.erb @@ -1,4 +1,6 @@ <%= form_tag registrar_domain_transfers_path, multipart: true, class: 'form-horizontal' do %> + <%= render 'registrar/domain_transfers/form/api_errors' %> +
<%= label_tag :domain_name, nil, class: 'required' %> @@ -30,7 +32,7 @@
diff --git a/app/views/registrar/domain_transfers/form/_batch.html.erb b/app/views/registrar/domain_transfers/form/_batch.html.erb deleted file mode 100644 index 6effa8a86..000000000 --- a/app/views/registrar/domain_transfers/form/_batch.html.erb +++ /dev/null @@ -1,19 +0,0 @@ -<%= form_tag registrar_domain_transfers_path, multipart: true, class: 'form-horizontal' do %> -
-
- <%= label_tag :batch_file %> -
-
- <%= file_field_tag :batch_file, required: true %> - <%= t '.batch_file_help' %> -
-
- -
-
- -
-
-<% end %> diff --git a/app/views/registrar/domain_transfers/new.html.erb b/app/views/registrar/domain_transfers/new.html.erb index 8ba658023..52dd3f900 100644 --- a/app/views/registrar/domain_transfers/new.html.erb +++ b/app/views/registrar/domain_transfers/new.html.erb @@ -6,24 +6,6 @@
- - -
-
- <%= render 'registrar/domain_transfers/form/single' %> -
- -
- <%= render 'registrar/domain_transfers/form/batch' %> -
-
+ <%= render 'form' %>
diff --git a/app/views/registrar/domains/index.html.erb b/app/views/registrar/domains/index.html.erb index 319f0d04f..cbc048697 100644 --- a/app/views/registrar/domains/index.html.erb +++ b/app/views/registrar/domains/index.html.erb @@ -1,13 +1,13 @@