diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 125492d4e..6d50feeb5 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -143,6 +143,17 @@ test:integration:user_agent: - ruby -I test/ test/integration/user_agent_test.rb - rake db:drop +test:integration:user_device: + stage: test + tags: + - core + script: + - export RAILS_ENV=test + - rake db:create + - rake db:migrate + - ruby -I test/ test/integration/user_device_controller_test.rb + - rake db:drop + test:integration:slack: stage: test tags: diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 3203eaca7..9215ccf6d 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -15,11 +15,8 @@ class ApplicationController < ActionController::Base :model_index_render skip_before_action :verify_authenticity_token - before_action :set_user, :session_update - before_action :cors_preflight_check - - after_action :user_device_update, :set_access_control_headers - after_action :trigger_events, :http_log + before_action :set_user, :session_update, :user_device_check, :cors_preflight_check + after_action :trigger_events, :http_log, :set_access_control_headers # For all responses in this controller, return the CORS access control headers. def set_access_control_headers @@ -157,67 +154,65 @@ class ApplicationController < ActionController::Base HttpLog.create(record) end - # user device recent action update - def user_device_update - - # return if we are in switch to user mode - return if session[:switched_from_user_id] - - # only if user_id exists - return if !session[:user_id] - - # only with user device - if !session[:user_device_id] - if params[:fingerprint] - return false if !user_device_log(current_user, 'session') - end - return - end - - # check if entry exists / only if write action - diff = Time.zone.now - 10.minutes - method = request.method - if method == 'GET' || method == 'OPTIONS' || method == 'HEAD' - diff = Time.zone.now - 30.minutes - end - - # only update if needed - return if session[:user_device_update_at] && session[:user_device_update_at] > diff - session[:user_device_update_at] = Time.zone.now - - user_device = UserDevice.action( - session[:user_device_id], - session[:user_agent], - session[:remote_ip], - session[:user_id], - 'session', - ) - - # remember if location has changed - return if !user_device - session[:user_device_id] = user_device.id + def user_device_check + return false if !user_device_log(current_user, 'session') + true end def user_device_log(user, type) + switched_from_user_id = ENV['SWITCHED_FROM_USER_ID'] || session[:switched_from_user_id] + return true if switched_from_user_id + return true if !user - # return if we are in switch to user mode - return true if session[:switched_from_user_id] - - # for sessions we need the fingperprint - if !params[:fingerprint] && type == 'session' - render json: { error: 'Need fingerprint param!' }, status: :unprocessable_entity - return false + time_to_check = true + user_device_updated_at = session[:user_device_updated_at] + if ENV['USER_DEVICE_UPDATED_AT'] + user_device_updated_at = Time.zone.parse(ENV['USER_DEVICE_UPDATED_AT']) end + if user_device_updated_at + # check if entry exists / only if write action + diff = Time.zone.now - 10.minutes + method = request.method + if method == 'GET' || method == 'OPTIONS' || method == 'HEAD' + diff = Time.zone.now - 30.minutes + end + + # only update if needed + if user_device_updated_at > diff + time_to_check = false + end + end + + # if ip has not changed and ttl in still valid + remote_ip = ENV['TEST_REMOTE_IP'] || request.remote_ip + return true if time_to_check == false && session[:user_device_remote_ip] == remote_ip + session[:user_device_remote_ip] = remote_ip + + # for sessions we need the fingperprint + if type == 'session' + if !session[:user_device_updated_at] && !params[:fingerprint] && !session[:user_device_fingerprint] + render json: { error: 'Need fingerprint param!' }, status: :unprocessable_entity + return false + end + if params[:fingerprint] + session[:user_device_fingerprint] = params[:fingerprint] + end + end + + session[:user_device_updated_at] = Time.zone.now + # add device if needed - user_device = UserDevice.add( - request.env['HTTP_USER_AGENT'], - request.remote_ip, - user.id, - params[:fingerprint], - type, + http_user_agent = ENV['HTTP_USER_AGENT'] || request.env['HTTP_USER_AGENT'] + Delayed::Job.enqueue( + Observer::UserDeviceLogJob.new( + http_user_agent, + remote_ip, + user.id, + session[:user_device_fingerprint], + type, + ) ) - session[:user_device_id] = user_device.id end def authentication_check_only(auth_param) diff --git a/app/models/observer/user_device_log_job.rb b/app/models/observer/user_device_log_job.rb new file mode 100644 index 000000000..72c4b2a34 --- /dev/null +++ b/app/models/observer/user_device_log_job.rb @@ -0,0 +1,19 @@ +class Observer::UserDeviceLogJob + def initialize(http_user_agent, remote_ip, user_id, fingerprint, type) + @http_user_agent = http_user_agent + @remote_ip = remote_ip + @user_id = user_id + @fingerprint = fingerprint + @type = type + end + + def perform + UserDevice.add( + @http_user_agent, + @remote_ip, + @user_id, + @fingerprint, + @type, + ) + end +end diff --git a/app/models/user_device.rb b/app/models/user_device.rb index f022ab207..c4a4ce13a 100644 --- a/app/models/user_device.rb +++ b/app/models/user_device.rb @@ -193,6 +193,8 @@ send user notification about new device or new location for device def notification_send(template) user = User.find(user_id) + Rails.logger.debug "Send notification (#{template}) to: #{user.email}" + NotificationFactory::Mailer.notification( template: template, user: user, diff --git a/test/integration/user_device_controller_test.rb b/test/integration/user_device_controller_test.rb new file mode 100644 index 000000000..6bc2f6f03 --- /dev/null +++ b/test/integration/user_device_controller_test.rb @@ -0,0 +1,425 @@ +# encoding: utf-8 +require 'test_helper' + +class UserDeviceControllerTest < ActionDispatch::IntegrationTest + setup do + + # set accept header + @headers = { 'ACCEPT' => 'application/json', 'CONTENT_TYPE' => 'application/json' } + + # create agent + roles = Role.where( name: %w(Admin Agent) ) + groups = Group.all + + UserInfo.current_user_id = 1 + @admin = User.create_or_update( + login: 'user-device-admin', + firstname: 'UserDevice', + lastname: 'Admin', + email: 'user-device-admin@example.com', + password: 'adminpw', + active: true, + roles: roles, + groups: groups, + ) + + # create agent + roles = Role.where( name: 'Agent' ) + @agent = User.create_or_update( + login: 'user-device-agent', + firstname: 'UserDevice', + lastname: 'Agent', + email: 'user-device-agent@example.com', + password: 'agentpw', + active: true, + roles: roles, + groups: groups, + ) + + ENV['TEST_REMOTE_IP'] = '5.9.62.170' # de + ENV['HTTP_USER_AGENT'] = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10.11; rv:46.0) Gecko/20100101 Firefox/46.0' + end + + test '01 - index with nobody' do + + get '/api/v1/signshow' + assert_response(200) + result = JSON.parse(@response.body) + assert_equal(result.class, Hash) + assert_equal(result['error'], 'no valid session') + assert(result['config']) + assert_not(controller.session[:user_device_fingerprint]) + + Scheduler.worker(true) + + end + + test '02 - login index with admin without fingerprint' do + + assert_equal(0, UserDevice.where(user_id: @admin.id).count) + assert_equal(0, email_notification_count('user_device_new', @admin.email)) + assert_equal(0, email_notification_count('user_device_new_location', @admin.email)) + + params = { without_fingerprint: 'none', username: 'user-device-admin', password: 'adminpw' } + post '/api/v1/signin', params.to_json, @headers + assert_response(422) + result = JSON.parse(@response.body) + + assert_equal(result.class, Hash) + assert_equal('Need fingerprint param!', result['error']) + assert_not(result['config']) + assert_not(controller.session[:user_device_fingerprint]) + + Scheduler.worker(true) + + assert_equal(0, UserDevice.where(user_id: @admin.id).count) + assert_equal(0, email_notification_count('user_device_new', @admin.email)) + assert_equal(0, email_notification_count('user_device_new_location', @admin.email)) + + end + + test '03 - login index with admin with fingerprint - I' do + + assert_equal(0, UserDevice.where(user_id: @admin.id).count) + assert_equal(0, email_notification_count('user_device_new', @admin.email)) + assert_equal(0, email_notification_count('user_device_new_location', @admin.email)) + + params = { fingerprint: 'my_finger_print', username: 'user-device-admin', password: 'adminpw' } + post '/api/v1/signin', params.to_json, @headers + assert_response(201) + result = JSON.parse(@response.body) + assert_equal(result.class, Hash) + assert_not(result['error']) + assert(result['config']) + assert('my_finger_print', controller.session[:user_device_fingerprint]) + + Scheduler.worker(true) + + assert_equal(1, UserDevice.where(user_id: @admin.id).count) + assert_equal(0, email_notification_count('user_device_new', @admin.email)) + assert_equal(0, email_notification_count('user_device_new_location', @admin.email)) + user_device_first = UserDevice.last + sleep 2 + + params = {} + get '/api/v1/users', params.to_json, @headers + assert_response(200) + result = JSON.parse(@response.body) + assert_equal(result.class, Array) + assert('my_finger_print', controller.session[:user_device_fingerprint]) + + Scheduler.worker(true) + + assert_equal(1, UserDevice.where(user_id: @admin.id).count) + assert_equal(0, email_notification_count('user_device_new', @admin.email)) + assert_equal(0, email_notification_count('user_device_new_location', @admin.email)) + user_device_last = UserDevice.last + assert_equal(user_device_last.updated_at.to_s, user_device_first.updated_at.to_s) + + params = { fingerprint: 'my_finger_print' } + get '/api/v1/signshow', params, @headers + assert_response(200) + result = JSON.parse(@response.body) + assert_equal(result.class, Hash) + assert(result['session']) + assert_equal(result['session']['login'], 'user-device-admin') + assert(result['config']) + + Scheduler.worker(true) + + assert_equal(1, UserDevice.where(user_id: @admin.id).count) + assert_equal(0, email_notification_count('user_device_new', @admin.email)) + assert_equal(0, email_notification_count('user_device_new_location', @admin.email)) + user_device_last = UserDevice.last + assert_equal(user_device_last.updated_at.to_s, user_device_first.updated_at.to_s) + + ENV['USER_DEVICE_UPDATED_AT'] = (Time.zone.now - 4.hours).to_s + params = {} + get '/api/v1/users', params.to_json, @headers + assert_response(200) + result = JSON.parse(@response.body) + assert_equal(result.class, Array) + assert('my_finger_print', controller.session[:user_device_fingerprint]) + + Scheduler.worker(true) + + assert_equal(1, UserDevice.where(user_id: @admin.id).count) + assert_equal(0, email_notification_count('user_device_new', @admin.email)) + assert_equal(0, email_notification_count('user_device_new_location', @admin.email)) + user_device_last = UserDevice.last + assert_not_equal(user_device_last.updated_at.to_s, user_device_first.updated_at.to_s) + ENV['USER_DEVICE_UPDATED_AT'] = nil + + ENV['TEST_REMOTE_IP'] = '195.65.29.254' # ch + + params = {} + get '/api/v1/users', params.to_json, @headers + assert_response(200) + result = JSON.parse(@response.body) + + Scheduler.worker(true) + + assert_equal(2, UserDevice.where(user_id: @admin.id).count) + assert_equal(0, email_notification_count('user_device_new', @admin.email)) + assert_equal(1, email_notification_count('user_device_new_location', @admin.email)) + + # ip reset + ENV['TEST_REMOTE_IP'] = '5.9.62.170' # de + + end + + test '04 - login index with admin with fingerprint - II' do + + params = { fingerprint: 'my_finger_print_II', username: 'user-device-admin', password: 'adminpw' } + post '/api/v1/signin', params.to_json, @headers + assert_response(201) + result = JSON.parse(@response.body) + + Scheduler.worker(true) + + assert_equal(3, UserDevice.where(user_id: @admin.id).count) + assert_equal(1, email_notification_count('user_device_new', @admin.email)) + assert_equal(0, email_notification_count('user_device_new_location', @admin.email)) + assert_equal(result.class, Hash) + assert_not(result['error']) + assert(result['config']) + assert('my_finger_print_II', controller.session[:user_device_fingerprint]) + + get '/api/v1/users', params.to_json, @headers + assert_response(200) + result = JSON.parse(@response.body) + assert_equal(result.class, Array) + + Scheduler.worker(true) + + assert_equal(3, UserDevice.where(user_id: @admin.id).count) + assert_equal(1, email_notification_count('user_device_new', @admin.email)) + assert_equal(0, email_notification_count('user_device_new_location', @admin.email)) + + params = { fingerprint: 'my_finger_print_II' } + get '/api/v1/signshow', params, @headers + assert_response(200) + result = JSON.parse(@response.body) + assert_equal(result.class, Hash) + assert(result['session']) + assert_equal(result['session']['login'], 'user-device-admin') + assert(result['config']) + + Scheduler.worker(true) + + assert_equal(3, UserDevice.where(user_id: @admin.id).count) + assert_equal(1, email_notification_count('user_device_new', @admin.email)) + assert_equal(0, email_notification_count('user_device_new_location', @admin.email)) + + ENV['TEST_REMOTE_IP'] = '195.65.29.254' # ch + + params = {} + get '/api/v1/users', params.to_json, @headers + assert_response(200) + result = JSON.parse(@response.body) + + Scheduler.worker(true) + + assert_equal(4, UserDevice.where(user_id: @admin.id).count) + assert_equal(1, email_notification_count('user_device_new', @admin.email)) + assert_equal(1, email_notification_count('user_device_new_location', @admin.email)) + + # ip reset + ENV['TEST_REMOTE_IP'] = '5.9.62.170' # de + + end + + test '05 - login index with admin with fingerprint - II' do + + params = { fingerprint: 'my_finger_print_II', username: 'user-device-admin', password: 'adminpw' } + post '/api/v1/signin', params.to_json, @headers + assert_response(201) + result = JSON.parse(@response.body) + + Scheduler.worker(true) + + assert_equal(4, UserDevice.where(user_id: @admin.id).count) + assert_equal(0, email_notification_count('user_device_new', @admin.email)) + assert_equal(0, email_notification_count('user_device_new_location', @admin.email)) + assert_equal(result.class, Hash) + assert_not(result['error']) + assert(result['config']) + assert('my_finger_print_II', controller.session[:user_device_fingerprint]) + end + + test '06 - login index with admin with basic auth' do + + ENV['HTTP_USER_AGENT'] = 'curl 1.2.3' + credentials = ActionController::HttpAuthentication::Basic.encode_credentials('user-device-admin', 'adminpw') + + params = {} + get '/api/v1/users', params.to_json, @headers.merge('Authorization' => credentials) + assert_response(200) + result = JSON.parse(@response.body) + + Scheduler.worker(true) + + assert_equal(5, UserDevice.where(user_id: @admin.id).count) + assert_equal(1, email_notification_count('user_device_new', @admin.email)) + assert_equal(0, email_notification_count('user_device_new_location', @admin.email)) + assert_equal(result.class, Array) + user_device_first = UserDevice.last + sleep 2 + + params = {} + get '/api/v1/users', params.to_json, @headers.merge('Authorization' => credentials) + assert_response(200) + result = JSON.parse(@response.body) + + Scheduler.worker(true) + + assert_equal(5, UserDevice.where(user_id: @admin.id).count) + assert_equal(1, email_notification_count('user_device_new', @admin.email)) + assert_equal(0, email_notification_count('user_device_new_location', @admin.email)) + assert_equal(result.class, Array) + user_device_last = UserDevice.last + assert_equal(user_device_last.updated_at.to_s, user_device_first.updated_at.to_s) + + ENV['USER_DEVICE_UPDATED_AT'] = (Time.zone.now - 4.hours).to_s + params = {} + get '/api/v1/users', params, @headers.merge('Authorization' => credentials) + assert_response(200) + result = JSON.parse(@response.body) + + Scheduler.worker(true) + + assert_equal(5, UserDevice.where(user_id: @admin.id).count) + assert_equal(1, email_notification_count('user_device_new', @admin.email)) + assert_equal(0, email_notification_count('user_device_new_location', @admin.email)) + assert_equal(result.class, Array) + user_device_last = UserDevice.last + assert_not_equal(user_device_last.updated_at.to_s, user_device_first.updated_at.to_s) + ENV['USER_DEVICE_UPDATED_AT'] = nil + end + + test '07 - login index with admin with basic auth' do + + ENV['HTTP_USER_AGENT'] = 'curl 1.2.3' + credentials = ActionController::HttpAuthentication::Basic.encode_credentials('user-device-admin', 'adminpw') + + params = {} + get '/api/v1/users', params.to_json, @headers.merge('Authorization' => credentials) + assert_response(200) + result = JSON.parse(@response.body) + + Scheduler.worker(true) + + assert_equal(5, UserDevice.where(user_id: @admin.id).count) + assert_equal(0, email_notification_count('user_device_new', @admin.email)) + assert_equal(0, email_notification_count('user_device_new_location', @admin.email)) + assert_equal(result.class, Array) + + end + + test '08 - login index with agent with basic auth' do + + assert_equal(0, UserDevice.where(user_id: @agent.id).count) + assert_equal(0, email_notification_count('user_device_new', @agent.email)) + assert_equal(0, email_notification_count('user_device_new_location', @agent.email)) + + ENV['HTTP_USER_AGENT'] = 'curl 1.2.3' + credentials = ActionController::HttpAuthentication::Basic.encode_credentials('user-device-agent', 'agentpw') + + params = {} + get '/api/v1/users', params.to_json, @headers.merge('Authorization' => credentials) + assert_response(200) + result = JSON.parse(@response.body) + + Scheduler.worker(true) + + assert_equal(1, UserDevice.where(user_id: @agent.id).count) + assert_equal(0, email_notification_count('user_device_new', @agent.email)) + assert_equal(0, email_notification_count('user_device_new_location', @agent.email)) + assert_equal(result.class, Array) + + end + + test '09 - login index with agent with basic auth' do + + assert_equal(1, UserDevice.where(user_id: @agent.id).count) + assert_equal(0, email_notification_count('user_device_new', @agent.email)) + assert_equal(0, email_notification_count('user_device_new_location', @agent.email)) + + ENV['HTTP_USER_AGENT'] = 'curl 1.2.3' + credentials = ActionController::HttpAuthentication::Basic.encode_credentials('user-device-agent', 'agentpw') + + params = {} + get '/api/v1/users', params.to_json, @headers.merge('Authorization' => credentials) + assert_response(200) + result = JSON.parse(@response.body) + + Scheduler.worker(true) + + assert_equal(1, UserDevice.where(user_id: @agent.id).count) + assert_equal(0, email_notification_count('user_device_new', @agent.email)) + assert_equal(0, email_notification_count('user_device_new_location', @agent.email)) + assert_equal(result.class, Array) + + end + + test '10 - login with switched_from_user_id' do + + assert_equal(1, UserDevice.where(user_id: @agent.id).count) + assert_equal(0, email_notification_count('user_device_new', @agent.email)) + assert_equal(0, email_notification_count('user_device_new_location', @agent.email)) + + ENV['SWITCHED_FROM_USER_ID'] = @admin.id.to_s + + params = { fingerprint: 'my_finger_print_II', username: 'user-device-agent', password: 'agentpw' } + post '/api/v1/signin', params.to_json, @headers + assert_response(201) + result = JSON.parse(@response.body) + + Scheduler.worker(true) + + assert_equal(1, UserDevice.where(user_id: @agent.id).count) + assert_equal(0, email_notification_count('user_device_new', @agent.email)) + assert_equal(0, email_notification_count('user_device_new_location', @agent.email)) + assert_equal(result.class, Hash) + assert_not(result['error']) + assert(result['config']) + assert('my_finger_print_II', controller.session[:user_device_fingerprint]) + + Scheduler.worker(true) + + assert_equal(1, UserDevice.where(user_id: @agent.id).count) + assert_equal(0, email_notification_count('user_device_new', @agent.email)) + assert_equal(0, email_notification_count('user_device_new_location', @agent.email)) + + ENV['USER_DEVICE_UPDATED_AT'] = (Time.zone.now - 4.hours).to_s + params = {} + get '/api/v1/users', params.to_json, @headers + assert_response(200) + result = JSON.parse(@response.body) + assert_equal(result.class, Array) + assert('my_finger_print_II', controller.session[:user_device_fingerprint]) + + Scheduler.worker(true) + + assert_equal(1, UserDevice.where(user_id: @agent.id).count) + assert_equal(0, email_notification_count('user_device_new', @agent.email)) + assert_equal(0, email_notification_count('user_device_new_location', @agent.email)) + ENV['USER_DEVICE_UPDATED_AT'] = nil + + ENV['TEST_REMOTE_IP'] = '195.65.29.254' # ch + params = {} + get '/api/v1/users', params.to_json, @headers + assert_response(200) + result = JSON.parse(@response.body) + + Scheduler.worker(true) + + # ip reset + ENV['TEST_REMOTE_IP'] = '5.9.62.170' # de + + assert_equal(1, UserDevice.where(user_id: @agent.id).count) + assert_equal(0, email_notification_count('user_device_new', @agent.email)) + assert_equal(0, email_notification_count('user_device_new_location', @agent.email)) + + end +end diff --git a/test/test_helper.rb b/test/test_helper.rb index 8d98c6c4b..c05123ee3 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -37,7 +37,27 @@ class ActiveSupport::TestCase # set current user UserInfo.current_user_id = nil + + Rails.logger.info '++++NEW++++TEST++++' end # Add more helper methods to be used by all tests here... + def email_notification_count(type, recipient) + + # read config file and count type & recipients + file = "#{Rails.root}/log/#{Rails.env}.log" + lines = [] + IO.foreach(file) do |line| + lines.push line + end + count = 0 + lines.reverse.each {|line| + break if line =~ /\+\+\+\+NEW\+\+\+\+TEST\+\+\+\+/ + next if line !~ /Send notification \(#{type}\)/ + next if line !~ /to:\s#{recipient}/ + count += 1 + } + count + end + end