Feature: Chat origin whitelisting functionality.
This commit is contained in:
parent
eb830a2e15
commit
33498bac91
10 changed files with 96 additions and 39 deletions
|
@ -1,5 +1,5 @@
|
|||
class App.Chat extends App.Model
|
||||
@configure 'Chat', 'name', 'active', 'public', 'max_queue', 'block_ip', 'block_country', 'note'
|
||||
@configure 'Chat', 'name', 'active', 'public', 'max_queue', 'block_ip', 'whitelisted_websites', 'block_country', 'note'
|
||||
@extend Spine.Model.Ajax
|
||||
@url: @apiPath + '/chats'
|
||||
@countries:
|
||||
|
@ -250,6 +250,7 @@ class App.Chat extends App.Model
|
|||
{ name: 'note', display: 'Note', tag: 'textarea', limit: 250, null: true },
|
||||
{ name: 'max_queue', display: 'Max. clients in waitlist', tag: 'input', default: 2 },
|
||||
{ name: 'block_ip', display: 'Blocked IPs (separated by ;)', tag: 'input', default: '', null: true },
|
||||
{ name: 'whitelisted_websites', display: 'Allow websites (separated by ;)', tag: 'input', default: '', null: true },
|
||||
{ name: 'block_country', display: 'Blocked countries', tag: 'column_select', multiple: true, null: true, default: '', options: @countries, seperator: ';' },
|
||||
{ name: 'active', display: 'Active', tag: 'active', default: true },
|
||||
{ name: 'created_by_id', display: 'Created by', relation: 'User', readonly: 1 },
|
||||
|
|
|
@ -617,6 +617,23 @@ check if ip address is blocked for chat
|
|||
|
||||
=begin
|
||||
|
||||
check if website is allowed for chat
|
||||
|
||||
chat = Chat.find(123)
|
||||
chat.website_whitelisted?('zammad.org')
|
||||
|
||||
=end
|
||||
|
||||
def website_whitelisted?(website)
|
||||
return true if whitelisted_websites.blank?
|
||||
|
||||
whitelisted_websites.split(';').any? do |whitelisted_website|
|
||||
website.downcase.include?(whitelisted_website.downcase.strip)
|
||||
end
|
||||
end
|
||||
|
||||
=begin
|
||||
|
||||
check if country is blocked for chat
|
||||
|
||||
chat = Chat.find(123)
|
||||
|
@ -633,10 +650,9 @@ check if country is blocked for chat
|
|||
return false if geo_ip['country_code'].blank?
|
||||
|
||||
countries = block_country.split(';')
|
||||
countries.each do |local_country|
|
||||
return true if geo_ip['country_code'] == local_country
|
||||
countries.any? do |local_country|
|
||||
geo_ip['country_code'] == local_country
|
||||
end
|
||||
false
|
||||
end
|
||||
|
||||
end
|
||||
|
|
|
@ -483,6 +483,7 @@ class CreateTicket < ActiveRecord::Migration[4.2]
|
|||
t.boolean :public, null: false, default: false
|
||||
t.string :block_ip, limit: 5000, null: true
|
||||
t.string :block_country, limit: 5000, null: true
|
||||
t.string :whitelisted_websites, limit: 5000, null: true
|
||||
t.string :preferences, limit: 5000, null: true
|
||||
t.integer :updated_by_id, null: false
|
||||
t.integer :created_by_id, null: false
|
||||
|
|
9
db/migrate/20200205000001_chat_add_allow_website.rb
Normal file
9
db/migrate/20200205000001_chat_add_allow_website.rb
Normal file
|
@ -0,0 +1,9 @@
|
|||
class ChatAddAllowWebsite < ActiveRecord::Migration[5.1]
|
||||
def up
|
||||
|
||||
# return if it's a new setup
|
||||
return if !Setting.find_by(name: 'system_init_done')
|
||||
|
||||
add_column :chats, :whitelisted_websites, :string, limit: 5000, null: true
|
||||
end
|
||||
end
|
|
@ -105,6 +105,14 @@ class Sessions::Event::Base
|
|||
user
|
||||
end
|
||||
|
||||
def remote_ip
|
||||
@headers&.fetch('X-Forwarded-For', nil).presence
|
||||
end
|
||||
|
||||
def origin
|
||||
@headers&.fetch('Origin', nil).presence
|
||||
end
|
||||
|
||||
def permission_check(key, event)
|
||||
user = current_user
|
||||
return if !user
|
||||
|
|
|
@ -24,17 +24,17 @@ return is sent as message back to peer
|
|||
|
||||
# geo ip lookup
|
||||
geo_ip = nil
|
||||
if @remote_ip
|
||||
geo_ip = Service::GeoIp.location(@remote_ip)
|
||||
if remote_ip
|
||||
geo_ip = Service::GeoIp.location(remote_ip)
|
||||
end
|
||||
|
||||
# dns lookup
|
||||
dns_name = nil
|
||||
if @remote_ip
|
||||
if remote_ip
|
||||
begin
|
||||
dns = Resolv::DNS.new
|
||||
dns.timeouts = 3
|
||||
result = dns.getname @remote_ip
|
||||
result = dns.getname remote_ip
|
||||
if result
|
||||
dns_name = result.to_s
|
||||
end
|
||||
|
@ -51,7 +51,7 @@ return is sent as message back to peer
|
|||
preferences: {
|
||||
url: @payload['data']['url'],
|
||||
participants: [@client_id],
|
||||
remote_ip: @remote_ip,
|
||||
remote_ip: remote_ip,
|
||||
geo_ip: geo_ip,
|
||||
dns_name: dns_name,
|
||||
},
|
||||
|
|
|
@ -21,8 +21,9 @@ return is sent as message back to peer
|
|||
def run
|
||||
return super if super
|
||||
return if !check_chat_exists
|
||||
return if !check_chat_block_by_ip
|
||||
return if !check_chat_block_by_country
|
||||
return if blocked_ip?
|
||||
return if blocked_country?
|
||||
return if blocked_origin?
|
||||
|
||||
# check if it's a chat sessin reconnect
|
||||
session_id = nil
|
||||
|
@ -54,10 +55,28 @@ return is sent as message back to peer
|
|||
}
|
||||
end
|
||||
|
||||
def check_chat_block_by_ip
|
||||
chat = current_chat
|
||||
return true if !chat.blocked_ip?(@remote_ip)
|
||||
def blocked_ip?
|
||||
return false if !current_chat.blocked_ip?(remote_ip)
|
||||
|
||||
send_unavailable
|
||||
true
|
||||
end
|
||||
|
||||
def blocked_country?
|
||||
return false if !current_chat.blocked_country?(remote_ip)
|
||||
|
||||
send_unavailable
|
||||
true
|
||||
end
|
||||
|
||||
def blocked_origin?
|
||||
return false if current_chat.website_whitelisted?(origin)
|
||||
|
||||
send_unavailable
|
||||
true
|
||||
end
|
||||
|
||||
def send_unavailable
|
||||
error = {
|
||||
event: 'chat_error',
|
||||
data: {
|
||||
|
@ -65,21 +84,5 @@ return is sent as message back to peer
|
|||
},
|
||||
}
|
||||
Sessions.send(@client_id, error)
|
||||
false
|
||||
end
|
||||
|
||||
def check_chat_block_by_country
|
||||
chat = current_chat
|
||||
return true if !chat.blocked_country?(@remote_ip)
|
||||
|
||||
error = {
|
||||
event: 'chat_error',
|
||||
data: {
|
||||
state: 'chat_unavailable',
|
||||
},
|
||||
}
|
||||
Sessions.send(@client_id, error)
|
||||
false
|
||||
end
|
||||
|
||||
end
|
||||
|
|
|
@ -48,7 +48,6 @@ class WebsocketServer
|
|||
|
||||
def self.onopen(websocket, handshake)
|
||||
headers = handshake.headers
|
||||
remote_ip = get_remote_ip(headers)
|
||||
client_id = websocket.object_id.to_s
|
||||
log 'notice', 'Client connected.', client_id
|
||||
Sessions.create( client_id, {}, { type: 'websocket' } )
|
||||
|
@ -60,7 +59,6 @@ class WebsocketServer
|
|||
last_ping: Time.now.utc.to_i,
|
||||
error_count: 0,
|
||||
headers: headers,
|
||||
remote_ip: remote_ip,
|
||||
}
|
||||
end
|
||||
|
||||
|
@ -103,7 +101,7 @@ class WebsocketServer
|
|||
event: data['event'],
|
||||
payload: data,
|
||||
session: @clients[client_id][:session],
|
||||
remote_ip: @clients[client_id][:remote_ip],
|
||||
headers: @clients[client_id][:headers],
|
||||
client_id: client_id,
|
||||
clients: @clients,
|
||||
options: @options,
|
||||
|
@ -116,12 +114,6 @@ class WebsocketServer
|
|||
end
|
||||
end
|
||||
|
||||
def self.get_remote_ip(headers)
|
||||
return headers['X-Forwarded-For'] if headers && headers['X-Forwarded-For']
|
||||
|
||||
nil
|
||||
end
|
||||
|
||||
def self.websocket_send(client_id, data)
|
||||
msg = if data.class != Array
|
||||
"[#{data.to_json}]"
|
||||
|
|
9
spec/factories/chat.rb
Normal file
9
spec/factories/chat.rb
Normal file
|
@ -0,0 +1,9 @@
|
|||
FactoryBot.define do
|
||||
factory :chat do
|
||||
sequence(:name) { |n| "Chat #{n}" }
|
||||
max_queue { 5 }
|
||||
active { true }
|
||||
created_by_id { 1 }
|
||||
updated_by_id { 1 }
|
||||
end
|
||||
end
|
18
spec/models/chat_spec.rb
Normal file
18
spec/models/chat_spec.rb
Normal file
|
@ -0,0 +1,18 @@
|
|||
require 'rails_helper'
|
||||
|
||||
RSpec.describe Chat, type: :model do
|
||||
|
||||
describe 'website whitelisting' do
|
||||
let(:chat) { create(:chat, whitelisted_websites: 'zammad.org') }
|
||||
|
||||
it 'detects whitelisted website' do
|
||||
result = chat.website_whitelisted?('https://www.zammad.org')
|
||||
expect(result).to be true
|
||||
end
|
||||
|
||||
it 'detects non-whitelisted website' do
|
||||
result = chat.website_whitelisted?('https://www.zammad.com')
|
||||
expect(result).to be false
|
||||
end
|
||||
end
|
||||
end
|
Loading…
Reference in a new issue