diff --git a/app/controllers/admin/link_blocks_controller.rb b/app/controllers/admin/link_blocks_controller.rb new file mode 100644 index 00000000..50ac5c88 --- /dev/null +++ b/app/controllers/admin/link_blocks_controller.rb @@ -0,0 +1,47 @@ +# frozen_string_literal: true + +module Admin + class LinkBlocksController < BaseController + before_action :set_link_block, only: [:show, :destroy] + + def index + authorize :link_block, :index? + @link_blocks = LinkBlock.page(params[:page]) + end + + def new + authorize :link_block, :create? + @link_block = LinkBlock.new + end + + def create + authorize :link_block, :create? + + @link_block = LinkBlock.new(resource_params) + + if @link_block.save + log_action :create, @link_block + redirect_to admin_link_blocks_path, notice: I18n.t('admin.link_blocks.created_msg') + else + render :new + end + end + + def destroy + authorize @link_block, :destroy? + @link_block.destroy! + log_action :destroy, @link_block + redirect_to admin_link_blocks_path, notice: I18n.t('admin.link_blocks.destroyed_msg') + end + + private + + def set_link_block + @link_block = LinkBlock.find(params[:id]) + end + + def resource_params + params.require(:link_block).permit(:link) + end + end +end diff --git a/app/lib/tag_manager.rb b/app/lib/tag_manager.rb index fb364cb9..c3b20aee 100644 --- a/app/lib/tag_manager.rb +++ b/app/lib/tag_manager.rb @@ -22,6 +22,12 @@ class TagManager uri.normalized_host end + def normalize_link(link) + return if link.nil? + uri = Addressable::URI.parse(link) + return "#{uri.normalized_host}#{uri.normalized_path}" + end + def same_acct?(canonical, needle) return true if canonical.casecmp(needle).zero? username, domain = needle.split('@') diff --git a/app/models/concerns/link_normalizable.rb b/app/models/concerns/link_normalizable.rb new file mode 100644 index 00000000..967d40be --- /dev/null +++ b/app/models/concerns/link_normalizable.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: true + +module LinkNormalizable + extend ActiveSupport::Concern + + included do + before_validation :normalize_link + end + + private + + def normalize_link + self.link = TagManager.instance.normalize_link(link&.strip) + end +end diff --git a/app/models/link_block.rb b/app/models/link_block.rb new file mode 100644 index 00000000..6766e1b7 --- /dev/null +++ b/app/models/link_block.rb @@ -0,0 +1,26 @@ +# frozen_string_literal: true +# == Schema Information +# +# Table name: link_blocks +# +# id :bigint(8) not null, primary key +# link :string default(""), not null +# created_at :datetime not null +# updated_at :datetime not null +# + +class LinkBlock < ApplicationRecord + include LinkNormalizable + + validates :link, presence: true, uniqueness: true + + def self.block?(text) + return false if text.nil? + return false if text.length < 1 + + urls = text.scan(FetchLinkCardService::URL_PATTERN).map { |array| Addressable::URI.parse(array[0]).normalize } + url = urls.first + link_for_fetch = TagManager.instance.normalize_link(url) + where(link: link_for_fetch).exists? + end +end \ No newline at end of file diff --git a/app/policies/link_block_policy.rb b/app/policies/link_block_policy.rb new file mode 100644 index 00000000..8b47dc14 --- /dev/null +++ b/app/policies/link_block_policy.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: true + +class LinkBlockPolicy < ApplicationPolicy + def index? + admin? + end + + def create? + admin? + end + + def destroy? + admin? + end +end diff --git a/app/services/edit_status_service.rb b/app/services/edit_status_service.rb index 16d7015e..561fee88 100644 --- a/app/services/edit_status_service.rb +++ b/app/services/edit_status_service.rb @@ -25,6 +25,7 @@ class EditStatusService < BaseService return idempotency_duplicate if idempotency_given? && idempotency_duplicate? + validate_links! validate_media! preprocess_attributes! revision_text = prepare_revision_text @@ -89,6 +90,10 @@ class EditStatusService < BaseService raise GabSocial::ValidationError, I18n.t('media_attachments.validations.images_and_video') if @media.size > 1 && hasVideoOrGif end + def validate_links! + raise GabSocial::NotPermittedError if LinkBlock.block?(@text) + end + def language_from_option(str) ISO_639.find(str)&.alpha2 end diff --git a/app/services/post_status_service.rb b/app/services/post_status_service.rb index 1af33e7f..601bf2b9 100644 --- a/app/services/post_status_service.rb +++ b/app/services/post_status_service.rb @@ -34,6 +34,7 @@ class PostStatusService < BaseService return idempotency_duplicate if idempotency_given? && idempotency_duplicate? + validate_links! validate_media! validate_group! preprocess_attributes! @@ -98,7 +99,7 @@ class PostStatusService < BaseService end def postprocess_status! - LinkCrawlWorker.perform_async(@status.id) unless @status.spoiler_text? + LinkCrawlWorker.perform_async(@status.id) DistributionWorker.perform_async(@status.id) # Pubsubhubbub::DistributionWorker.perform_async(@status.stream_entry.id) # ActivityPub::DistributionWorker.perform_async(@status.id) @@ -127,6 +128,10 @@ class PostStatusService < BaseService raise GabSocial::ValidationError, I18n.t('media_attachments.validations.images_and_video') if @media.size > 1 && hasVideoOrGif end + def validate_links! + raise GabSocial::NotPermittedError if LinkBlock.block?(@text) + end + def language_from_option(str) ISO_639.find(str)&.alpha2 end diff --git a/app/views/admin/link_blocks/_link_block.html.haml b/app/views/admin/link_blocks/_link_block.html.haml new file mode 100644 index 00000000..845b3be3 --- /dev/null +++ b/app/views/admin/link_blocks/_link_block.html.haml @@ -0,0 +1,5 @@ +%tr + %td + %samp= link_block.link + %td + = table_link_to 'trash', t('admin.link_blocks.delete'), admin_link_block_path(link_block), method: :delete diff --git a/app/views/admin/link_blocks/index.html.haml b/app/views/admin/link_blocks/index.html.haml new file mode 100644 index 00000000..2b834df0 --- /dev/null +++ b/app/views/admin/link_blocks/index.html.haml @@ -0,0 +1,14 @@ +- content_for :page_title do + = t('admin.link_blocks.title') + +.table-wrapper + %table.table + %thead + %tr + %th= t('admin.link_blocks.link') + %th + %tbody + = render @link_blocks + += paginate @link_blocks += link_to t('admin.link_blocks.add_new'), new_admin_link_block_path, class: 'button' diff --git a/app/views/admin/link_blocks/new.html.haml b/app/views/admin/link_blocks/new.html.haml new file mode 100644 index 00000000..cc19d9e0 --- /dev/null +++ b/app/views/admin/link_blocks/new.html.haml @@ -0,0 +1,11 @@ +- content_for :page_title do + = t('.title') + += simple_form_for @link_block, url: admin_link_blocks_path do |f| + = render 'shared/error_messages', object: @link_block + + .fields-group + = f.input :link, wrapper: :with_label, label: t('admin.link_blocks.link') + + .actions + = f.button :button, t('.create'), type: :submit diff --git a/config/locales/en.yml b/config/locales/en.yml index ddab0e43..68692541 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -325,6 +325,16 @@ en: title: Undo domain block for %{domain} undo: Undo undo: Undo domain block + link_blocks: + add_new: Add new + created_msg: Successfully added link to blacklist + delete: Delete + destroyed_msg: Successfully deleted link from blacklist + link: Link + new: + create: Add link + title: New link blacklist entry + title: Link blacklist email_domain_blocks: add_new: Add new created_msg: Successfully added e-mail domain to blacklist diff --git a/config/locales/en_GB.yml b/config/locales/en_GB.yml index 4e3ed84f..60044551 100644 --- a/config/locales/en_GB.yml +++ b/config/locales/en_GB.yml @@ -292,6 +292,16 @@ en_GB: title: Undo domain block for %{domain} undo: Undo undo: Undo domain block + link_blocks: + add_new: Add new + created_msg: Successfully added link to blacklist + delete: Delete + destroyed_msg: Successfully deleted link from blacklist + link: Link + new: + create: Add link + title: New link blacklist entry + title: Link blacklist email_domain_blocks: add_new: Add new created_msg: Successfully added e-mail domain to blacklist diff --git a/config/navigation.rb b/config/navigation.rb index 414c179d..028d8bc7 100644 --- a/config/navigation.rb +++ b/config/navigation.rb @@ -37,6 +37,7 @@ SimpleNavigation::Configuration.run do |navigation| s.item :tags, safe_join([fa_icon('tag fw'), t('admin.tags.title')]), admin_tags_path s.item :instances, safe_join([fa_icon('cloud fw'), t('admin.instances.title')]), admin_instances_url(limited: '1'), highlights_on: %r{/admin/instances|/admin/domain_blocks}, if: -> { current_user.admin? } s.item :email_domain_blocks, safe_join([fa_icon('envelope fw'), t('admin.email_domain_blocks.title')]), admin_email_domain_blocks_url, highlights_on: %r{/admin/email_domain_blocks}, if: -> { current_user.admin? } + s.item :link_blocks, safe_join([fa_icon('link fw'), t('admin.link_blocks.title')]), admin_link_blocks_url, highlights_on: %r{/admin/link_blocks}, if: -> { current_user.admin? } end n.item :admin, safe_join([fa_icon('cogs fw'), t('admin.title')]), admin_dashboard_url, if: proc { current_user.staff? } do |s| diff --git a/config/routes.rb b/config/routes.rb index 8b089dc6..c8747409 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -153,6 +153,7 @@ Rails.application.routes.draw do resources :subscriptions, only: [:index] resources :domain_blocks, only: [:new, :create, :show, :destroy] resources :email_domain_blocks, only: [:index, :new, :create, :destroy] + resources :link_blocks, only: [:index, :new, :create, :destroy] resources :action_logs, only: [:index] resources :warning_presets, except: [:new] resource :settings, only: [:edit, :update] diff --git a/db/migrate/20201206060226_create_link_blocks.rb b/db/migrate/20201206060226_create_link_blocks.rb new file mode 100644 index 00000000..af47466b --- /dev/null +++ b/db/migrate/20201206060226_create_link_blocks.rb @@ -0,0 +1,10 @@ +class CreateLinkBlocks < ActiveRecord::Migration[5.2] + def change + create_table :link_blocks do |t| + t.string :link, null: false, default: '' + t.timestamps null: false + end + + add_index :link_blocks, :link, unique: true + end +end \ No newline at end of file diff --git a/db/schema.rb b/db/schema.rb index 6fa418c8..d2aaba61 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 2020_09_08_210104) do +ActiveRecord::Schema.define(version: 2020_12_06_060226) do # These are extensions that must be enabled in order to support this database enable_extension "pg_stat_statements" @@ -432,6 +432,13 @@ ActiveRecord::Schema.define(version: 2020_09_08_210104) do t.index ["user_id"], name: "index_invites_on_user_id" end + create_table "link_blocks", force: :cascade do |t| + t.string "link", default: "", null: false + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.index ["link"], name: "index_link_blocks_on_link", unique: true + end + create_table "list_accounts", force: :cascade do |t| t.bigint "list_id", null: false t.bigint "account_id", null: false