Skip to content

Commit

Permalink
Fixing conflict
Browse files Browse the repository at this point in the history
  • Loading branch information
caiosba committed Jun 10, 2024
2 parents 8349d56 + 6826b16 commit b4a0a7a
Show file tree
Hide file tree
Showing 13 changed files with 188 additions and 51 deletions.
11 changes: 6 additions & 5 deletions app/mailers/security_mailer.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,23 +4,25 @@ class SecurityMailer < ApplicationMailer
def notify(user_id, type, activity_id)
user = User.find_by_id(user_id)
activity = LoginActivity.find_by_id(activity_id)
return if user.nil? || activity.nil?
address = []
Geocoder.configure(language: I18n.locale)
ip_result = Geocoder.search(activity&.ip)&.first
ip_result = Geocoder.search(activity.ip)&.first
unless ip_result.blank? || ip_result.data["loc"].blank?
loc_result = Geocoder.search(ip_result.data["loc"]).first
address = [loc_result.city, loc_result.country] unless loc_result.nil?
end
@user = user
@type = type
email = user.email
@user_agent = UserAgent.parse(activity.user_agent)
user_agent = UserAgent.parse(activity.user_agent)
@location = address.compact.join(', ')
@timestamp = activity.created_at
@ip = activity.ip
@platform = begin @user_agent.os.split.first rescue 'Unknown' end
@platform = begin user_agent.os.split.first rescue 'Unknown' end
@browser = begin user_agent.browser rescue 'Unknown' end
subject = I18n.t("mail_security.#{type}_subject",
app_name: CheckConfig.get('app_name'), browser: @user_agent.browser, platform: @platform)
app_name: CheckConfig.get('app_name'), browser: @browser, platform: @platform)
mail(to: email, subject: subject)
end

Expand All @@ -29,5 +31,4 @@ def custom_notification(user_id, subject)
attachments.inline['signup.png'] = File.read('public/images/signup.png')
mail(to: @user.email, subject: subject)
end

end
3 changes: 2 additions & 1 deletion app/models/bot/smooch.rb
Original file line number Diff line number Diff line change
Expand Up @@ -821,7 +821,8 @@ def self.save_text_message(message)
pm = nil
extra = {}
if link.nil?
claim = self.extract_claim(text).gsub(/\s+/, ' ').strip
# strip and remove null bytes
claim = self.extract_claim(text).gsub(/\s+/, ' ').strip.gsub("\u0000", "\\u0000")
extra = { quote: claim }
pm = ProjectMedia.joins(:media).where('trim(lower(quote)) = ?', claim.downcase).where('project_medias.team_id' => team.id).last
# Don't create a new text media if it's an unconfirmed request with just a few words
Expand Down
36 changes: 22 additions & 14 deletions app/models/concerns/alegre_v2.rb
Original file line number Diff line number Diff line change
Expand Up @@ -355,8 +355,7 @@ def get_confirmed_items_async(project_media, field, threshold=nil)
end

def get_similar_items_v2(project_media, field, threshold=nil)
type = get_type(project_media)
if !should_get_similar_items_of_type?('master', project_media.team_id) || !should_get_similar_items_of_type?(type, project_media.team_id)
if similarity_disabled_for_project_media?(project_media)
{}
else
suggested_or_confirmed = get_suggested_items(project_media, field, threshold)
Expand All @@ -365,9 +364,13 @@ def get_similar_items_v2(project_media, field, threshold=nil)
end
end

def similarity_disabled_for_project_media?(project_media)
type = Bot::Alegre.get_type(project_media)
!should_get_similar_items_of_type?('master', project_media.team_id) || !should_get_similar_items_of_type?(type, project_media.team_id)
end

def get_similar_items_v2_async(project_media, field, threshold=nil)
type = get_type(project_media)
if !should_get_similar_items_of_type?('master', project_media.team_id) || !should_get_similar_items_of_type?(type, project_media.team_id)
if similarity_disabled_for_project_media?(project_media)
return false
else
get_suggested_items_async(project_media, field, threshold)
Expand Down Expand Up @@ -398,8 +401,7 @@ def get_cached_data(required_keys)
end

def get_similar_items_v2_callback(project_media, field)
type = get_type(project_media)
if !should_get_similar_items_of_type?('master', project_media.team_id) || !should_get_similar_items_of_type?(type, project_media.team_id)
if similarity_disabled_for_project_media?(project_media)
return {}
else
cached_data = get_cached_data(get_required_keys(project_media, field))
Expand Down Expand Up @@ -427,6 +429,19 @@ def is_cached_data_not_good(cached_data)
cached_data.values.collect{|x| x.nil?}.include?(true)
end

def wait_for_results(project_media, args)
return {} if similarity_disabled_for_project_media?(project_media)
cached_data = get_cached_data(get_required_keys(project_media, nil))
timeout = args[:timeout] || 60
start_time = Time.now
while start_time + timeout > Time.now && is_cached_data_not_good(cached_data) #more robust for any type of null response
sleep(1)
cached_data = get_cached_data(get_required_keys(project_media, nil))
end
CheckSentry.notify(AlegreTimeoutError.new('Timeout when waiting for async response from Alegre'), params: args.merge({ cached_data: cached_data }).merge({time: Time.now, start_time: start_time, timeout: timeout})) if start_time + timeout < Time.now
return cached_data
end

def get_items_with_similar_media_v2(args={})
media_url = args[:media_url]
project_media = args[:project_media]
Expand All @@ -442,14 +457,7 @@ def get_items_with_similar_media_v2(args={})
project_media.type = type
end
get_similar_items_v2_async(project_media, nil, threshold)
cached_data = get_cached_data(get_required_keys(project_media, nil))
timeout = args[:timeout] || 60
start_time = Time.now
while start_time + timeout > Time.now && is_cached_data_not_good(cached_data) #more robust for any type of null response
sleep(1)
cached_data = get_cached_data(get_required_keys(project_media, nil))
end
CheckSentry.notify(AlegreTimeoutError.new('Timeout when waiting for async response from Alegre'), params: args.merge({ cached_data: cached_data.merge(time: Time.now, start_time: start_time, timeout: timeout) })) if start_time + timeout < Time.now
wait_for_results(project_media, args)
response = get_similar_items_v2_callback(project_media, nil)
delete(project_media, nil) if project_media.is_a?(TemporaryProjectMedia)
return response
Expand Down
5 changes: 3 additions & 2 deletions app/models/fact_check.rb
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ def update_report
reports = pm.get_dynamic_annotation('report_design') || Dynamic.new(annotation_type: 'report_design', annotated: pm)
data = reports.data.to_h.with_indifferent_access
report = data[:options]
language = self.language || pm.team.default_language || 'en'
language = self.language || pm.team.default_language
report_language = report.to_h.with_indifferent_access[:language]
default_use_introduction = !!reports.report_design_team_setting_value('use_introduction', language)
default_introduction = reports.report_design_team_setting_value('introduction', language).to_s
Expand All @@ -70,7 +70,8 @@ def update_report
})
report.merge!({ use_introduction: default_use_introduction, introduction: default_introduction }) if language != report_language
data[:options] = report
data[:state] = (self.publish_report ? 'published' : 'paused') if data[:state].blank? || !self.publish_report.nil?
unpublished_state = data[:state].blank? ? 'unpublished' : 'paused'
data[:state] = (self.publish_report ? 'published' : unpublished_state) if data[:state].blank? || !self.publish_report.nil?
reports.annotator = self.user || User.current
reports.set_fields = data.to_json
reports.skip_check_ability = true
Expand Down
4 changes: 2 additions & 2 deletions app/views/security_mailer/notify.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@
</tr>
</table>
<div class="h1 notify-title__header" style="font-size: 40px; font-weight: bold; letter-spacing: -0.8px; line-height: 40px;">
<%= I18n.t("mail_security.#{@type}", browser: @user_agent.browser, platform: @platform,
<%= I18n.t("mail_security.#{@type}", browser: @browser, platform: @platform,
location: @location)
%>
</div>
Expand Down Expand Up @@ -131,7 +131,7 @@
</table>
</div>
<div class="text-gray" style="color: #9a9a9a !important;"><span class="span" style="font-size: 15px; line-height: 15px;">
<%= I18n.t("mail_security.devise_name", browser: @user_agent.browser, platform: @platform) %>
<%= I18n.t("mail_security.devise_name", browser: @browser, platform: @platform) %>
</span>
</div>
</div>
Expand Down
4 changes: 2 additions & 2 deletions app/views/security_mailer/notify.text.erb
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
<%= I18n.t("mails_notifications.greeting", username: @user.name) %>
=======================================================
<%= I18n.t("mail_security.#{@type}", browser: @user_agent.browser, platform: @platform, location: @location) %>
<%= I18n.t("mail_security.#{@type}", browser: @browser, platform: @platform, location: @location) %>
=======================================================

<%= I18n.t("mail_security.#{@type}_text", app_name: CheckConfig.get('app_name'), change_password: t("mail_security.password_text")) %>
<%= "#{CheckConfig.get('checkdesk_client')}/check/user/password-reset" %>

<%= I18n.t("mail_security.time_h").upcase %> (<%= I18n.l @timestamp, format: :email %>)
<%= I18n.t("mail_security.device_h").upcase %> (<%= I18n.t("mail_security.devise_name", browser: @user_agent.browser, platform: @platform) %>)
<%= I18n.t("mail_security.device_h").upcase %> (<%= I18n.t("mail_security.devise_name", browser: @browser, platform: @platform) %>)
<%= I18n.t("mail_security.location_h").upcase %> (<%= @location %>)
<%= I18n.t("mail_security.location_disclaimer") %>
<%= I18n.t("mail_security.ip_h").upcase %> (<%= @ip %>)
Expand Down
6 changes: 0 additions & 6 deletions app/workers/smooch_worker.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,6 @@ class SmoochWorker

def perform(message_json, type, app_id, request_type, associated_id = nil, associated_class = nil)
User.current = BotUser.smooch_user
# This is a temp code for existing jobs and should remove it in next deployment
annotated = begin YAML.load(associated_id) rescue nil end
unless annotated.nil?
associated_id = annotated.id
associated_class = annotated.class.name
end
benchmark.send("smooch_save_#{type}_message") do
Bot::Smooch.save_message(message_json, app_id, User.current, request_type, associated_id, associated_class)
end
Expand Down
29 changes: 19 additions & 10 deletions config/initializers/rack_attack.rb
Original file line number Diff line number Diff line change
@@ -1,27 +1,36 @@
class Rack::Attack
redis = Redis.new(REDIS_CONFIG)

# Extract real IP address from Cloudflare header if present
def self.real_ip(req)
req.get_header('HTTP_CF_CONNECTING_IP') || req.ip
end

# Throttle all graphql requests by IP address

throttle('api/graphql', limit: proc { CheckConfig.get('api_rate_limit', 100, :integer) }, period: 60.seconds) do |req|
req.ip if req.path == '/api/graphql'
real_ip(req) if req.path == '/api/graphql'
end

# Blocklist IP addresses that are permanently blocked
blocklist('block aggressive IPs') do |req|
redis.get("block:#{req.ip}") == "true"
redis.get("block:#{real_ip(req)}") == "true"
end

# Track excessive login attempts for permanent blocking
track('track excessive logins/ip') do |req|
if req.path == '/api/users/sign_in' && req.post?
ip = req.ip
# Increment the counter for the IP and check if it should be blocked
count = redis.incr("track:#{ip}")
redis.expire("track:#{ip}", 3600) # Set the expiration time to 1 hour
ip = real_ip(req)
begin
# Increment the counter for the IP and check if it should be blocked
count = redis.incr("track:#{ip}")
redis.expire("track:#{ip}", 3600) # Set the expiration time to 1 hour

# Add IP to blocklist if count exceeds the threshold
if count.to_i >= CheckConfig.get('login_block_limit', 100, :integer)
redis.set("block:#{ip}", true) # No expiration
# Add IP to blocklist if count exceeds the threshold
if count.to_i >= CheckConfig.get('login_block_limit', 100, :integer)
redis.set("block:#{ip}", true) # No expiration
end
rescue => e
Rails.logger.error("Rack::Attack Error: #{e.message}")
end

ip
Expand Down
52 changes: 43 additions & 9 deletions db/seeds.rb
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,8 @@ def open_file(file)
end
}

BLANK_PARAMS = Array.new(8, { type: 'Blank' })

class Setup

private
Expand Down Expand Up @@ -211,14 +213,22 @@ class PopulatedWorkspaces

private

attr_reader :teams, :users, :invited_teams
attr_reader :teams, :users, :invited_teams, :bot

public

def initialize(setup)
@teams = setup.teams
@users = setup.users
@invited_teams = teams.size > 1
@bot = BotUser.fetch_user
end

def fetch_bot_installation
teams.each_value do |team|
installation = TeamBotInstallation.where(team: team, user: bot).last
bot.install_to!(team) if installation.nil?
end
end

def populate_projects
Expand All @@ -232,11 +242,12 @@ def populate_projects
media_attributes: media_params,
user: users[:main_user_a],
team: teams[:main_team_a],
channel: channel(media_params[:type]),
claim_description_attributes: {
description: claim_title(media_params),
context: Faker::Lorem.sentence,
user: users[:main_user_a],
fact_check_attributes: fact_check_params_for_half_the_claims(index, users[:main_user_a]),
user: media_params[:type] == "Blank" ? bot : users[:main_user_a],
fact_check_attributes: imported_fact_check_params(media_params[:type]) || fact_check_params_for_half_the_claims(index, users[:main_user_a]),
}
}
}
Expand All @@ -256,11 +267,12 @@ def populate_projects
media_attributes: media_params,
user: users[:invited_user_b],
team: teams[:invited_team_b1],
channel: channel(media_params[:type]),
claim_description_attributes: {
description: claim_title(media_params),
context: Faker::Lorem.sentence,
user: users[:invited_user_b],
fact_check_attributes: fact_check_params_for_half_the_claims(index, users[:invited_user_b]),
user: media_params[:type] == "Blank" ? bot : users[:invited_user_b],
fact_check_attributes: imported_fact_check_params(media_params[:type]) || fact_check_params_for_half_the_claims(index, users[:invited_user_b]),
}
}
}
Expand All @@ -274,11 +286,12 @@ def populate_projects
media_attributes: media_params,
user: users[:invited_user_b],
team: teams[:invited_team_b2],
channel: channel(media_params[:type]),
claim_description_attributes: {
description: claim_title(media_params),
context: Faker::Lorem.sentence,
user: users[:invited_user_b],
fact_check_attributes: fact_check_params_for_half_the_claims(index, users[:invited_user_b]),
user: media_params[:type] == "Blank" ? bot : users[:invited_user_b],
fact_check_attributes: imported_fact_check_params(media_params[:type]) || fact_check_params_for_half_the_claims(index, users[:invited_user_b]),
}
}
}
Expand All @@ -292,11 +305,12 @@ def populate_projects
media_attributes: media_params,
user: users[:invited_user_c],
team: teams[:invited_team_c],
channel: channel(media_params[:type]),
claim_description_attributes: {
description: claim_title(media_params),
context: Faker::Lorem.sentence,
user: users[:invited_user_c],
fact_check_attributes: fact_check_params_for_half_the_claims(index, users[:invited_user_c]),
user: media_params[:type] == "Blank" ? bot : users[:invited_user_c],
fact_check_attributes: imported_fact_check_params(media_params[:type]) || fact_check_params_for_half_the_claims(index, users[:invited_user_c]),
}
}
}
Expand Down Expand Up @@ -413,6 +427,7 @@ def medias_params
*UPLOADED_AUDIO_PARAMS,
*UPLOADED_IMAGE_PARAMS,
*UPLOADED_VIDEO_PARAMS,
*BLANK_PARAMS,
*LINK_PARAMS.call
].shuffle!
end
Expand Down Expand Up @@ -670,6 +685,24 @@ def cluster(project_media, feed, team_id)
}
Cluster.create!(cluster_params)
end

def imported_fact_check_params(media_type)
if media_type == 'Blank'
{
summary: Faker::Company.catch_phrase,
title: Faker::Company.name,
user: bot,
language: 'en',
url: get_url_for_some_fact_checks(4)
}
else
false
end
end

def channel(media_type)
media_type == "Blank" ? { main: CheckChannels::ChannelCodes::FETCH } : { main: CheckChannels::ChannelCodes::MANUAL }
end
end

puts "If you want to create a new user: press enter"
Expand All @@ -690,6 +723,7 @@ def cluster(project_media, feed, team_id)
setup = Setup.new(answer.presence) # .presence : returns nil or the string
puts 'Creating projects for all users...'
populated_workspaces = PopulatedWorkspaces.new(setup)
populated_workspaces.fetch_bot_installation
populated_workspaces.populate_projects
puts 'Creating saved searches for all teams...'
populated_workspaces.saved_searches
Expand Down
28 changes: 28 additions & 0 deletions test/lib/check_rack_attack_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -30,4 +30,32 @@ class ThrottlingTest < ActionDispatch::IntegrationTest
assert_response :forbidden
end
end

test "should handle requests via Cloudflare correctly in production" do
original_env = Rails.env
Rails.env = 'production'

stub_configs({ 'api_rate_limit' => 3, 'login_block_limit' => 2 }) do
# Test throttling for /api/graphql via Cloudflare
3.times do
post api_graphql_path, headers: { 'CF-Connecting-IP' => '1.2.3.4' }
assert_response :unauthorized
end

post api_graphql_path, headers: { 'CF-Connecting-IP' => '1.2.3.4' }
assert_response :too_many_requests

# Test blocking for /api/users/sign_in via Cloudflare
user_params = { api_user: { email: '[email protected]', password: random_complex_password } }

2.times do
post api_user_session_path, params: user_params, as: :json, headers: { 'CF-Connecting-IP' => '1.2.3.4' }
end

post api_user_session_path, params: user_params, as: :json, headers: { 'CF-Connecting-IP' => '1.2.3.4' }
assert_response :forbidden
end

Rails.env = original_env
end
end
Loading

0 comments on commit b4a0a7a

Please sign in to comment.