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
|
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
|
@extend Spine.Model.Ajax
|
||||||
@url: @apiPath + '/chats'
|
@url: @apiPath + '/chats'
|
||||||
@countries:
|
@countries:
|
||||||
|
@ -250,6 +250,7 @@ class App.Chat extends App.Model
|
||||||
{ name: 'note', display: 'Note', tag: 'textarea', limit: 250, null: true },
|
{ name: 'note', display: 'Note', tag: 'textarea', limit: 250, null: true },
|
||||||
{ name: 'max_queue', display: 'Max. clients in waitlist', tag: 'input', default: 2 },
|
{ 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: '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: '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: 'active', display: 'Active', tag: 'active', default: true },
|
||||||
{ name: 'created_by_id', display: 'Created by', relation: 'User', readonly: 1 },
|
{ name: 'created_by_id', display: 'Created by', relation: 'User', readonly: 1 },
|
||||||
|
|
|
@ -617,6 +617,23 @@ check if ip address is blocked for chat
|
||||||
|
|
||||||
=begin
|
=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
|
check if country is blocked for chat
|
||||||
|
|
||||||
chat = Chat.find(123)
|
chat = Chat.find(123)
|
||||||
|
@ -633,10 +650,9 @@ check if country is blocked for chat
|
||||||
return false if geo_ip['country_code'].blank?
|
return false if geo_ip['country_code'].blank?
|
||||||
|
|
||||||
countries = block_country.split(';')
|
countries = block_country.split(';')
|
||||||
countries.each do |local_country|
|
countries.any? do |local_country|
|
||||||
return true if geo_ip['country_code'] == local_country
|
geo_ip['country_code'] == local_country
|
||||||
end
|
end
|
||||||
false
|
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
|
@ -483,6 +483,7 @@ class CreateTicket < ActiveRecord::Migration[4.2]
|
||||||
t.boolean :public, null: false, default: false
|
t.boolean :public, null: false, default: false
|
||||||
t.string :block_ip, limit: 5000, null: true
|
t.string :block_ip, limit: 5000, null: true
|
||||||
t.string :block_country, 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.string :preferences, limit: 5000, null: true
|
||||||
t.integer :updated_by_id, null: false
|
t.integer :updated_by_id, null: false
|
||||||
t.integer :created_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
|
user
|
||||||
end
|
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)
|
def permission_check(key, event)
|
||||||
user = current_user
|
user = current_user
|
||||||
return if !user
|
return if !user
|
||||||
|
|
|
@ -24,17 +24,17 @@ return is sent as message back to peer
|
||||||
|
|
||||||
# geo ip lookup
|
# geo ip lookup
|
||||||
geo_ip = nil
|
geo_ip = nil
|
||||||
if @remote_ip
|
if remote_ip
|
||||||
geo_ip = Service::GeoIp.location(@remote_ip)
|
geo_ip = Service::GeoIp.location(remote_ip)
|
||||||
end
|
end
|
||||||
|
|
||||||
# dns lookup
|
# dns lookup
|
||||||
dns_name = nil
|
dns_name = nil
|
||||||
if @remote_ip
|
if remote_ip
|
||||||
begin
|
begin
|
||||||
dns = Resolv::DNS.new
|
dns = Resolv::DNS.new
|
||||||
dns.timeouts = 3
|
dns.timeouts = 3
|
||||||
result = dns.getname @remote_ip
|
result = dns.getname remote_ip
|
||||||
if result
|
if result
|
||||||
dns_name = result.to_s
|
dns_name = result.to_s
|
||||||
end
|
end
|
||||||
|
@ -51,7 +51,7 @@ return is sent as message back to peer
|
||||||
preferences: {
|
preferences: {
|
||||||
url: @payload['data']['url'],
|
url: @payload['data']['url'],
|
||||||
participants: [@client_id],
|
participants: [@client_id],
|
||||||
remote_ip: @remote_ip,
|
remote_ip: remote_ip,
|
||||||
geo_ip: geo_ip,
|
geo_ip: geo_ip,
|
||||||
dns_name: dns_name,
|
dns_name: dns_name,
|
||||||
},
|
},
|
||||||
|
|
|
@ -21,8 +21,9 @@ return is sent as message back to peer
|
||||||
def run
|
def run
|
||||||
return super if super
|
return super if super
|
||||||
return if !check_chat_exists
|
return if !check_chat_exists
|
||||||
return if !check_chat_block_by_ip
|
return if blocked_ip?
|
||||||
return if !check_chat_block_by_country
|
return if blocked_country?
|
||||||
|
return if blocked_origin?
|
||||||
|
|
||||||
# check if it's a chat sessin reconnect
|
# check if it's a chat sessin reconnect
|
||||||
session_id = nil
|
session_id = nil
|
||||||
|
@ -54,10 +55,28 @@ return is sent as message back to peer
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
def check_chat_block_by_ip
|
def blocked_ip?
|
||||||
chat = current_chat
|
return false if !current_chat.blocked_ip?(remote_ip)
|
||||||
return true if !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 = {
|
error = {
|
||||||
event: 'chat_error',
|
event: 'chat_error',
|
||||||
data: {
|
data: {
|
||||||
|
@ -65,21 +84,5 @@ return is sent as message back to peer
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
Sessions.send(@client_id, error)
|
Sessions.send(@client_id, error)
|
||||||
false
|
|
||||||
end
|
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
|
end
|
||||||
|
|
|
@ -48,7 +48,6 @@ class WebsocketServer
|
||||||
|
|
||||||
def self.onopen(websocket, handshake)
|
def self.onopen(websocket, handshake)
|
||||||
headers = handshake.headers
|
headers = handshake.headers
|
||||||
remote_ip = get_remote_ip(headers)
|
|
||||||
client_id = websocket.object_id.to_s
|
client_id = websocket.object_id.to_s
|
||||||
log 'notice', 'Client connected.', client_id
|
log 'notice', 'Client connected.', client_id
|
||||||
Sessions.create( client_id, {}, { type: 'websocket' } )
|
Sessions.create( client_id, {}, { type: 'websocket' } )
|
||||||
|
@ -60,7 +59,6 @@ class WebsocketServer
|
||||||
last_ping: Time.now.utc.to_i,
|
last_ping: Time.now.utc.to_i,
|
||||||
error_count: 0,
|
error_count: 0,
|
||||||
headers: headers,
|
headers: headers,
|
||||||
remote_ip: remote_ip,
|
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -103,7 +101,7 @@ class WebsocketServer
|
||||||
event: data['event'],
|
event: data['event'],
|
||||||
payload: data,
|
payload: data,
|
||||||
session: @clients[client_id][:session],
|
session: @clients[client_id][:session],
|
||||||
remote_ip: @clients[client_id][:remote_ip],
|
headers: @clients[client_id][:headers],
|
||||||
client_id: client_id,
|
client_id: client_id,
|
||||||
clients: @clients,
|
clients: @clients,
|
||||||
options: @options,
|
options: @options,
|
||||||
|
@ -116,12 +114,6 @@ class WebsocketServer
|
||||||
end
|
end
|
||||||
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)
|
def self.websocket_send(client_id, data)
|
||||||
msg = if data.class != Array
|
msg = if data.class != Array
|
||||||
"[#{data.to_json}]"
|
"[#{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