diff --git a/Guardfile b/Guardfile index 358d58e80..e6d0a443a 100644 --- a/Guardfile +++ b/Guardfile @@ -3,11 +3,11 @@ group :red_green_refactor, halt_on_fail: true do # be sure you have apache2 configured to # accept EPP request on port 701, what proxy to 8989. # port and environment is just for correct notification, all is overwritten by CLI - guard :rails, port: 8989, environment: 'test' do - # guard :rails, port: 8989, environment: 'test', CLI: 'RAILS_ENV=test unicorn -p 8989' do - watch('Gemfile.lock') - watch(%r{^(config|lib)/.*}) - end + # guard :rails, port: 8989, environment: 'test' do + # # guard :rails, port: 8989, environment: 'test', CLI: 'RAILS_ENV=test unicorn -p 8989' do + # watch('Gemfile.lock') + # watch(%r{^(config|lib)/.*}) + # end guard :rspec, cmd: 'spring rspec', notification: false do watch(%r{^spec/.+_spec\.rb$}) diff --git a/app/controllers/epp/contacts_controller.rb b/app/controllers/epp/contacts_controller.rb index 5af05d9fd..d93916055 100644 --- a/app/controllers/epp/contacts_controller.rb +++ b/app/controllers/epp/contacts_controller.rb @@ -17,9 +17,7 @@ class Epp::ContactsController < EppController def create authorize! :create, Epp::Contact - - @contact = Epp::Contact.new(params[:parsed_frame]) - @contact.registrar = current_user.registrar + @contact = Epp::Contact.new(params[:parsed_frame], current_user.registrar) if @contact.save render_epp_response '/epp/contacts/create' diff --git a/app/models/contact.rb b/app/models/contact.rb index 722e4de33..da8a3f651 100644 --- a/app/models/contact.rb +++ b/app/models/contact.rb @@ -22,7 +22,11 @@ class Contact < ActiveRecord::Base format: { with: /\d{4}-\d{2}-\d{2}/, message: :invalid_birthday_format }, if: proc { |c| c.ident_type == 'birthday' } validates :ident_country_code, presence: true, if: proc { |c| %w(bic priv).include? c.ident_type } - validates :code, uniqueness: { message: :epp_id_taken } + validates :code, + uniqueness: { message: :epp_id_taken }, + format: { with: /\A[\w\-\:]*\Z/i }, + length: { maximum: 100 } + validate :ident_valid_format? delegate :street, to: :address @@ -99,15 +103,27 @@ class Contact < ActiveRecord::Base ident_type != IDENT_TYPE_BIC end - # generate random id for contact def generate_code - self.code = SecureRandom.hex(4) + self.code = SecureRandom.hex(4) if code.blank? end def generate_auth_info + return if @generate_auth_info_disabled self.auth_info = SecureRandom.hex(16) end + def disable_generate_auth_info! # needed for testing + @generate_auth_info_disabled = true + end + + def auth_info=(pw) + self[:auth_info] = pw if new_record? + end + + def code=(code) + self[:code] = code if new_record? + end + # Find a way to use self.domains with contact def domains_owned Domain.where(owner_contact_id: id) diff --git a/app/models/epp/contact.rb b/app/models/epp/contact.rb index 0a6c14020..d9df7f2ae 100644 --- a/app/models/epp/contact.rb +++ b/app/models/epp/contact.rb @@ -45,9 +45,22 @@ class Epp::Contact < Contact # rubocop: enable Metrics/PerceivedComplexity # rubocop: enable Metrics/CyclomaticComplexity - def new(frame) + def new(frame, registrar) return super if frame.blank? - super(attrs_from(frame)) + + custom_code = + if frame.css('id').present? + "#{registrar.code}:#{frame.css('id').text.parameterize}" + else + nil + end + + super( + attrs_from(frame).merge( + code: custom_code, + registrar: registrar + ) + ) end def legal_document_attrs(legal_frame) diff --git a/app/models/registrar.rb b/app/models/registrar.rb index 2078226b2..e895273b3 100644 --- a/app/models/registrar.rb +++ b/app/models/registrar.rb @@ -9,10 +9,18 @@ class Registrar < ActiveRecord::Base validates :name, :reg_no, :country_code, :email, presence: true validates :name, :reg_no, uniqueness: true + validate :set_code, if: :new_record? after_save :touch_domains_version validates :email, :billing_email, format: /@/, allow_blank: true + class << self + def search_by_query(query) + res = search(name_or_reg_no_cont: query).result + res.reduce([]) { |o, v| o << { id: v[:id], display_key: "#{v[:name]} (#{v[:reg_no]})" } } + end + end + def domain_transfers at = DomainTransfer.arel_table DomainTransfer.where( @@ -34,10 +42,23 @@ class Registrar < ActiveRecord::Base Country.new(country_code) end - class << self - def search_by_query(query) - res = search(name_or_reg_no_cont: query).result - res.reduce([]) { |o, v| o << { id: v[:id], display_key: "#{v[:name]} (#{v[:reg_no]})" } } + def code=(code) + self[:code] = code if new_record? + end + + private + + def set_code + return false if name.blank? + new_code = name.parameterize + + # ensure code is always uniq automatically for a new record + seq = 1 + while self.class.find_by_code(new_code) + new_code += seq.to_s + seq += 1 end + + self.code = new_code end end diff --git a/db/migrate/20150303130729_add_code_to_registrar.rb b/db/migrate/20150303130729_add_code_to_registrar.rb new file mode 100644 index 000000000..6dea363fe --- /dev/null +++ b/db/migrate/20150303130729_add_code_to_registrar.rb @@ -0,0 +1,6 @@ +class AddCodeToRegistrar < ActiveRecord::Migration + def change + add_column :registrars, :code, :string + add_index :registrars, :code + end +end diff --git a/db/schema.rb b/db/schema.rb index 4630543d3..02859bae8 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -11,7 +11,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 20150227113121) do +ActiveRecord::Schema.define(version: 20150303130729) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -611,8 +611,11 @@ ActiveRecord::Schema.define(version: 20150227113121) do t.string "city" t.string "street" t.string "zip" + t.string "code" end + add_index "registrars", ["code"], name: "index_registrars_on_code", using: :btree + create_table "reserved_domains", force: :cascade do |t| t.string "name" t.datetime "created_at" diff --git a/doc/epp/contact.md b/doc/epp/contact.md index d977c4e24..db136c0db 100644 --- a/doc/epp/contact.md +++ b/doc/epp/contact.md @@ -13,6 +13,7 @@ Contact Mapping protocol short version: ----------------------- ------- ----------------- 1 1 Attribute: xmlns:contact="urn:ietf:params:xml:ns:contact-1.0" + 0-1 Contact id, optional, generated automatically if missing 1 Postal information container 1 Full name of the contact 0-1 Name of organization @@ -42,7 +43,7 @@ Contact Mapping protocol short version: ----------------------- ------- ----------------- 1 1 Attribute: xmlns:contact="urn:ietf:params:xml:ns:contact-1.0" - 1 contact id, required + 1 Contact id, required 1 Change container 1 Postal information container 0-1 Full name of the contact diff --git a/spec/epp/contact_spec.rb b/spec/epp/contact_spec.rb index a025197c6..78085eed6 100644 --- a/spec/epp/contact_spec.rb +++ b/spec/epp/contact_spec.rb @@ -13,10 +13,8 @@ describe 'EPP Contact', epp: true do login_as :registrar1 - Contact.skip_callback(:create, :before, :generate_code) - Contact.skip_callback(:create, :before, :generate_auth_info) - @contact = Fabricate(:contact, registrar: @registrar1) + @legal_document = { legalDocument: { value: 'JVBERi0xLjQKJcOkw7zDtsOfCjIgMCBvYmoKPDwvTGVuZ3RoIDMgMCBSL0Zp==', @@ -25,11 +23,6 @@ describe 'EPP Contact', epp: true do } end - after :all do - Contact.set_callback(:create, :before, :generate_code) - Contact.set_callback(:create, :before, :generate_auth_info) - end - context 'with valid user' do context 'create command' do def create_request(overwrites = {}) @@ -133,6 +126,17 @@ describe 'EPP Contact', epp: true do # 5 seconds for what-ever weird lag reasons might happen cr_date.text.to_time.should be_within(5).of(Time.now) end + + it 'successfully saves custom code' do + response = create_request( + { id: { value: '12345' } } + ) + + response[:msg].should == 'Command completed successfully' + response[:result_code].should == '1000' + + Contact.last.code.should == 'registrar1:12345' + end end context 'update command' do @@ -140,11 +144,9 @@ describe 'EPP Contact', epp: true do @contact = Fabricate( :contact, - # created_by_id: 1, registrar: @registrar1, email: 'not_updated@test.test', - code: 'sh8013', - auth_info: 'password' + code: 'sh8013' ) end @@ -226,6 +228,20 @@ describe 'EPP Contact', epp: true do response[:results][1][:msg].should == 'Email is invalid' response[:results][1][:result_code].should == '2005' end + + it 'should not update code with custom string' do + response = update_request( + id: { value: 'sh8013' }, + chg: { + id: { value: 'notpossibletoupdate' } + } + ) + + response[:msg].should == 'Object does not exist' + response[:result_code].should == '2303' + + @contact.reload.code.should == 'sh8013' + end end context 'delete command' do diff --git a/spec/epp/domain_spec.rb b/spec/epp/domain_spec.rb index cf49a0987..18249e471 100644 --- a/spec/epp/domain_spec.rb +++ b/spec/epp/domain_spec.rb @@ -11,8 +11,6 @@ describe 'EPP Domain', epp: true do login_as :registrar1 - Contact.skip_callback(:create, :before, :generate_code) - Fabricate(:contact, code: 'citizen_1234') Fabricate(:contact, code: 'sh8013') Fabricate(:contact, code: 'sh801333') @@ -254,8 +252,8 @@ describe 'EPP Domain', epp: true do }) response = epp_plain_request(xml, :xml) - response[:result_code].should == '2005' response[:msg].should == 'Hostname is invalid' + response[:result_code].should == '2005' end it 'checks hostAttr presence' do @@ -271,8 +269,8 @@ describe 'EPP Domain', epp: true do }) response = epp_plain_request(xml, :xml) - response[:result_code].should == '2003' response[:msg].should == 'Required parameter missing: create > create > ns > hostAttr' + response[:result_code].should == '2003' end it 'creates domain with nameservers with ips' do diff --git a/spec/fabricators/contact_fabricator.rb b/spec/fabricators/contact_fabricator.rb index 0462f7a61..45c4db75e 100644 --- a/spec/fabricators/contact_fabricator.rb +++ b/spec/fabricators/contact_fabricator.rb @@ -1,13 +1,16 @@ Fabricator(:contact) do code { "sh#{Faker::Number.number(8)}" } + auth_info 'password' name { sequence(:name) { |i| "#{Faker::Name.name}#{i}" } } phone '+372.12345678' email Faker::Internet.email ident '37605030299' ident_type 'priv' ident_country_code 'EE' - auth_info 'ccds4324pok' address registrar { Fabricate(:registrar, name: Faker::Company.name, reg_no: Faker::Company.duns_number) } disclosure { Fabricate(:contact_disclosure) } + # rubocop: disable Style/SymbolProc + after_validation { |c| c.disable_generate_auth_info! } + # rubocop: enamble Style/SymbolProc end diff --git a/spec/models/contact_spec.rb b/spec/models/contact_spec.rb index d605e9781..dbf7bc3dc 100644 --- a/spec/models/contact_spec.rb +++ b/spec/models/contact_spec.rb @@ -91,6 +91,12 @@ describe Contact do it 'should not have any versions' do @contact.versions.should == [] end + + it 'should not accept long code' do + @contact.code = 'verylongcode' * 100 + @contact.valid? + @contact.errors[:code].should == ['is too long (maximum is 100 characters)'] + end end context 'with valid attributes' do @@ -130,6 +136,17 @@ describe Contact do @contact.errors.full_messages.should match_array([]) end + it 'should not accept new custom code' do + old_code = @contact.code + @contact.code = 'CID:REG1:12345' + @contact.save.should == true + @contact.code.should == old_code + end + + it 'should have static password' do + @contact.auth_info.should == 'password' + end + context 'as birthday' do before :all do @contact.ident_type = 'birthday' @@ -182,20 +199,56 @@ describe Contact do end context 'after create' do - it 'should generate a new code and password' do + it 'should not generate a new code when code is present' do + @contact = Fabricate.build(:contact, code: '123asd', auth_info: 'qwe321') + @contact.code.should == '123asd' + @contact.save.should == true + @contact.code.should == '123asd' + end + + it 'should generate a new password' do @contact = Fabricate.build(:contact, code: '123asd', auth_info: 'qwe321') - @contact.code.should == '123asd' @contact.auth_info.should == 'qwe321' - @contact.save! - @contact.code.should_not == '123asd' + @contact.save.should == true @contact.auth_info.should_not == 'qwe321' end + + it 'should not allow same code' do + @double_contact = Fabricate.build(:contact, code: @contact.code) + @double_contact.valid? + @double_contact.errors.full_messages.should == ["Code Contact id already exists"] + end + + it 'should allow supported code format' do + @contact = Fabricate.build(:contact, code: 'CID:REG1:12345') + @contact.valid? + @contact.errors.full_messages.should == [] + end + + it 'should not allow unsupported characters in code' do + @contact = Fabricate.build(:contact, code: 'unsupported!ÄÖÜ~?') + @contact.valid? + @contact.errors.full_messages.should == ['Code is invalid'] + end + + it 'should generate code if empty code is given' do + @contact = Fabricate(:contact, code: '') + @contact.code.should_not == '' + end + + it 'should not allow empty spaces as code' do + @contact = Fabricate.build(:contact, code: ' ') + @contact.valid? + @contact.errors.full_messages.should == ['Code is invalid'] + end end context 'after update' do before :all do - @contact.code = '123asd' - @contact.auth_info = 'qwe321' + @contact = Fabricate.build(:contact, code: '123asd', auth_info: 'qwe321') + @contact.save + @contact.code.should == '123asd' + @auth_info = @contact.auth_info end it 'should not generate new code' do @@ -205,7 +258,7 @@ describe Contact do it 'should not generate new auth_info' do @contact.update_attributes(name: 'fvrsgbqevciherot23') - @contact.auth_info.should == 'qwe321' + @contact.auth_info.should == @auth_info end end end diff --git a/spec/models/registrar_spec.rb b/spec/models/registrar_spec.rb index 7271a307d..4c92e5f66 100644 --- a/spec/models/registrar_spec.rb +++ b/spec/models/registrar_spec.rb @@ -28,6 +28,10 @@ describe Registrar do @registrar.errors[:email].should == ['is invalid'] @registrar.errors[:billing_email].should == ['is invalid'] end + + it 'should not have valid code' do + @registrar.code.should == nil + end end context 'with valid attributes' do @@ -59,5 +63,26 @@ describe Registrar do it 'should return full address' do @registrar.address.should == 'Street 999, Town, County, Postal' end + + it 'should have code' do + @registrar.code.should =~ /registrar/ + end + + it 'should not be able to change code' do + @registrar.code = 'not-updated' + @registrar.code.should =~ /registrar/ + end + + it 'should automatically add next code if original is taken' do + @registrar = Fabricate(:registrar, name: 'uniq') + @registrar.name = 'New name' + @registrar.code.should == 'uniq' + @registrar.save + + @new_registrar = Fabricate.build(:registrar, name: 'uniq') + @new_registrar.valid? + @new_registrar.errors.full_messages.should == [] + @new_registrar.code.should == 'uniq1' + end end end