Skip to content
Snippets Groups Projects
Unverified Commit 216b85b0 authored by Eugen Rochko's avatar Eugen Rochko Committed by GitHub
Browse files

Fix performance on instances list in admin UI (#15282)

- Reduce duplicate queries
- Remove n+1 queries
- Add accounts count to detailed view
- Add separate action log entry for updating existing domain blocks
parent a3b5675a
No related branches found
No related tags found
No related merge requests found
Showing
with 265 additions and 140 deletions
...@@ -82,6 +82,7 @@ gem 'mario-redis-lock', '~> 1.2', require: 'redis_lock' ...@@ -82,6 +82,7 @@ gem 'mario-redis-lock', '~> 1.2', require: 'redis_lock'
gem 'rqrcode', '~> 1.1' gem 'rqrcode', '~> 1.1'
gem 'ruby-progressbar', '~> 1.10' gem 'ruby-progressbar', '~> 1.10'
gem 'sanitize', '~> 5.2' gem 'sanitize', '~> 5.2'
gem 'scenic', '~> 1.5'
gem 'sidekiq', '~> 6.1' gem 'sidekiq', '~> 6.1'
gem 'sidekiq-scheduler', '~> 3.0' gem 'sidekiq-scheduler', '~> 3.0'
gem 'sidekiq-unique-jobs', '~> 6.0' gem 'sidekiq-unique-jobs', '~> 6.0'
......
...@@ -561,6 +561,9 @@ GEM ...@@ -561,6 +561,9 @@ GEM
crass (~> 1.0.2) crass (~> 1.0.2)
nokogiri (>= 1.8.0) nokogiri (>= 1.8.0)
nokogumbo (~> 2.0) nokogumbo (~> 2.0)
scenic (1.5.4)
activerecord (>= 4.0.0)
railties (>= 4.0.0)
securecompare (1.0.0) securecompare (1.0.0)
semantic_range (2.3.0) semantic_range (2.3.0)
sidekiq (6.1.2) sidekiq (6.1.2)
...@@ -782,6 +785,7 @@ DEPENDENCIES ...@@ -782,6 +785,7 @@ DEPENDENCIES
rubocop-rails (~> 2.8) rubocop-rails (~> 2.8)
ruby-progressbar (~> 1.10) ruby-progressbar (~> 1.10)
sanitize (~> 5.2) sanitize (~> 5.2)
scenic (~> 1.5)
sidekiq (~> 6.1) sidekiq (~> 6.1)
sidekiq-bulk (~> 0.2.0) sidekiq-bulk (~> 0.2.0)
sidekiq-scheduler (~> 3.0) sidekiq-scheduler (~> 3.0)
......
...@@ -29,6 +29,7 @@ module Admin ...@@ -29,6 +29,7 @@ module Admin
@domain_block = existing_domain_block @domain_block = existing_domain_block
@domain_block.update(resource_params) @domain_block.update(resource_params)
end end
if @domain_block.save if @domain_block.save
DomainBlockWorker.perform_async(@domain_block.id) DomainBlockWorker.perform_async(@domain_block.id)
log_action :create, @domain_block log_action :create, @domain_block
...@@ -40,7 +41,7 @@ module Admin ...@@ -40,7 +41,7 @@ module Admin
end end
def update def update
authorize :domain_block, :create? authorize :domain_block, :update?
@domain_block.update(update_params) @domain_block.update(update_params)
...@@ -48,7 +49,7 @@ module Admin ...@@ -48,7 +49,7 @@ module Admin
if @domain_block.save if @domain_block.save
DomainBlockWorker.perform_async(@domain_block.id, severity_changed) DomainBlockWorker.perform_async(@domain_block.id, severity_changed)
log_action :create, @domain_block log_action :update, @domain_block
redirect_to admin_instances_path(limited: '1'), notice: I18n.t('admin.domain_blocks.created_msg') redirect_to admin_instances_path(limited: '1'), notice: I18n.t('admin.domain_blocks.created_msg')
else else
render :edit render :edit
......
...@@ -2,65 +2,31 @@ ...@@ -2,65 +2,31 @@
module Admin module Admin
class InstancesController < BaseController class InstancesController < BaseController
before_action :set_domain_block, only: :show before_action :set_instances, only: :index
before_action :set_domain_allow, only: :show
before_action :set_instance, only: :show before_action :set_instance, only: :show
def index def index
authorize :instance, :index? authorize :instance, :index?
@instances = ordered_instances
end end
def show def show
authorize :instance, :show? authorize :instance, :show?
@following_count = Follow.where(account: Account.where(domain: params[:id])).count
@followers_count = Follow.where(target_account: Account.where(domain: params[:id])).count
@reports_count = Report.where(target_account: Account.where(domain: params[:id])).count
@blocks_count = Block.where(target_account: Account.where(domain: params[:id])).count
@available = DeliveryFailureTracker.available?(params[:id])
@media_storage = MediaAttachment.where(account: Account.where(domain: params[:id])).sum(:file_file_size)
@private_comment = @domain_block&.private_comment
@public_comment = @domain_block&.public_comment
end end
private private
def set_domain_block
@domain_block = DomainBlock.rule_for(params[:id])
end
def set_domain_allow
@domain_allow = DomainAllow.rule_for(params[:id])
end
def set_instance def set_instance
resource = Account.by_domain_accounts.find_by(domain: params[:id]) @instance = Instance.find(params[:id])
resource ||= @domain_block end
resource ||= @domain_allow
if resource def set_instances
@instance = Instance.new(resource) @instances = filtered_instances.page(params[:page])
else
not_found
end
end end
def filtered_instances def filtered_instances
InstanceFilter.new(whitelist_mode? ? { allowed: true } : filter_params).results InstanceFilter.new(whitelist_mode? ? { allowed: true } : filter_params).results
end end
def paginated_instances
filtered_instances.page(params[:page])
end
helper_method :paginated_instances
def ordered_instances
paginated_instances.map { |resource| Instance.new(resource) }
end
def filter_params def filter_params
params.slice(*InstanceFilter::KEYS).permit(*InstanceFilter::KEYS) params.slice(*InstanceFilter::KEYS).permit(*InstanceFilter::KEYS)
end end
......
...@@ -8,7 +8,7 @@ class Api::V1::Instances::PeersController < Api::BaseController ...@@ -8,7 +8,7 @@ class Api::V1::Instances::PeersController < Api::BaseController
def index def index
expires_in 1.day, public: true expires_in 1.day, public: true
render_with_cache(expires_in: 1.day) { Account.remote.domains } render_with_cache(expires_in: 1.day) { Instance.where.not(domain: DomainBlock.select(:domain)).pluck(:domain) }
end end
private private
......
...@@ -67,6 +67,7 @@ class Account < ApplicationRecord ...@@ -67,6 +67,7 @@ class Account < ApplicationRecord
include Paginable include Paginable
include AccountCounters include AccountCounters
include DomainNormalizable include DomainNormalizable
include DomainMaterializable
include AccountMerging include AccountMerging
TRUST_LEVELS = { TRUST_LEVELS = {
...@@ -103,7 +104,6 @@ class Account < ApplicationRecord ...@@ -103,7 +104,6 @@ class Account < ApplicationRecord
scope :bots, -> { where(actor_type: %w(Application Service)) } scope :bots, -> { where(actor_type: %w(Application Service)) }
scope :groups, -> { where(actor_type: 'Group') } scope :groups, -> { where(actor_type: 'Group') }
scope :alphabetic, -> { order(domain: :asc, username: :asc) } scope :alphabetic, -> { order(domain: :asc, username: :asc) }
scope :by_domain_accounts, -> { group(:domain).select(:domain, 'COUNT(*) AS accounts_count').order('accounts_count desc') }
scope :matches_username, ->(value) { where(arel_table[:username].matches("#{value}%")) } scope :matches_username, ->(value) { where(arel_table[:username].matches("#{value}%")) }
scope :matches_display_name, ->(value) { where(arel_table[:display_name].matches("#{value}%")) } scope :matches_display_name, ->(value) { where(arel_table[:display_name].matches("#{value}%")) }
scope :matches_domain, ->(value) { where(arel_table[:domain].matches("%#{value}%")) } scope :matches_domain, ->(value) { where(arel_table[:domain].matches("%#{value}%")) }
...@@ -438,10 +438,6 @@ class Account < ApplicationRecord ...@@ -438,10 +438,6 @@ class Account < ApplicationRecord
super - %w(statuses_count following_count followers_count) super - %w(statuses_count following_count followers_count)
end end
def domains
reorder(nil).pluck(Arel.sql('distinct accounts.domain'))
end
def inboxes def inboxes
urls = reorder(nil).where(protocol: :activitypub).group(:preferred_inbox_url).pluck(Arel.sql("coalesce(nullif(accounts.shared_inbox_url, ''), accounts.inbox_url) AS preferred_inbox_url")) urls = reorder(nil).where(protocol: :activitypub).group(:preferred_inbox_url).pluck(Arel.sql("coalesce(nullif(accounts.shared_inbox_url, ''), accounts.inbox_url) AS preferred_inbox_url"))
DeliveryFailureTracker.without_unavailable(urls) DeliveryFailureTracker.without_unavailable(urls)
......
# frozen_string_literal: true
module DomainMaterializable
extend ActiveSupport::Concern
included do
after_create_commit :refresh_instances_view
end
def refresh_instances_view
Instance.refresh unless domain.nil? || Instance.where(domain: domain).exists?
end
end
...@@ -12,6 +12,7 @@ ...@@ -12,6 +12,7 @@
class DomainAllow < ApplicationRecord class DomainAllow < ApplicationRecord
include DomainNormalizable include DomainNormalizable
include DomainMaterializable
validates :domain, presence: true, uniqueness: true, domain: true validates :domain, presence: true, uniqueness: true, domain: true
......
...@@ -16,6 +16,7 @@ ...@@ -16,6 +16,7 @@
class DomainBlock < ApplicationRecord class DomainBlock < ApplicationRecord
include DomainNormalizable include DomainNormalizable
include DomainMaterializable
enum severity: [:silence, :suspend, :noop] enum severity: [:silence, :suspend, :noop]
......
# frozen_string_literal: true # frozen_string_literal: true
# == Schema Information
#
# Table name: instances
#
# domain :string primary key
# accounts_count :bigint(8)
#
class Instance class Instance < ApplicationRecord
include ActiveModel::Model self.primary_key = :domain
attr_accessor :domain, :accounts_count, :domain_block has_many :accounts, foreign_key: :domain, primary_key: :domain
def initialize(resource) belongs_to :domain_block, foreign_key: :domain, primary_key: :domain
@domain = resource.domain belongs_to :domain_allow, foreign_key: :domain, primary_key: :domain
@accounts_count = resource.respond_to?(:accounts_count) ? resource.accounts_count : nil
@domain_block = resource.is_a?(DomainBlock) ? resource : DomainBlock.rule_for(domain) scope :matches_domain, ->(value) { where(arel_table[:domain].matches("%#{value}%")) }
@domain_allow = resource.is_a?(DomainAllow) ? resource : DomainAllow.rule_for(domain)
def self.refresh
Scenic.database.refresh_materialized_view(table_name, concurrently: true, cascade: false)
end end
def countable? def readonly?
@accounts_count.present? true
end end
def to_param def delivery_failure_tracker
domain @delivery_failure_tracker ||= DeliveryFailureTracker.new(domain)
end
def following_count
@following_count ||= Follow.where(account: accounts).count
end
def followers_count
@followers_count ||= Follow.where(target_account: accounts).count
end
def reports_count
@reports_count ||= Report.where(target_account: accounts).count
end end
def cache_key def blocks_count
@blocks_count ||= Block.where(target_account: accounts).count
end
def public_comment
domain_block&.public_comment
end
def private_comment
domain_block&.private_comment
end
def media_storage
@media_storage ||= MediaAttachment.where(account: accounts).sum(:file_file_size)
end
def to_param
domain domain
end end
end end
...@@ -13,18 +13,27 @@ class InstanceFilter ...@@ -13,18 +13,27 @@ class InstanceFilter
end end
def results def results
if params[:limited].present? scope = Instance.includes(:domain_block, :domain_allow).order(accounts_count: :desc)
scope = DomainBlock
scope = scope.matches_domain(params[:by_domain]) if params[:by_domain].present? params.each do |key, value|
scope.order(id: :desc) scope.merge!(scope_for(key, value.to_s.strip)) if value.present?
elsif params[:allowed].present? end
scope = DomainAllow
scope = scope.matches_domain(params[:by_domain]) if params[:by_domain].present? scope
scope.order(id: :desc) end
private
def scope_for(key, value)
case key.to_s
when 'limited'
Instance.joins(:domain_block).reorder(Arel.sql('domain_blocks.id desc'))
when 'allowed'
Instance.joins(:domain_allow).reorder(Arel.sql('domain_allows.id desc'))
when 'by_domain'
Instance.matches_domain(value)
else else
scope = Account.remote raise "Unknown filter: #{key}"
scope = scope.matches_domain(params[:by_domain]) if params[:by_domain].present?
scope.by_domain_accounts
end end
end end
end end
...@@ -12,6 +12,8 @@ ...@@ -12,6 +12,8 @@
class UnavailableDomain < ApplicationRecord class UnavailableDomain < ApplicationRecord
include DomainNormalizable include DomainNormalizable
validates :domain, presence: true, uniqueness: true
after_commit :reset_cache! after_commit :reset_cache!
private private
......
...@@ -13,6 +13,10 @@ class DomainBlockPolicy < ApplicationPolicy ...@@ -13,6 +13,10 @@ class DomainBlockPolicy < ApplicationPolicy
admin? admin?
end end
def update?
admin?
end
def destroy? def destroy?
admin? admin?
end end
......
...@@ -29,7 +29,7 @@ class InstancePresenter ...@@ -29,7 +29,7 @@ class InstancePresenter
end end
def domain_count def domain_count
Rails.cache.fetch('distinct_domain_count') { Account.distinct.count(:domain) } Rails.cache.fetch('distinct_domain_count') { Instance.count }
end end
def sample_accounts def sample_accounts
......
.directory__tag
= link_to admin_instance_path(instance) do
%h4
= instance.domain
%small
- if instance.domain_block
- first_item = true
- if !instance.domain_block.noop?
= t("admin.domain_blocks.severity.#{instance.domain_block.severity}")
- first_item = false
- unless instance.domain_block.suspend?
- if instance.domain_block.reject_media?
- unless first_item
&bull;
= t('admin.domain_blocks.rejecting_media')
- first_item = false
- if instance.domain_block.reject_reports?
- unless first_item
&bull;
= t('admin.domain_blocks.rejecting_reports')
- elsif whitelist_mode?
= t('admin.accounts.whitelisted')
- else
= t('admin.accounts.no_limits_imposed')
.trends__item__current{ title: t('admin.instances.known_accounts', count: instance.accounts_count) }= number_to_human instance.accounts_count, strip_insignificant_zeros: true
...@@ -32,32 +32,10 @@ ...@@ -32,32 +32,10 @@
%hr.spacer/ %hr.spacer/
- @instances.each do |instance| - if @instances.empty?
.directory__tag %div.muted-hint.center-text
= link_to admin_instance_path(instance) do = t 'admin.instances.empty'
%h4 - else
= instance.domain = render @instances
%small
- if instance.domain_block = paginate @instances
- first_item = true
- if !instance.domain_block.noop?
= t("admin.domain_blocks.severity.#{instance.domain_block.severity}")
- first_item = false
- unless instance.domain_block.suspend?
- if instance.domain_block.reject_media?
- unless first_item
&bull;
= t('admin.domain_blocks.rejecting_media')
- first_item = false
- if instance.domain_block.reject_reports?
- unless first_item
&bull;
= t('admin.domain_blocks.rejecting_reports')
- elsif whitelist_mode?
= t('admin.accounts.whitelisted')
- else
= t('admin.accounts.no_limits_imposed')
- if instance.countable?
.trends__item__current{ title: t('admin.instances.known_accounts', count: instance.accounts_count) }= number_to_human instance.accounts_count, strip_insignificant_zeros: true
= paginate paginated_instances
...@@ -2,58 +2,60 @@ ...@@ -2,58 +2,60 @@
= @instance.domain = @instance.domain
.dashboard__counters .dashboard__counters
%div
= link_to admin_accounts_path(remote: '1', by_domain: @instance.domain) do
.dashboard__counters__num= number_with_delimiter @instance.accounts_count
.dashboard__counters__label= t 'admin.accounts.title'
%div
= link_to admin_reports_path(by_target_domain: @instance.domain) do
.dashboard__counters__num= number_with_delimiter @instance.reports_count
.dashboard__counters__label= t 'admin.instances.total_reported'
%div %div
%div %div
.dashboard__counters__num= number_with_delimiter @following_count .dashboard__counters__num= number_to_human_size @instance.media_storage
.dashboard__counters__label= t 'admin.instances.total_followed_by_them' .dashboard__counters__label= t 'admin.instances.total_storage'
%div %div
%div %div
.dashboard__counters__num= number_with_delimiter @followers_count .dashboard__counters__num= number_with_delimiter @instance.following_count
.dashboard__counters__label= t 'admin.instances.total_followed_by_us' .dashboard__counters__label= t 'admin.instances.total_followed_by_them'
%div %div
%div %div
.dashboard__counters__num= number_to_human_size @media_storage .dashboard__counters__num= number_with_delimiter @instance.followers_count
.dashboard__counters__label= t 'admin.instances.total_storage' .dashboard__counters__label= t 'admin.instances.total_followed_by_us'
%div %div
%div %div
.dashboard__counters__num= number_with_delimiter @blocks_count .dashboard__counters__num= number_with_delimiter @instance.blocks_count
.dashboard__counters__label= t 'admin.instances.total_blocked_by_us' .dashboard__counters__label= t 'admin.instances.total_blocked_by_us'
%div
= link_to admin_reports_path(by_target_domain: @instance.domain) do
.dashboard__counters__num= number_with_delimiter @reports_count
.dashboard__counters__label= t 'admin.instances.total_reported'
%div %div
%div %div
.dashboard__counters__num .dashboard__counters__num
- if @available - if @instance.delivery_failure_tracker.available?
= fa_icon 'check' = fa_icon 'check'
- else - else
= fa_icon 'times' = fa_icon 'times'
.dashboard__counters__label= t 'admin.instances.delivery_available' .dashboard__counters__label= t 'admin.instances.delivery_available'
- if @private_comment.present? - if @instance.private_comment.present?
.speech-bubble .speech-bubble
.speech-bubble__bubble .speech-bubble__bubble
= simple_format(h(@private_comment)) = simple_format(h(@instance.private_comment))
.speech-bubble__owner= t 'admin.instances.private_comment' .speech-bubble__owner= t 'admin.instances.private_comment'
- if @public_comment.present? - if @instance.public_comment.present?
.speech-bubble .speech-bubble
.speech-bubble__bubble .speech-bubble__bubble
= simple_format(h(@public_comment)) = simple_format(h(@instance.public_comment))
.speech-bubble__owner= t 'admin.instances.public_comment' .speech-bubble__owner= t 'admin.instances.public_comment'
%hr.spacer/ %hr.spacer/
%div.action-buttons %div.action-buttons
%div %div
= link_to t('admin.accounts.title'), admin_accounts_path(remote: '1', by_domain: @instance.domain), class: 'button' - if @instance.domain_allow
= link_to t('admin.domain_allows.undo'), admin_domain_allow_path(@instance.domain_allow), class: 'button button--destructive', data: { confirm: t('admin.accounts.are_you_sure'), method: :delete }
%div - elsif @instance.domain_block
- if @domain_allow = link_to t('admin.domain_blocks.edit'), edit_admin_domain_block_path(@instance.domain_block), class: 'button'
= link_to t('admin.domain_allows.undo'), admin_domain_allow_path(@domain_allow), class: 'button button--destructive', data: { confirm: t('admin.accounts.are_you_sure'), method: :delete } = link_to t('admin.domain_blocks.undo'), admin_domain_block_path(@instance.domain_block), class: 'button'
- elsif @domain_block
= link_to t('admin.domain_blocks.edit'), edit_admin_domain_block_path(@domain_block), class: 'button'
= link_to t('admin.domain_blocks.undo'), admin_domain_block_path(@domain_block), class: 'button'
- else - else
= link_to t('admin.domain_blocks.add_new'), new_admin_domain_block_path(_domain: @instance.domain), class: 'button' = link_to t('admin.domain_blocks.add_new'), new_admin_domain_block_path(_domain: @instance.domain), class: 'button'
# frozen_string_literal: true
class Scheduler::InstanceRefreshScheduler
include Sidekiq::Worker
sidekiq_options lock: :until_executed, retry: 0
def perform
Instance.refresh
end
end
...@@ -102,6 +102,37 @@ ...@@ -102,6 +102,37 @@
"confidence": "Weak", "confidence": "Weak",
"note": "" "note": ""
}, },
{
"warning_type": "Dynamic Render Path",
"warning_code": 15,
"fingerprint": "4704e8093e3e0561bf705f892e8fc6780419f8255f4440b1c0afd09339bd6446",
"check_name": "Render",
"message": "Render path contains parameter value",
"file": "app/views/admin/instances/index.html.haml",
"line": 39,
"link": "https://brakemanscanner.org/docs/warning_types/dynamic_render_path/",
"code": "render(action => filtered_instances.page(params[:page]), {})",
"render_path": [
{
"type": "controller",
"class": "Admin::InstancesController",
"method": "index",
"line": 10,
"file": "app/controllers/admin/instances_controller.rb",
"rendered": {
"name": "admin/instances/index",
"file": "app/views/admin/instances/index.html.haml"
}
}
],
"location": {
"type": "template",
"template": "admin/instances/index"
},
"user_input": "params[:page]",
"confidence": "Weak",
"note": ""
},
{ {
"warning_type": "Redirect", "warning_type": "Redirect",
"warning_code": 18, "warning_code": 18,
...@@ -122,6 +153,26 @@ ...@@ -122,6 +153,26 @@
"confidence": "High", "confidence": "High",
"note": "" "note": ""
}, },
{
"warning_type": "SQL Injection",
"warning_code": 0,
"fingerprint": "6e4051854bb62e2ddbc671f82d6c2328892e1134b8b28105ecba9b0122540714",
"check_name": "SQL",
"message": "Possible SQL injection",
"file": "app/models/account.rb",
"line": 491,
"link": "https://brakemanscanner.org/docs/warning_types/sql_injection/",
"code": "find_by_sql([\" WITH first_degree AS (\\n SELECT target_account_id\\n FROM follows\\n WHERE account_id = ?\\n UNION ALL\\n SELECT ?\\n )\\n SELECT\\n accounts.*,\\n (count(f.id) + 1) * ts_rank_cd(#{textsearch}, #{query}, 32) AS rank\\n FROM accounts\\n LEFT OUTER JOIN follows AS f ON (accounts.id = f.account_id AND f.target_account_id = ?)\\n WHERE accounts.id IN (SELECT * FROM first_degree)\\n AND #{query} @@ #{textsearch}\\n AND accounts.suspended_at IS NULL\\n AND accounts.moved_to_account_id IS NULL\\n GROUP BY accounts.id\\n ORDER BY rank DESC\\n LIMIT ? OFFSET ?\\n\".squish, account.id, account.id, account.id, limit, offset])",
"render_path": null,
"location": {
"type": "method",
"class": "Account",
"method": "advanced_search_for"
},
"user_input": "textsearch",
"confidence": "Medium",
"note": ""
},
{ {
"warning_type": "SQL Injection", "warning_type": "SQL Injection",
"warning_code": 0, "warning_code": 0,
...@@ -163,23 +214,23 @@ ...@@ -163,23 +214,23 @@
"note": "" "note": ""
}, },
{ {
"warning_type": "Mass Assignment", "warning_type": "SQL Injection",
"warning_code": 105, "warning_code": 0,
"fingerprint": "8f63dec68951d9bcf7eddb15af9392b2e1333003089c41fb76688dfd3579f394", "fingerprint": "9251d682c4e2840e1b2fea91e7d758efe2097ecb7f6255c065e3750d25eb178c",
"check_name": "PermitAttributes", "check_name": "SQL",
"message": "Potentially dangerous key allowed for mass assignment", "message": "Possible SQL injection",
"file": "app/controllers/api/v1/crypto/deliveries_controller.rb", "file": "app/models/account.rb",
"line": 23, "line": 460,
"link": "https://brakemanscanner.org/docs/warning_types/mass_assignment/", "link": "https://brakemanscanner.org/docs/warning_types/sql_injection/",
"code": "params.require(:device).permit(:account_id, :device_id, :type, :body, :hmac)", "code": "find_by_sql([\" SELECT\\n accounts.*,\\n ts_rank_cd(#{textsearch}, #{query}, 32) AS rank\\n FROM accounts\\n WHERE #{query} @@ #{textsearch}\\n AND accounts.suspended_at IS NULL\\n AND accounts.moved_to_account_id IS NULL\\n ORDER BY rank DESC\\n LIMIT ? OFFSET ?\\n\".squish, limit, offset])",
"render_path": null, "render_path": null,
"location": { "location": {
"type": "method", "type": "method",
"class": "Api::V1::Crypto::DeliveriesController", "class": "Account",
"method": "resource_params" "method": "search_for"
}, },
"user_input": ":account_id", "user_input": "textsearch",
"confidence": "High", "confidence": "Medium",
"note": "" "note": ""
}, },
{ {
...@@ -273,6 +324,26 @@ ...@@ -273,6 +324,26 @@
"confidence": "High", "confidence": "High",
"note": "" "note": ""
}, },
{
"warning_type": "SQL Injection",
"warning_code": 0,
"fingerprint": "e21d8fee7a5805761679877ca35ed1029c64c45ef3b4012a30262623e1ba8bb9",
"check_name": "SQL",
"message": "Possible SQL injection",
"file": "app/models/account.rb",
"line": 507,
"link": "https://brakemanscanner.org/docs/warning_types/sql_injection/",
"code": "find_by_sql([\" SELECT\\n accounts.*,\\n (count(f.id) + 1) * ts_rank_cd(#{textsearch}, #{query}, 32) AS rank\\n FROM accounts\\n LEFT OUTER JOIN follows AS f ON (accounts.id = f.account_id AND f.target_account_id = ?) OR (accounts.id = f.target_account_id AND f.account_id = ?)\\n WHERE #{query} @@ #{textsearch}\\n AND accounts.suspended_at IS NULL\\n AND accounts.moved_to_account_id IS NULL\\n GROUP BY accounts.id\\n ORDER BY rank DESC\\n LIMIT ? OFFSET ?\\n\".squish, account.id, account.id, limit, offset])",
"render_path": null,
"location": {
"type": "method",
"class": "Account",
"method": "advanced_search_for"
},
"user_input": "textsearch",
"confidence": "Medium",
"note": ""
},
{ {
"warning_type": "Mass Assignment", "warning_type": "Mass Assignment",
"warning_code": 105, "warning_code": 105,
...@@ -294,6 +365,6 @@ ...@@ -294,6 +365,6 @@
"note": "" "note": ""
} }
], ],
"updated": "2020-06-01 18:18:02 +0200", "updated": "2020-12-07 01:17:13 +0100",
"brakeman_version": "4.8.0" "brakeman_version": "4.10.0"
} }
...@@ -255,6 +255,7 @@ en: ...@@ -255,6 +255,7 @@ en:
unsuspend_account: Unsuspend Account unsuspend_account: Unsuspend Account
update_announcement: Update Announcement update_announcement: Update Announcement
update_custom_emoji: Update Custom Emoji update_custom_emoji: Update Custom Emoji
update_domain_block: Update Domain Block
update_status: Update Status update_status: Update Status
actions: actions:
assigned_to_self_report: "%{name} assigned report %{target} to themselves" assigned_to_self_report: "%{name} assigned report %{target} to themselves"
...@@ -295,6 +296,7 @@ en: ...@@ -295,6 +296,7 @@ en:
unsuspend_account: "%{name} unsuspended %{target}'s account" unsuspend_account: "%{name} unsuspended %{target}'s account"
update_announcement: "%{name} updated announcement %{target}" update_announcement: "%{name} updated announcement %{target}"
update_custom_emoji: "%{name} updated emoji %{target}" update_custom_emoji: "%{name} updated emoji %{target}"
update_domain_block: "%{name} updated domain block for %{target}"
update_status: "%{name} updated status by %{target}" update_status: "%{name} updated status by %{target}"
deleted_status: "(deleted status)" deleted_status: "(deleted status)"
empty: No logs found. empty: No logs found.
...@@ -437,6 +439,7 @@ en: ...@@ -437,6 +439,7 @@ en:
instances: instances:
by_domain: Domain by_domain: Domain
delivery_available: Delivery is available delivery_available: Delivery is available
empty: No domains found.
known_accounts: known_accounts:
one: "%{count} known account" one: "%{count} known account"
other: "%{count} known accounts" other: "%{count} known accounts"
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment