Merge pull request #1314 from internetee/improve-epp-login

Improve EPP login
This commit is contained in:
Timo Võhmar 2020-11-13 12:43:50 +02:00 committed by GitHub
commit 71991f730e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 230 additions and 176 deletions

View file

@ -79,19 +79,32 @@ module Epp
if success && EppSession.limit_reached?(@api_user.registrar)
epp_errors << {
msg: 'Authentication error; server closing connection (connection limit reached)',
code: '2501'
msg: 'Session limit exceeded; server closing connection (connection limit reached)',
code: '2502',
}
success = false
end
if success
if params[:parsed_frame].css('newPW').first
unless @api_user.update(plain_text_password: params[:parsed_frame].css('newPW').first.text)
handle_errors(@api_user) and return
new_password = params[:parsed_frame].at_css('newPW')&.text
password_change = new_password.present?
if password_change
@api_user.plain_text_password = new_password
@api_user.save!
end
already_authenticated = EppSession.exists?(session_id: epp_session_id)
if already_authenticated
epp_errors << {
msg: 'Command use error; Already authenticated',
code: 2002,
}
handle_errors
return
end
end
epp_session = EppSession.new
epp_session.session_id = epp_session_id
@ -100,8 +113,8 @@ module Epp
render_epp_response('login_success')
else
handle_errors
end
end
end
def ip_white?
webclient_request = ENV['webclient_ips'].split(',').map(&:strip).include?(request.ip)
@ -125,7 +138,7 @@ module Epp
@api_user = current_user # cache current_user for logging
epp_session.destroy
render_epp_response('logout')
end
end
### HELPER METHODS ###

View file

@ -30,6 +30,7 @@ module Epp
data_management_policy_violation: 2308,
command_failed: 2400,
authentication_error_server_closing_connection: 2501,
session_limit_exceeded_server_closing_connection: 2502,
}.freeze
private_constant :KEY_TO_VALUE
@ -59,6 +60,7 @@ module Epp
2308 => 'Data management policy violation',
2400 => 'Command failed',
2501 => 'Authentication error; server closing connection',
2502 => 'Session limit exceeded; server closing connection',
}.freeze
private_constant :DEFAULT_DESCRIPTIONS

View file

@ -6,19 +6,26 @@ class EppSession < ApplicationRecord
class_attribute :timeout
self.timeout = (ENV['epp_session_timeout_seconds'] || 300).to_i.seconds
class_attribute :sessions_per_registrar
self.sessions_per_registrar = (ENV['epp_sessions_per_registrar'] || 4).to_i
alias_attribute :last_access, :updated_at
def self.limit_per_registrar
4
end
scope :not_expired,
lambda {
where(':now <= (updated_at + interval :interval)', now: Time.zone.now, interval: interval)
}
def self.limit_reached?(registrar)
count = where(user_id: registrar.api_users.ids).where('updated_at >= ?', Time.zone.now - 1.second).count
count >= limit_per_registrar
count = where(user_id: registrar.api_users.ids).not_expired.count
count >= sessions_per_registrar
end
def self.interval
"#{timeout.parts.first.second} #{timeout.parts.first.first}"
end
def self.expired
interval = "#{timeout.parts.first.second} #{timeout.parts.first.first}"
where(':now > (updated_at + interval :interval)', now: Time.zone.now, interval: interval)
end

View file

@ -170,6 +170,8 @@ tara_rant_redirect_uri: 'redirect_uri'
default_email_validation_type: 'regex'
epp_sessions_per_registrar: '4'
# Since the keys for staging are absent from the repo, we need to supply them separate for testing.
test:
payments_seb_bank_certificate: 'test/fixtures/files/seb_bank_cert.pem'

View file

@ -1,64 +0,0 @@
require 'test_helper'
class EppLoginCredentialsTest < EppTestCase
def test_correct_credentials
request_xml = <<-XML
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<epp xmlns="https://epp.tld.ee/schema/epp-ee-1.0.xsd">
<command>
<login>
<clID>test_bestnames</clID>
<pw>testtest</pw>
<options>
<version>1.0</version>
<lang>en</lang>
</options>
<svcs>
<objURI>https://epp.tld.ee/schema/domain-eis-1.0.xsd</objURI>
<objURI>https://epp.tld.ee/schema/contact-ee-1.1.xsd</objURI>
<objURI>urn:ietf:params:xml:ns:host-1.0</objURI>
</svcs>
</login>
</command>
</epp>
XML
post epp_login_path, params: { frame: request_xml },
headers: { 'HTTP_COOKIE' => 'session=new_session_id' }
assert EppSession.find_by(session_id: 'new_session_id')
assert_equal users(:api_bestnames), EppSession.find_by(session_id: 'new_session_id').user
assert_epp_response :completed_successfully
end
def test_already_logged_in
assert true # Handled by EPP proxy
end
def test_wrong_credentials
request_xml = <<-XML
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<epp xmlns="https://epp.tld.ee/schema/epp-ee-1.0.xsd">
<command>
<login>
<clID>non-existent</clID>
<pw>valid-but-wrong</pw>
<options>
<version>1.0</version>
<lang>en</lang>
</options>
<svcs>
<objURI>https://epp.tld.ee/schema/domain-eis-1.0.xsd</objURI>
<objURI>https://epp.tld.ee/schema/contact-ee-1.1.xsd</objURI>
<objURI>urn:ietf:params:xml:ns:host-1.0</objURI>
</svcs>
</login>
</command>
</epp>
XML
post epp_login_path, params: { frame: request_xml },
headers: { 'HTTP_COOKIE' => 'session=any_random_string' }
assert_epp_response :authentication_error_server_closing_connection
end
end

View file

@ -1,32 +0,0 @@
require 'test_helper'
class EppLoginPasswordChangeTest < EppTestCase
def test_password_change
request_xml = <<-XML
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<epp xmlns="https://epp.tld.ee/schema/epp-ee-1.0.xsd">
<command>
<login>
<clID>test_bestnames</clID>
<pw>testtest</pw>
<newPW>new-password</newPW>
<options>
<version>1.0</version>
<lang>en</lang>
</options>
<svcs>
<objURI>https://epp.tld.ee/schema/domain-eis-1.0.xsd</objURI>
<objURI>https://epp.tld.ee/schema/contact-ee-1.1.xsd</objURI>
<objURI>urn:ietf:params:xml:ns:host-1.0</objURI>
</svcs>
</login>
</command>
</epp>
XML
post epp_login_path, params: { frame: request_xml },
headers: { 'HTTP_COOKIE' => 'session=new_session_id' }
assert_equal 'new-password', users(:api_bestnames).plain_text_password
assert_epp_response :completed_successfully
end
end

View file

@ -1,61 +0,0 @@
require 'test_helper'
class EppLoginSessionLimitTest < EppTestCase
setup do
travel_to Time.zone.parse('2010-07-05')
EppSession.delete_all
end
def test_not_reached
(EppSession.limit_per_registrar - 1).times do
EppSession.create!(session_id: SecureRandom.hex,
user: users(:api_bestnames),
updated_at: Time.zone.parse('2010-07-05'))
end
assert_difference 'EppSession.count' do
post epp_login_path, params: { frame: request_xml },
headers: { 'HTTP_COOKIE' => 'session=new_session_id' }
end
assert_epp_response :completed_successfully
end
def test_reached
EppSession.limit_per_registrar.times do
EppSession.create!(session_id: SecureRandom.hex,
user: users(:api_bestnames),
updated_at: Time.zone.parse('2010-07-05'))
end
assert_no_difference 'EppSession.count' do
post epp_login_path, params: { frame: request_xml },
headers: { 'HTTP_COOKIE' => 'session=new_session_id' }
end
assert_epp_response :authentication_error_server_closing_connection
end
private
def request_xml
<<-XML
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<epp xmlns="https://epp.tld.ee/schema/epp-ee-1.0.xsd">
<command>
<login>
<clID>test_bestnames</clID>
<pw>testtest</pw>
<options>
<version>1.0</version>
<lang>en</lang>
</options>
<svcs>
<objURI>https://epp.tld.ee/schema/domain-eis-1.0.xsd</objURI>
<objURI>https://epp.tld.ee/schema/contact-ee-1.1.xsd</objURI>
<objURI>urn:ietf:params:xml:ns:host-1.0</objURI>
</svcs>
</login>
</command>
</epp>
XML
end
end

View file

@ -0,0 +1,189 @@
require 'test_helper'
class EppLoginTest < EppTestCase
setup do
@original_sessions_per_registrar_setting = EppSession.sessions_per_registrar
end
teardown do
EppSession.sessions_per_registrar = @original_sessions_per_registrar_setting
end
def test_logging_in_with_correct_credentials_creates_new_session
user = users(:api_bestnames)
new_session_id = 'new-session-id'
request_xml = <<-XML
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<epp xmlns="https://epp.tld.ee/schema/epp-ee-1.0.xsd">
<command>
<login>
<clID>#{user.username}</clID>
<pw>#{user.plain_text_password}</pw>
<options>
<version>1.0</version>
<lang>en</lang>
</options>
<svcs>
<objURI>https://epp.tld.ee/schema/domain-eis-1.0.xsd</objURI>
<objURI>https://epp.tld.ee/schema/contact-ee-1.1.xsd</objURI>
<objURI>urn:ietf:params:xml:ns:host-1.0</objURI>
<objURI>urn:ietf:params:xml:ns:keyrelay-1.0</objURI>
</svcs>
</login>
</command>
</epp>
XML
assert_difference 'EppSession.count' do
post '/epp/session/login', params: { frame: request_xml },
headers: { 'HTTP_COOKIE' => "session=#{new_session_id}" }
end
assert_epp_response :completed_successfully
session = EppSession.last
assert_equal new_session_id, session.session_id
assert_equal user, session.user
end
def test_user_cannot_login_again
session = epp_sessions(:api_bestnames)
user = session.user
request_xml = <<-XML
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<epp xmlns="https://epp.tld.ee/schema/epp-ee-1.0.xsd">
<command>
<login>
<clID>#{user.username}</clID>
<pw>#{user.plain_text_password}</pw>
<options>
<version>1.0</version>
<lang>en</lang>
</options>
<svcs>
<objURI>https://epp.tld.ee/schema/domain-eis-1.0.xsd</objURI>
<objURI>https://epp.tld.ee/schema/contact-ee-1.1.xsd</objURI>
<objURI>urn:ietf:params:xml:ns:host-1.0</objURI>
<objURI>urn:ietf:params:xml:ns:keyrelay-1.0</objURI>
</svcs>
</login>
</command>
</epp>
XML
assert_no_difference 'EppSession.count' do
post '/epp/session/login', params: { frame: request_xml },
headers: { HTTP_COOKIE: "session=#{session.session_id}" }
end
assert_epp_response :use_error
end
def test_user_cannot_login_with_wrong_credentials
user = users(:api_bestnames)
wrong_password = 'a' * ApiUser.min_password_length
assert_not_equal wrong_password, user.plain_text_password
request_xml = <<-XML
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<epp xmlns="https://epp.tld.ee/schema/epp-ee-1.0.xsd">
<command>
<login>
<clID>#{user.username}</clID>
<pw>#{wrong_password}</pw>
<options>
<version>1.0</version>
<lang>en</lang>
</options>
<svcs>
<objURI>https://epp.tld.ee/schema/domain-eis-1.0.xsd</objURI>
<objURI>https://epp.tld.ee/schema/contact-ee-1.1.xsd</objURI>
<objURI>urn:ietf:params:xml:ns:host-1.0</objURI>
<objURI>urn:ietf:params:xml:ns:keyrelay-1.0</objURI>
</svcs>
</login>
</command>
</epp>
XML
assert_no_difference 'EppSession.count' do
post '/epp/session/login', params: { frame: request_xml },
headers: { 'HTTP_COOKIE' => 'session=new-session-id' }
end
assert_epp_response :authentication_error_server_closing_connection
end
def test_password_change
user = users(:api_bestnames)
new_password = 'a' * ApiUser.min_password_length
assert_not_equal new_password, user.plain_text_password
request_xml = <<-XML
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<epp xmlns="https://epp.tld.ee/schema/epp-ee-1.0.xsd">
<command>
<login>
<clID>#{user.username}</clID>
<pw>#{user.plain_text_password}</pw>
<newPW>#{new_password}</newPW>
<options>
<version>1.0</version>
<lang>en</lang>
</options>
<svcs>
<objURI>https://epp.tld.ee/schema/domain-eis-1.0.xsd</objURI>
<objURI>https://epp.tld.ee/schema/contact-ee-1.1.xsd</objURI>
<objURI>urn:ietf:params:xml:ns:host-1.0</objURI>
<objURI>urn:ietf:params:xml:ns:keyrelay-1.0</objURI>
</svcs>
</login>
</command>
</epp>
XML
post '/epp/session/login', params: { frame: request_xml },
headers: { 'HTTP_COOKIE' => 'session=new-session-id' }
user.reload
assert_epp_response :completed_successfully
assert_equal new_password, user.plain_text_password
end
def test_user_cannot_login_when_max_allowed_sessions_per_registrar_is_exceeded
user = users(:api_bestnames)
eliminate_effect_of_existing_epp_sessions
EppSession.sessions_per_registrar = 1
EppSession.create!(session_id: 'any', user: user)
request_xml = <<-XML
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<epp xmlns="https://epp.tld.ee/schema/epp-ee-1.0.xsd">
<command>
<login>
<clID>#{user.username}</clID>
<pw>#{user.plain_text_password}</pw>
<options>
<version>1.0</version>
<lang>en</lang>
</options>
<svcs>
<objURI>https://epp.tld.ee/schema/domain-eis-1.0.xsd</objURI>
<objURI>https://epp.tld.ee/schema/contact-ee-1.1.xsd</objURI>
<objURI>urn:ietf:params:xml:ns:host-1.0</objURI>
<objURI>urn:ietf:params:xml:ns:keyrelay-1.0</objURI>
</svcs>
</login>
</command>
</epp>
XML
assert_no_difference 'EppSession.count' do
post '/epp/session/login', params: { frame: request_xml },
headers: { 'HTTP_COOKIE' => 'session=new-session-id' }
end
assert_epp_response :session_limit_exceeded_server_closing_connection
end
private
def eliminate_effect_of_existing_epp_sessions
EppSession.delete_all
end
end

View file

@ -51,6 +51,7 @@ class EppResponseResultCodeTest < ActiveSupport::TestCase
data_management_policy_violation: 2308,
command_failed: 2400,
authentication_error_server_closing_connection: 2501,
session_limit_exceeded_server_closing_connection: 2502,
}
assert_equal codes, Epp::Response::Result::Code.codes
end
@ -82,6 +83,7 @@ class EppResponseResultCodeTest < ActiveSupport::TestCase
2308 => 'Data management policy violation',
2400 => 'Command failed',
2501 => 'Authentication error; server closing connection',
2502 => 'Session limit exceeded; server closing connection',
}
assert_equal descriptions, Epp::Response::Result::Code.default_descriptions
end

View file

@ -49,15 +49,11 @@ class EppSessionTest < ActiveSupport::TestCase
end
end
def test_limit_per_registrar
assert_equal 4, EppSession.limit_per_registrar
end
def test_limit_is_per_registrar
travel_to Time.zone.parse('2010-07-05')
EppSession.delete_all
EppSession.limit_per_registrar.times do
EppSession.sessions_per_registrar.times do
EppSession.create!(session_id: SecureRandom.hex,
user: users(:api_goodnames),
updated_at: Time.zone.parse('2010-07-05'))