diff --git a/app/controllers/epp/commands_controller.rb b/app/controllers/epp/commands_controller.rb deleted file mode 100644 index 279a86d62..000000000 --- a/app/controllers/epp/commands_controller.rb +++ /dev/null @@ -1,45 +0,0 @@ -class Epp::CommandsController < ApplicationController - include Epp::Common - include Epp::DomainsHelper - include Epp::ContactsHelper - include Epp::PollHelper - include Epp::KeyrelayHelper - include Shared::UserStamper - helper WhodunnitHelper - - layout false - - private - - def create - send("create_#{OBJECT_TYPES[params_hash['epp']['xmlns:ns2']]}") - end - - def renew - send("renew_#{OBJECT_TYPES[params_hash['epp']['xmlns:ns2']]}") - end - - def transfer - send("transfer_#{OBJECT_TYPES[params_hash['epp']['xmlns:ns2']]}") - end - - def check - send("check_#{OBJECT_TYPES[params_hash['epp']['xmlns:ns2']]}") - end - - def delete - send("delete_#{OBJECT_TYPES[params_hash['epp']['xmlns:ns2']]}") - end - - def info - send("info_#{OBJECT_TYPES[params_hash['epp']['xmlns:ns2']]}") - end - - def update - send("update_#{OBJECT_TYPES[params_hash['epp']['xmlns:ns2']]}") - end - - def user_for_paper_trail - current_epp_user ? "#{current_epp_user.id}-EppUser" : nil - end -end diff --git a/app/controllers/epp/contacts_controller.rb b/app/controllers/epp/contacts_controller.rb index 83108a3d1..152561886 100644 --- a/app/controllers/epp/contacts_controller.rb +++ b/app/controllers/epp/contacts_controller.rb @@ -1,5 +1,4 @@ -class Epp::ContactsController < ApplicationController - include Epp::Common +class Epp::ContactsController < EppController include Shared::UserStamper ## Refactor this? helper WhodunnitHelper ## Refactor this? diff --git a/app/controllers/epp/domains_controller.rb b/app/controllers/epp/domains_controller.rb index 35cf74476..dec6a04a6 100644 --- a/app/controllers/epp/domains_controller.rb +++ b/app/controllers/epp/domains_controller.rb @@ -1,6 +1,4 @@ -class Epp::DomainsController < ApplicationController - include Epp::Common - +class Epp::DomainsController < EppController def create @domain = Epp::EppDomain.new(domain_create_params) diff --git a/app/controllers/epp/errors_controller.rb b/app/controllers/epp/errors_controller.rb index 126b0a46a..b0da5473c 100644 --- a/app/controllers/epp/errors_controller.rb +++ b/app/controllers/epp/errors_controller.rb @@ -1,7 +1,4 @@ -class Epp::ErrorsController < ApplicationController - include Epp::Common - layout false - +class Epp::ErrorsController < EppController def error epp_errors << { code: params[:code], msg: params[:msg] } render_epp_response '/epp/error' diff --git a/app/controllers/epp/keyrelays_controller.rb b/app/controllers/epp/keyrelays_controller.rb index d4ce7688a..8b2b4480c 100644 --- a/app/controllers/epp/keyrelays_controller.rb +++ b/app/controllers/epp/keyrelays_controller.rb @@ -1,5 +1,4 @@ -class Epp::KeyrelaysController < ApplicationController - include Epp::Common +class Epp::KeyrelaysController < EppController # rubocop: disable Metrics/PerceivedComplexity # rubocop: disable Metrics/CyclomaticComplexity def keyrelay diff --git a/app/controllers/epp/polls_controller.rb b/app/controllers/epp/polls_controller.rb index 62e45abbc..e9a6d1563 100644 --- a/app/controllers/epp/polls_controller.rb +++ b/app/controllers/epp/polls_controller.rb @@ -1,6 +1,4 @@ -class Epp::PollsController < ApplicationController - include Epp::Common - +class Epp::PollsController < EppController def poll req_poll if params[:parsed_frame].css('poll').first['op'] == 'req' ack_poll if params[:parsed_frame].css('poll').first['op'] == 'ack' diff --git a/app/controllers/epp/sessions_controller.rb b/app/controllers/epp/sessions_controller.rb index 07228cbf4..f15685b4c 100644 --- a/app/controllers/epp/sessions_controller.rb +++ b/app/controllers/epp/sessions_controller.rb @@ -1,7 +1,4 @@ -class Epp::SessionsController < ApplicationController - include Epp::Common - layout false - +class Epp::SessionsController < EppController def hello render_epp_response('greeting') end diff --git a/app/controllers/concerns/epp/common.rb b/app/controllers/epp_controller.rb similarity index 70% rename from app/controllers/concerns/epp/common.rb rename to app/controllers/epp_controller.rb index 63de4a1c6..f3ecdc78e 100644 --- a/app/controllers/concerns/epp/common.rb +++ b/app/controllers/epp_controller.rb @@ -1,42 +1,28 @@ -module Epp::Common - extend ActiveSupport::Concern +class EppController < ApplicationController + protect_from_forgery with: :null_session + before_action :validate_request + layout false + helper_method :current_epp_user - OBJECT_TYPES = { - 'urn:ietf:params:xml:ns:contact-1.0' => 'contact', - 'urn:ietf:params:xml:ns:domain-1.0' => 'domain' - } - - included do - protect_from_forgery with: :null_session - before_action :validate_request - layout false - helper_method :current_epp_user - end - - def proxy - # rubocop: disable Style/VariableName - @svTRID = "ccReg-#{format('%010d', rand(10**10))}" - # rubocop: enable Style/VariableName - send(params[:command]) - end - - def params_hash + def params_hash # TODO: THIS IS DEPRECATED AND WILL BE REMOVED IN FUTURE @params_hash ||= Hash.from_xml(params[:frame]).with_indifferent_access end + # SESSION MANAGEMENT def epp_session cookie = env['rack.request.cookie_hash'] || {} EppSession.find_or_initialize_by(session_id: cookie['session']) end - def epp_errors - @errors ||= [] - end - def current_epp_user @current_epp_user ||= EppUser.find(epp_session[:epp_user_id]) if epp_session[:epp_user_id] end + # ERROR + RESPONSE HANDLING + def epp_errors + @errors ||= [] + end + def handle_errors(obj = nil) @errors ||= [] if obj @@ -55,9 +41,17 @@ module Epp::Common render_epp_response '/epp/error' end - def append_errors(obj) - obj.construct_epp_errors - @errors += obj.errors[:epp_errors] + def render_epp_response(*args) + @response = render_to_string(*args) + render xml: @response + write_to_epp_log + end + + # VALIDATION + def validate_request + validation_method = "validate_#{params[:action]}" + return unless respond_to?(validation_method, true) + handle_errors and return unless send(validation_method) end def epp_request_valid?(*selectors) @@ -72,7 +66,7 @@ module Epp::Common epp_errors.empty? end - def xml_attrs_present?(ph, attributes) + def xml_attrs_present?(ph, attributes) # TODO: THIS IS DEPRECATED AND WILL BE REMOVED IN FUTURE attributes.each do |x| epp_errors << { code: '2003', @@ -82,38 +76,14 @@ module Epp::Common epp_errors.empty? end - def xml_attrs_array_present?(array_ph, attributes) - [array_ph].flatten.each do |ph| - attributes.each do |x| - next if has_attribute(ph, x) - epp_errors << { - code: '2003', - msg: I18n.t('errors.messages.required_parameter_missing', key: x.last) - } - end - end - epp_errors.empty? - end - # rubocop: disable Style/PredicateName - def has_attribute(ph, path) + def has_attribute(ph, path) # TODO: THIS IS DEPRECATED AND WILL BE REMOVED IN FUTURE path.reduce(ph) do |location, key| location.respond_to?(:keys) ? location[key] : nil end end # rubocop: enable Style/PredicateName - def validate_request - validation_method = "validate_#{params[:action]}" - return unless respond_to?(validation_method, true) - handle_errors and return unless send(validation_method) - end - - def render_epp_response(*args) - @response = render_to_string(*args) - render xml: @response - write_to_epp_log - end def write_to_epp_log request_command = params[:command] || params[:action] # error receives :command, other methods receive :action diff --git a/app/helpers/epp/contacts_helper.rb b/app/helpers/epp/contacts_helper.rb deleted file mode 100644 index 1270b692e..000000000 --- a/app/helpers/epp/contacts_helper.rb +++ /dev/null @@ -1,189 +0,0 @@ -module Epp::ContactsHelper - def create_contact - @contact = Contact.new(contact_and_address_attributes) - @contact.registrar = current_epp_user.registrar - render_epp_response '/epp/contacts/create' and return if stamp(@contact) && @contact.save - handle_errors(@contact) - end - - def update_contact - # FIXME: Update returns 2303 update multiple times - code = params_hash['epp']['command']['update']['update'][:id] - @contact = Contact.where(code: code).first - # if update_rights? && stamp(@contact) && @contact.update_attributes(contact_and_address_attributes(:update)) - if owner? && stamp(@contact) && @contact.update_attributes(contact_and_address_attributes(:update)) - render_epp_response 'epp/contacts/update' - else - contact_exists?(code) - handle_errors(@contact) and return - end - end - - # rubocop:disable Metrics/CyclomaticComplexity - def delete_contact - @contact = find_contact - handle_errors(@contact) and return unless rights? # owner? - handle_errors(@contact) and return unless @contact - handle_errors(@contact) and return unless @contact.destroy_and_clean - - render_epp_response '/epp/contacts/delete' - end - # rubocop:enable Metrics/CyclomaticComplexity - - def check_contact - ph = params_hash['epp']['command']['check']['check'] - @contacts = Contact.check_availability(ph[:id]) - render_epp_response '/epp/contacts/check' - end - - def info_contact - handle_errors(@contact) and return unless @contact && rights? - # handle_errors(@contact) and return unless rights? - @disclosure = ContactDisclosure.default_values.merge(@contact.disclosure.try(:as_hash) || {}) - @disclosure_policy = @contact.disclosure.try(:attributes_with_flag) - @owner = owner?(false) - # need to reload contact eagerly - @contact = find_contact('with eager load') if @owner # for clarity, could just be true - render_epp_response 'epp/contacts/info' - end - - def renew_contact - epp_errors << { code: '2101', msg: t(:'errors.messages.unimplemented_command') } - handle_errors - end - - ## HELPER METHODS - - private - - ## CREATE - def validate_contact_create_request - @ph = params_hash['epp']['command']['create']['create'] - return false unless validate_params - xml_attrs_present?(@ph, [%w(postalInfo name), %w(postalInfo addr city), %w(postalInfo addr cc), - %w(ident), %w(voice), %w(email)]) - - epp_errors.empty? - end - - ## UPDATE - def validate_contact_update_request - @ph = params_hash['epp']['command']['update']['update'] - update_attrs_present? - # xml_attrs_present?(@ph, [['id'], %w(authInfo pw)]) - xml_attrs_present?(@ph, [['id']]) - end - - def contact_exists?(code) - return true if @contact.is_a?(Contact) - epp_errors << { code: '2303', msg: t('errors.messages.epp_obj_does_not_exist'), - value: { obj: 'id', val: code } } - end - - def update_attrs_present? - return true if parsed_frame.css('add').present? - return true if parsed_frame.css('rem').present? - return true if parsed_frame.css('chg').present? - epp_errors << { code: '2003', msg: I18n.t('errors.messages.required_parameter_missing', key: 'add, rem or chg') } - end - - ## DELETE - def validate_contact_delete_request - @ph = params_hash['epp']['command']['delete']['delete'] - xml_attrs_present?(@ph, [['id']]) - end - - ## check - def validate_contact_check_request - @ph = params_hash['epp']['command']['check']['check'] - xml_attrs_present?(@ph, [['id']]) - end - - ## info - def validate_contact_info_request # and process - @ph = params_hash['epp']['command']['info']['info'] - return false unless xml_attrs_present?(@ph, [['id']]) - @contact = find_contact - return false unless @contact - return true if current_epp_user.registrar == @contact.registrar || xml_attrs_present?(@ph, [%w(authInfo pw)]) - false - end - - ## SHARED - - def find_contact(eager_load = nil) - if eager_load - contact = Contact.includes(address: :country).find_by(code: @ph[:id]) - else - contact = Contact.find_by(code: @ph[:id]) - end - unless contact - epp_errors << { code: '2303', - msg: t('errors.messages.epp_obj_does_not_exist'), - value: { obj: 'id', val: @ph[:id] } } - end - contact - end - - def owner?(with_errors = true) - return false unless find_contact - return true if @contact.registrar == current_epp_user.registrar - return false unless with_errors - epp_errors << { code: '2201', msg: t('errors.messages.epp_authorization_error') } - false - end - - def rights? - pw = @ph.try(:[], :authInfo).try(:[], :pw) - - return true if current_epp_user.try(:registrar) == @contact.try(:registrar) - return true if pw && @contact.auth_info_matches(pw) # @contact.try(:auth_info_matches, pw) - - epp_errors << { code: '2200', msg: t('errors.messages.epp_authentication_error') } - false - end - - def update_rights? - pw = @ph.try(:[], :authInfo).try(:[], :pw) - return true if pw && @contact.auth_info_matches(pw) - epp_errors << { code: '2200', msg: t('errors.messages.epp_authentication_error') } - false - end - - def contact_and_address_attributes(type = :create) - case type - when :update - # TODO: support for rem/add - contact_hash = merge_attribute_hash(@ph[:chg], type).delete_if { |_k, v| v.empty? } - else - contact_hash = merge_attribute_hash(@ph, type) - end - contact_hash[:ident_type] = ident_type unless ident_type.nil? - contact_hash - end - - def merge_attribute_hash(prms, type) - contact_hash = Contact.extract_attributes(prms, type) - contact_hash = contact_hash.merge( - Address.extract_attributes((prms.try(:[], :postalInfo) || [])) - ) - contact_hash[:disclosure_attributes] = - ContactDisclosure.extract_attributes(parsed_frame) - - contact_hash - end - - def ident_type - result = parsed_frame.css('ident').first.try(:attributes).try(:[], 'type').try(:value) - return nil unless result - - Contact::IDENT_TYPES.any? { |type| return type if result.include?(type) } - nil - end - - def validate_params - return true if @ph - epp_errors << { code: '2001', msg: t(:'errors.messages.epp_command_syntax_error') } - false - end -end diff --git a/app/helpers/epp/domains_helper.rb b/app/helpers/epp/domains_helper.rb deleted file mode 100644 index 006435231..000000000 --- a/app/helpers/epp/domains_helper.rb +++ /dev/null @@ -1,236 +0,0 @@ -module Epp::DomainsHelper - def create_domain - @domain = Epp::EppDomain.new(domain_create_params) - - @domain.parse_and_attach_domain_dependencies(parsed_frame) - @domain.parse_and_attach_ds_data(parsed_frame.css('extension create')) - - if @domain.errors.any? || !@domain.save - handle_errors(@domain) - else - render_epp_response '/epp/domains/create' - end - end - - def check_domain - names = parsed_frame.css('name').map(&:text) - @domains = Epp::EppDomain.check_availability(names) - render_epp_response '/epp/domains/check' - end - - def renew_domain - # TODO: support period unit - @domain = find_domain - - handle_errors(@domain) and return unless @domain - handle_errors(@domain) and return unless @domain.renew( - parsed_frame.css('curExpDate').text, - parsed_frame.css('period').text, - parsed_frame.css('period').first['unit'] - ) - - render_epp_response '/epp/domains/renew' - end - - def info_domain - @domain = find_domain - - handle_errors(@domain) and return unless @domain - - render_epp_response '/epp/domains/info' - end - - # rubocop:disable Metrics/CyclomaticComplexity - def update_domain - @domain = find_domain - - handle_errors(@domain) and return unless @domain - - @domain.parse_and_detach_domain_dependencies(parsed_frame.css('rem')) - @domain.parse_and_detach_ds_data(parsed_frame.css('extension rem')) - @domain.parse_and_attach_domain_dependencies(parsed_frame.css('add')) - @domain.parse_and_attach_ds_data(parsed_frame.css('extension add')) - @domain.parse_and_update_domain_dependencies(parsed_frame.css('chg')) - @domain.attach_legal_document(Epp::EppDomain.parse_legal_document_from_frame(parsed_frame)) - - if @domain.errors.any? || !@domain.save - handle_errors(@domain) - else - render_epp_response '/epp/domains/success' - end - end - - # rubocop: disable Metrics/PerceivedComplexity - # rubocop: disable Metrics/MethodLength - - def transfer_domain - @domain = find_domain(secure: false) - handle_errors(@domain) and return unless @domain - handle_errors(@domain) and return unless @domain.authenticate(domain_transfer_params[:pw]) - - if domain_transfer_params[:action] == 'query' - if @domain.pending_transfer - @domain_transfer = @domain.pending_transfer - else - @domain_transfer = @domain.query_transfer(domain_transfer_params, parsed_frame) - handle_errors(@domain) and return unless @domain_transfer - end - elsif domain_transfer_params[:action] == 'approve' - if @domain.pending_transfer - @domain_transfer = @domain.approve_transfer(domain_transfer_params, parsed_frame) - handle_errors(@domain) and return unless @domain_transfer - else - epp_errors << { code: '2303', msg: I18n.t('pending_transfer_was_not_found') } - handle_errors(@domain) and return - end - elsif domain_transfer_params[:action] == 'reject' - if @domain.pending_transfer - @domain_transfer = @domain.reject_transfer(domain_transfer_params, parsed_frame) - handle_errors(@domain) and return unless @domain_transfer - else - epp_errors << { code: '2303', msg: I18n.t('pending_transfer_was_not_found') } - handle_errors(@domain) and return - end - end - - render_epp_response '/epp/domains/transfer' - end - - # rubocop: enable Metrics/MethodLength - # rubocop: enable Metrics/CyclomaticComplexity - # rubocop:disable Metrics/CyclomaticComplexity - - def delete_domain - @domain = find_domain - - handle_errors(@domain) and return unless @domain - handle_errors(@domain) and return unless @domain.can_be_deleted? - - @domain.attach_legal_document(Epp::EppDomain.parse_legal_document_from_frame(parsed_frame)) - @domain.save(validate: false) - - handle_errors(@domain) and return unless @domain.destroy - - render_epp_response '/epp/domains/success' - end - # rubocop:enbale Metrics/CyclomaticComplexity - - ### HELPER METHODS ### - - private - - ## CHECK - - def validate_domain_check_request - epp_request_valid?('name') - end - - ## CREATE - def validate_domain_create_request - ret = true - - # TODO: Verify contact presence if registrant is juridical - attrs_present = epp_request_valid?('name', 'ns', 'registrant', 'legalDocument') - ret = false unless attrs_present - - if parsed_frame.css('hostObj').any? - epp_errors << { code: '2306', msg: I18n.t('host_obj_is_not_allowed') } - ret = false - end - - if parsed_frame.css('dsData').count > 0 && parsed_frame.css('create > keyData').count > 0 - epp_errors << { code: '2306', msg: I18n.t('ds_data_and_key_data_must_not_exists_together') } - ret = false - end - ret - end - - def domain_create_params - name = parsed_frame.css('name').text - period = parsed_frame.css('period').text - - { - name: name, - registrar_id: current_epp_user.registrar.try(:id), - registered_at: Time.now, - period: (period.to_i == 0) ? 1 : period.to_i, - period_unit: Epp::EppDomain.parse_period_unit_from_frame(parsed_frame) || 'y' - } - end - - def domain_transfer_params - res = {} - res[:pw] = parsed_frame.css('pw').first.try(:text) - res[:action] = parsed_frame.css('transfer').first[:op] - res[:current_user] = current_epp_user - res - end - - ## RENEW - def validate_domain_renew_request - @ph = params_hash['epp']['command']['renew']['renew'] - xml_attrs_present?(@ph, [['name'], ['curExpDate'], ['period']]) - end - - ## INFO - def validate_domain_info_request - @ph = params_hash['epp']['command']['info']['info'] - xml_attrs_present?(@ph, [['name']]) - end - - ## UPDATE - def validate_domain_update_request - @ph = params_hash['epp']['command']['update']['update'] - - if parsed_frame.css('chg registrant').present? && parsed_frame.css('legalDocument').blank? - xml_attrs_present?(@ph, [['name'], ['legalDocument']]) - else - xml_attrs_present?(@ph, [['name']]) - end - end - - ## TRANSFER - def validate_domain_transfer_request - @ph = params_hash['epp']['command']['transfer']['transfer'] - attrs_present = xml_attrs_present?(@ph, [['name']]) - return false unless attrs_present - - op = parsed_frame.css('transfer').first[:op] - return true if %w(approve query reject).include?(op) - epp_errors << { code: '2306', msg: I18n.t('errors.messages.attribute_op_is_invalid') } - false - end - - ## DELETE - def validate_domain_delete_request - epp_request_valid?('name', 'legalDocument') - end - - ## SHARED - def find_domain(secure = { secure: true }) - domain_name = parsed_frame.css('name').text.strip.downcase - domain = Epp::EppDomain.find_by(name: domain_name) - - unless domain - epp_errors << { - code: '2303', - msg: I18n.t('errors.messages.epp_domain_not_found'), - value: { obj: 'name', val: domain_name } - } - return nil - end - - return domain if domain.auth_info == parsed_frame.css('authInfo pw').text - - if (domain.registrar != current_epp_user.registrar && secure[:secure] == true) && - epp_errors << { - code: '2302', - msg: I18n.t('errors.messages.domain_exists_but_belongs_to_other_registrar'), - value: { obj: 'name', val: parsed_frame.css('name').text.strip.downcase } - } - return nil - end - - domain - end -end diff --git a/app/helpers/epp/keyrelay_helper.rb b/app/helpers/epp/keyrelay_helper.rb deleted file mode 100644 index 925a4876f..000000000 --- a/app/helpers/epp/keyrelay_helper.rb +++ /dev/null @@ -1,55 +0,0 @@ -module Epp::KeyrelayHelper - # rubocop: disable Metrics/PerceivedComplexity - # rubocop: disable Metrics/CyclomaticComplexity - def keyrelay - handle_errors and return unless validate_keyrelay_request - - @domain = find_domain_for_keyrelay - - handle_errors(@domain) and return unless @domain - handle_errors(@domain) and return unless @domain.authenticate(parsed_frame.css('pw').text) - handle_errors(@domain) and return unless @domain.keyrelay(parsed_frame, current_epp_user.registrar) - - render_epp_response '/epp/shared/success' - end - - private - - def validate_keyrelay_request - epp_request_valid?('pubKey', 'flags', 'protocol', 'alg', 'name', 'pw') - - begin - abs_datetime = parsed_frame.css('absolute').text - abs_datetime = DateTime.parse(abs_datetime) if abs_datetime.present? - rescue => _e - epp_errors << { - code: '2005', - msg: I18n.t('unknown_expiry_absolute_pattern'), - value: { obj: 'expiry_absolute', val: abs_datetime } - } - end - - epp_errors.empty? - end - # rubocop: enable Metrics/PerceivedComplexity - # rubocop: enable Metrics/CyclomaticComplexity - - # rubocop: enable Metrics/PerceivedComplexity - # rubocop: enable Metrics/CyclomaticComplexity - - def find_domain_for_keyrelay - domain_name = parsed_frame.css('name').text.strip.downcase - domain = Epp::EppDomain.find_by(name: domain_name) - - unless domain - epp_errors << { - code: '2303', - msg: I18n.t('errors.messages.epp_domain_not_found'), - value: { obj: 'name', val: domain_name } - } - return nil - end - - domain - end -end diff --git a/app/helpers/epp/poll_helper.rb b/app/helpers/epp/poll_helper.rb deleted file mode 100644 index 8f0d17a22..000000000 --- a/app/helpers/epp/poll_helper.rb +++ /dev/null @@ -1,46 +0,0 @@ -module Epp::PollHelper - def poll - req_poll if parsed_frame.css('poll').first['op'] == 'req' - ack_poll if parsed_frame.css('poll').first['op'] == 'ack' - end - - def req_poll - @message = current_epp_user.queued_messages.last - render_epp_response 'epp/poll/poll_no_messages' and return unless @message - - if @message.attached_obj_type && @message.attached_obj_id - @object = Object.const_get(@message.attached_obj_type).find(@message.attached_obj_id) - end - - if @message.attached_obj_type == 'Keyrelay' - render_epp_response 'epp/poll/poll_keyrelay' - else - render_epp_response 'epp/poll/poll_req' - end - end - - def ack_poll - @message = current_epp_user.queued_messages.find_by(id: parsed_frame.css('poll').first['msgID']) - - unless @message - epp_errors << { - code: '2303', - msg: I18n.t('message_was_not_found'), - value: { obj: 'msgID', val: parsed_frame.css('poll').first['msgID'] } - } - handle_errors and return - end - - handle_errors(@message) and return unless @message.dequeue - render_epp_response 'epp/poll/poll_ack' - end - - private - - def validate__poll_request - op = parsed_frame.css('poll').first[:op] - return true if %w(ack req).include?(op) - epp_errors << { code: '2306', msg: I18n.t('errors.messages.attribute_op_is_invalid') } - false - end -end