From b3af3f9f8cd5ed9c7ee06452e981b1b7734e1d89 Mon Sep 17 00:00:00 2001 From: utam0k Date: Wed, 4 Oct 2017 22:16:10 +0900 Subject: Implement EmailBlackList (#5109) * Implement BlacklistedEmailDomain * Use Faker::Internet.domain_name * Remove note column * Add frozen_string_literal comment * Delete unnecessary codes * Sort alphabetically * Change of wording * Rename BlacklistedEmailDomain to EmailDomainBlock --- .../admin/email_domain_blocks_controller.rb | 40 ++++++++++++++++++++++ 1 file changed, 40 insertions(+) create mode 100644 app/controllers/admin/email_domain_blocks_controller.rb (limited to 'app/controllers/admin') diff --git a/app/controllers/admin/email_domain_blocks_controller.rb b/app/controllers/admin/email_domain_blocks_controller.rb new file mode 100644 index 000000000..09275d5dc --- /dev/null +++ b/app/controllers/admin/email_domain_blocks_controller.rb @@ -0,0 +1,40 @@ +# frozen_string_literal: true + +module Admin + class EmailDomainBlocksController < BaseController + before_action :set_email_domain_block, only: [:show, :destroy] + + def index + @email_domain_blocks = EmailDomainBlock.page(params[:page]) + end + + def new + @email_domain_block = EmailDomainBlock.new + end + + def create + @email_domain_block = EmailDomainBlock.new(resource_params) + + if @email_domain_block.save + redirect_to admin_email_domain_blocks_path, notice: I18n.t('admin.email_domain_blocks.created_msg') + else + render :new + end + end + + def destroy + @email_domain_block.destroy + redirect_to admin_email_domain_blocks_path, notice: I18n.t('admin.email_domain_blocks.destroyed_msg') + end + + private + + def set_email_domain_block + @email_domain_block = EmailDomainBlock.find(params[:id]) + end + + def resource_params + params.require(:email_domain_block).permit(:domain) + end + end +end -- cgit From 49cc0eb3e7d1521079e33a60216df46679082547 Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Thu, 5 Oct 2017 23:42:05 +0200 Subject: Improve admin UI for custom emojis, add copy/disable/enable (#5231) --- app/controllers/admin/custom_emojis_controller.rb | 42 ++++- app/controllers/api/v1/custom_emojis_controller.rb | 2 +- app/models/account_filter.rb | 2 + app/models/custom_emoji.rb | 11 +- app/models/custom_emoji_filter.rb | 34 ++++ .../admin/custom_emojis/_custom_emoji.html.haml | 13 ++ app/views/admin/custom_emojis/index.html.haml | 20 +++ config/brakeman.ignore | 182 ++++++++++++++++++++- config/locales/de.yml | 6 +- config/locales/en.yml | 7 + config/routes.rb | 8 +- ...20171005171936_add_disabled_to_custom_emojis.rb | 15 ++ db/schema.rb | 3 +- 13 files changed, 330 insertions(+), 15 deletions(-) create mode 100644 app/models/custom_emoji_filter.rb create mode 100644 db/migrate/20171005171936_add_disabled_to_custom_emojis.rb (limited to 'app/controllers/admin') diff --git a/app/controllers/admin/custom_emojis_controller.rb b/app/controllers/admin/custom_emojis_controller.rb index d70514d9a..dba9f1012 100644 --- a/app/controllers/admin/custom_emojis_controller.rb +++ b/app/controllers/admin/custom_emojis_controller.rb @@ -2,8 +2,10 @@ module Admin class CustomEmojisController < BaseController + before_action :set_custom_emoji, except: [:index, :new, :create] + def index - @custom_emojis = CustomEmoji.local + @custom_emojis = filtered_custom_emojis.page(params[:page]) end def new @@ -21,14 +23,50 @@ module Admin end def destroy - CustomEmoji.find(params[:id]).destroy + @custom_emoji.destroy redirect_to admin_custom_emojis_path, notice: I18n.t('admin.custom_emojis.destroyed_msg') end + def copy + emoji = @custom_emoji.dup + emoji.domain = nil + + if emoji.save + redirect_to admin_custom_emojis_path, notice: I18n.t('admin.custom_emojis.copied_msg') + else + redirect_to admin_custom_emojis_path, alert: I18n.t('admin.custom_emojis.copy_failed_msg') + end + end + + def enable + @custom_emoji.update!(disabled: false) + redirect_to admin_custom_emojis_path, notice: I18n.t('admin.custom_emojis.enabled_msg') + end + + def disable + @custom_emoji.update!(disabled: true) + redirect_to admin_custom_emojis_path, notice: I18n.t('admin.custom_emojis.disabled_msg') + end + private + def set_custom_emoji + @custom_emoji = CustomEmoji.find(params[:id]) + end + def resource_params params.require(:custom_emoji).permit(:shortcode, :image) end + + def filtered_custom_emojis + CustomEmojiFilter.new(filter_params).results + end + + def filter_params + params.permit( + :local, + :remote + ) + end end end diff --git a/app/controllers/api/v1/custom_emojis_controller.rb b/app/controllers/api/v1/custom_emojis_controller.rb index 4dd77fb55..f8cd64455 100644 --- a/app/controllers/api/v1/custom_emojis_controller.rb +++ b/app/controllers/api/v1/custom_emojis_controller.rb @@ -4,6 +4,6 @@ class Api::V1::CustomEmojisController < Api::BaseController respond_to :json def index - render json: CustomEmoji.local, each_serializer: REST::CustomEmojiSerializer + render json: CustomEmoji.local.where(disabled: false), each_serializer: REST::CustomEmojiSerializer end end diff --git a/app/models/account_filter.rb b/app/models/account_filter.rb index 1a8cc5192..189872368 100644 --- a/app/models/account_filter.rb +++ b/app/models/account_filter.rb @@ -9,9 +9,11 @@ class AccountFilter def results scope = Account.alphabetic + params.each do |key, value| scope.merge!(scope_for(key, value)) if value.present? end + scope end diff --git a/app/models/custom_emoji.rb b/app/models/custom_emoji.rb index 9e9be5e12..258b50c82 100644 --- a/app/models/custom_emoji.rb +++ b/app/models/custom_emoji.rb @@ -12,6 +12,7 @@ # image_updated_at :datetime # created_at :datetime not null # updated_at :datetime not null +# disabled :boolean default(FALSE), not null # class CustomEmoji < ApplicationRecord @@ -26,10 +27,16 @@ class CustomEmoji < ApplicationRecord validates_attachment :image, content_type: { content_type: 'image/png' }, presence: true, size: { in: 0..50.kilobytes } validates :shortcode, uniqueness: { scope: :domain }, format: { with: /\A#{SHORTCODE_RE_FRAGMENT}\z/ }, length: { minimum: 2 } - scope :local, -> { where(domain: nil) } + scope :local, -> { where(domain: nil) } + scope :remote, -> { where.not(domain: nil) } + scope :alphabetic, -> { order(domain: :asc, shortcode: :asc) } include Remotable + def local? + domain.nil? + end + class << self def from_text(text, domain) return [] if text.blank? @@ -38,7 +45,7 @@ class CustomEmoji < ApplicationRecord return [] if shortcodes.empty? - where(shortcode: shortcodes, domain: domain) + where(shortcode: shortcodes, domain: domain, disabled: false) end end end diff --git a/app/models/custom_emoji_filter.rb b/app/models/custom_emoji_filter.rb new file mode 100644 index 000000000..2d1394a59 --- /dev/null +++ b/app/models/custom_emoji_filter.rb @@ -0,0 +1,34 @@ +# frozen_string_literal: true + +class CustomEmojiFilter + attr_reader :params + + def initialize(params) + @params = params + end + + def results + scope = CustomEmoji.alphabetic + + params.each do |key, value| + scope.merge!(scope_for(key, value)) if value.present? + end + + scope + end + + private + + def scope_for(key, value) + case key.to_s + when 'local' + CustomEmoji.local + when 'remote' + CustomEmoji.remote + when 'by_domain' + CustomEmoji.where(domain: value) + else + raise "Unknown filter: #{key}" + end + end +end diff --git a/app/views/admin/custom_emojis/_custom_emoji.html.haml b/app/views/admin/custom_emojis/_custom_emoji.html.haml index ff1aa9925..53263c43f 100644 --- a/app/views/admin/custom_emojis/_custom_emoji.html.haml +++ b/app/views/admin/custom_emojis/_custom_emoji.html.haml @@ -3,5 +3,18 @@ = image_tag custom_emoji.image.url, class: 'emojione', alt: ":#{custom_emoji.shortcode}:" %td %samp= ":#{custom_emoji.shortcode}:" + %td + - if custom_emoji.local? + = t('admin.accounts.location.local') + - else + = custom_emoji.domain + %td + - unless custom_emoji.local? + = table_link_to 'copy', t('admin.custom_emojis.copy'), copy_admin_custom_emoji_path(custom_emoji), method: :post + %td + - if custom_emoji.disabled? + = table_link_to 'power-off', t('admin.custom_emojis.enable'), enable_admin_custom_emoji_path(custom_emoji), method: :post, data: { confirm: t('admin.accounts.are_you_sure') } + - else + = table_link_to 'power-off', t('admin.custom_emojis.disable'), disable_admin_custom_emoji_path(custom_emoji), method: :post, data: { confirm: t('admin.accounts.are_you_sure') } %td = table_link_to 'times', t('admin.custom_emojis.delete'), admin_custom_emoji_path(custom_emoji), method: :delete, data: { confirm: t('admin.accounts.are_you_sure') } diff --git a/app/views/admin/custom_emojis/index.html.haml b/app/views/admin/custom_emojis/index.html.haml index d5f32e84b..20ffb8529 100644 --- a/app/views/admin/custom_emojis/index.html.haml +++ b/app/views/admin/custom_emojis/index.html.haml @@ -1,14 +1,34 @@ - content_for :page_title do = t('admin.custom_emojis.title') +.filters + .filter-subset + %strong= t('admin.accounts.location.title') + %ul + %li= filter_link_to t('admin.accounts.location.all'), local: nil, remote: nil + %li + - if selected? local: '1', remote: nil + = filter_link_to t('admin.accounts.location.local'), {local: nil, remote: nil}, {local: '1', remote: nil} + - else + = filter_link_to t('admin.accounts.location.local'), local: '1', remote: nil + %li + - if selected? remote: '1', local: nil + = filter_link_to t('admin.accounts.location.remote'), {remote: nil, local: nil}, {remote: '1', local: nil} + - else + = filter_link_to t('admin.accounts.location.remote'), remote: '1', local: nil + .table-wrapper %table.table %thead %tr %th= t('admin.custom_emojis.emoji') %th= t('admin.custom_emojis.shortcode') + %th= t('admin.accounts.domain') + %th + %th %th %tbody = render @custom_emojis += paginate @custom_emojis = link_to t('admin.custom_emojis.upload'), new_admin_custom_emoji_path, class: 'button' diff --git a/config/brakeman.ignore b/config/brakeman.ignore index dbb59dd07..ed6e121d2 100644 --- a/config/brakeman.ignore +++ b/config/brakeman.ignore @@ -1,5 +1,81 @@ { "ignored_warnings": [ + { + "warning_type": "Cross-Site Scripting", + "warning_code": 4, + "fingerprint": "0adbe361b91afff22ba51e5fc2275ec703cc13255a0cb3eecd8dab223ab9f61e", + "check_name": "LinkToHref", + "message": "Potentially unsafe model attribute in link_to href", + "file": "app/views/admin/accounts/show.html.haml", + "line": 122, + "link": "http://brakemanscanner.org/docs/warning_types/link_to_href", + "code": "link_to(Account.find(params[:id]).inbox_url, Account.find(params[:id]).inbox_url)", + "render_path": [{"type":"controller","class":"Admin::AccountsController","method":"show","line":13,"file":"app/controllers/admin/accounts_controller.rb"}], + "location": { + "type": "template", + "template": "admin/accounts/show" + }, + "user_input": "Account.find(params[:id]).inbox_url", + "confidence": "Weak", + "note": "" + }, + { + "warning_type": "Cross-Site Scripting", + "warning_code": 4, + "fingerprint": "1fc29c578d0c89bf13bd5476829d272d54cd06b92ccf6df18568fa1f2674926e", + "check_name": "LinkToHref", + "message": "Potentially unsafe model attribute in link_to href", + "file": "app/views/admin/accounts/show.html.haml", + "line": 128, + "link": "http://brakemanscanner.org/docs/warning_types/link_to_href", + "code": "link_to(Account.find(params[:id]).shared_inbox_url, Account.find(params[:id]).shared_inbox_url)", + "render_path": [{"type":"controller","class":"Admin::AccountsController","method":"show","line":13,"file":"app/controllers/admin/accounts_controller.rb"}], + "location": { + "type": "template", + "template": "admin/accounts/show" + }, + "user_input": "Account.find(params[:id]).shared_inbox_url", + "confidence": "Weak", + "note": "" + }, + { + "warning_type": "Cross-Site Scripting", + "warning_code": 4, + "fingerprint": "2129d4c1e63a351d28d8d2937ff0b50237809c3df6725c0c5ef82b881dbb2086", + "check_name": "LinkToHref", + "message": "Potentially unsafe model attribute in link_to href", + "file": "app/views/admin/accounts/show.html.haml", + "line": 35, + "link": "http://brakemanscanner.org/docs/warning_types/link_to_href", + "code": "link_to(Account.find(params[:id]).url, Account.find(params[:id]).url)", + "render_path": [{"type":"controller","class":"Admin::AccountsController","method":"show","line":13,"file":"app/controllers/admin/accounts_controller.rb"}], + "location": { + "type": "template", + "template": "admin/accounts/show" + }, + "user_input": "Account.find(params[:id]).url", + "confidence": "Weak", + "note": "" + }, + { + "warning_type": "Dynamic Render Path", + "warning_code": 15, + "fingerprint": "3b0a20b08aef13cf8cf865384fae0cfd3324d8200a83262bf4abbc8091b5fec5", + "check_name": "Render", + "message": "Render path contains parameter value", + "file": "app/views/admin/custom_emojis/index.html.haml", + "line": 31, + "link": "http://brakemanscanner.org/docs/warning_types/dynamic_render_path/", + "code": "render(action => filtered_custom_emojis.page(params[:page]), {})", + "render_path": [{"type":"controller","class":"Admin::CustomEmojisController","method":"index","line":9,"file":"app/controllers/admin/custom_emojis_controller.rb"}], + "location": { + "type": "template", + "template": "admin/custom_emojis/index" + }, + "user_input": "params[:page]", + "confidence": "Weak", + "note": "" + }, { "warning_type": "Dynamic Render Path", "warning_code": 15, @@ -19,6 +95,44 @@ "confidence": "Weak", "note": "" }, + { + "warning_type": "Cross-Site Scripting", + "warning_code": 4, + "fingerprint": "64b5b2a02ede9c2b3598881eb5a466d63f7d27fe0946aa00d570111ec7338d2e", + "check_name": "LinkToHref", + "message": "Potentially unsafe model attribute in link_to href", + "file": "app/views/admin/accounts/show.html.haml", + "line": 131, + "link": "http://brakemanscanner.org/docs/warning_types/link_to_href", + "code": "link_to(Account.find(params[:id]).followers_url, Account.find(params[:id]).followers_url)", + "render_path": [{"type":"controller","class":"Admin::AccountsController","method":"show","line":13,"file":"app/controllers/admin/accounts_controller.rb"}], + "location": { + "type": "template", + "template": "admin/accounts/show" + }, + "user_input": "Account.find(params[:id]).followers_url", + "confidence": "Weak", + "note": "" + }, + { + "warning_type": "Cross-Site Scripting", + "warning_code": 4, + "fingerprint": "82f7b0d09beb3ab68e0fa16be63cedf4e820f2490326e9a1cec05761d92446cd", + "check_name": "LinkToHref", + "message": "Potentially unsafe model attribute in link_to href", + "file": "app/views/admin/accounts/show.html.haml", + "line": 106, + "link": "http://brakemanscanner.org/docs/warning_types/link_to_href", + "code": "link_to(Account.find(params[:id]).salmon_url, Account.find(params[:id]).salmon_url)", + "render_path": [{"type":"controller","class":"Admin::AccountsController","method":"show","line":13,"file":"app/controllers/admin/accounts_controller.rb"}], + "location": { + "type": "template", + "template": "admin/accounts/show" + }, + "user_input": "Account.find(params[:id]).salmon_url", + "confidence": "Weak", + "note": "" + }, { "warning_type": "Dynamic Render Path", "warning_code": 15, @@ -26,7 +140,7 @@ "check_name": "Render", "message": "Render path contains parameter value", "file": "app/views/admin/accounts/index.html.haml", - "line": 63, + "line": 64, "link": "http://brakemanscanner.org/docs/warning_types/dynamic_render_path/", "code": "render(action => filtered_accounts.page(params[:page]), {})", "render_path": [{"type":"controller","class":"Admin::AccountsController","method":"index","line":10,"file":"app/controllers/admin/accounts_controller.rb"}], @@ -38,6 +152,25 @@ "confidence": "Weak", "note": "" }, + { + "warning_type": "Cross-Site Scripting", + "warning_code": 4, + "fingerprint": "bb0ad5c4a42e06e3846c2089ff5269c17f65483a69414f6ce65eecf2bb11fab7", + "check_name": "LinkToHref", + "message": "Potentially unsafe model attribute in link_to href", + "file": "app/views/admin/accounts/show.html.haml", + "line": 95, + "link": "http://brakemanscanner.org/docs/warning_types/link_to_href", + "code": "link_to(Account.find(params[:id]).remote_url, Account.find(params[:id]).remote_url)", + "render_path": [{"type":"controller","class":"Admin::AccountsController","method":"show","line":13,"file":"app/controllers/admin/accounts_controller.rb"}], + "location": { + "type": "template", + "template": "admin/accounts/show" + }, + "user_input": "Account.find(params[:id]).remote_url", + "confidence": "Weak", + "note": "" + }, { "warning_type": "Redirect", "warning_code": 18, @@ -65,7 +198,7 @@ "check_name": "Render", "message": "Render path contains parameter value", "file": "app/views/admin/reports/index.html.haml", - "line": 24, + "line": 25, "link": "http://brakemanscanner.org/docs/warning_types/dynamic_render_path/", "code": "render(action => filtered_reports.page(params[:page]), {})", "render_path": [{"type":"controller","class":"Admin::ReportsController","method":"index","line":9,"file":"app/controllers/admin/reports_controller.rb"}], @@ -77,6 +210,45 @@ "confidence": "Weak", "note": "" }, + { + "warning_type": "SQL Injection", + "warning_code": 0, + "fingerprint": "cd440d9d0bcb76225f4142030cec0bdec6ad119c537c108c9d514bf87bc34d29", + "check_name": "SQL", + "message": "Possible SQL injection", + "file": "lib/mastodon/timestamp_ids.rb", + "line": 69, + "link": "http://brakemanscanner.org/docs/warning_types/sql_injection/", + "code": "ActiveRecord::Base.connection.execute(\" CREATE OR REPLACE FUNCTION timestamp_id(table_name text)\\n RETURNS bigint AS\\n $$\\n DECLARE\\n time_part bigint;\\n sequence_base bigint;\\n tail bigint;\\n BEGIN\\n -- Our ID will be composed of the following:\\n -- 6 bytes (48 bits) of millisecond-level timestamp\\n -- 2 bytes (16 bits) of sequence data\\n\\n -- The 'sequence data' is intended to be unique within a\\n -- given millisecond, yet obscure the 'serial number' of\\n -- this row.\\n\\n -- To do this, we hash the following data:\\n -- * Table name (if provided, skipped if not)\\n -- * Secret salt (should not be guessable)\\n -- * Timestamp (again, millisecond-level granularity)\\n\\n -- We then take the first two bytes of that value, and add\\n -- the lowest two bytes of the table ID sequence number\\n -- (`table_name`_id_seq). This means that even if we insert\\n -- two rows at the same millisecond, they will have\\n -- distinct 'sequence data' portions.\\n\\n -- If this happens, and an attacker can see both such IDs,\\n -- they can determine which of the two entries was inserted\\n -- first, but not the total number of entries in the table\\n -- (even mod 2**16).\\n\\n -- The table name is included in the hash to ensure that\\n -- different tables derive separate sequence bases so rows\\n -- inserted in the same millisecond in different tables do\\n -- not reveal the table ID sequence number for one another.\\n\\n -- The secret salt is included in the hash to ensure that\\n -- external users cannot derive the sequence base given the\\n -- timestamp and table name, which would allow them to\\n -- compute the table ID sequence number.\\n\\n time_part := (\\n -- Get the time in milliseconds\\n ((date_part('epoch', now()) * 1000))::bigint\\n -- And shift it over two bytes\\n << 16);\\n\\n sequence_base := (\\n 'x' ||\\n -- Take the first two bytes (four hex characters)\\n substr(\\n -- Of the MD5 hash of the data we documented\\n md5(table_name ||\\n '#{SecureRandom.hex(16)}' ||\\n time_part::text\\n ),\\n 1, 4\\n )\\n -- And turn it into a bigint\\n )::bit(16)::bigint;\\n\\n -- Finally, add our sequence number to our base, and chop\\n -- it to the last two bytes\\n tail := (\\n (sequence_base + nextval(table_name || '_id_seq'))\\n & 65535);\\n\\n -- Return the time part and the sequence part. OR appears\\n -- faster here than addition, but they're equivalent:\\n -- time_part has no trailing two bytes, and tail is only\\n -- the last two bytes.\\n RETURN time_part | tail;\\n END\\n $$ LANGUAGE plpgsql VOLATILE;\\n\")", + "render_path": null, + "location": { + "type": "method", + "class": "Mastodon::TimestampIds", + "method": "s(:self).define_timestamp_id" + }, + "user_input": "SecureRandom.hex(16)", + "confidence": "Medium", + "note": "" + }, + { + "warning_type": "Cross-Site Scripting", + "warning_code": 4, + "fingerprint": "e04aafe1e06cf8317fb6ac0a7f35783e45aa1274272ee6eaf28d39adfdad489b", + "check_name": "LinkToHref", + "message": "Potentially unsafe model attribute in link_to href", + "file": "app/views/admin/accounts/show.html.haml", + "line": 125, + "link": "http://brakemanscanner.org/docs/warning_types/link_to_href", + "code": "link_to(Account.find(params[:id]).outbox_url, Account.find(params[:id]).outbox_url)", + "render_path": [{"type":"controller","class":"Admin::AccountsController","method":"show","line":13,"file":"app/controllers/admin/accounts_controller.rb"}], + "location": { + "type": "template", + "template": "admin/accounts/show" + }, + "user_input": "Account.find(params[:id]).outbox_url", + "confidence": "Weak", + "note": "" + }, { "warning_type": "Dynamic Render Path", "warning_code": 15, @@ -84,7 +256,7 @@ "check_name": "Render", "message": "Render path contains parameter value", "file": "app/views/stream_entries/show.html.haml", - "line": 23, + "line": 21, "link": "http://brakemanscanner.org/docs/warning_types/dynamic_render_path/", "code": "render(partial => \"stream_entries/#{Account.find_local!(params[:account_username]).statuses.find(params[:id]).stream_entry.activity_type.downcase}\", { :locals => ({ Account.find_local!(params[:account_username]).statuses.find(params[:id]).stream_entry.activity_type.downcase.to_sym => Account.find_local!(params[:account_username]).statuses.find(params[:id]).stream_entry.activity, :include_threads => true }) })", "render_path": [{"type":"controller","class":"StatusesController","method":"show","line":20,"file":"app/controllers/statuses_controller.rb"}], @@ -97,6 +269,6 @@ "note": "" } ], - "updated": "2017-08-30 05:14:04 +0200", - "brakeman_version": "3.7.2" + "updated": "2017-10-05 20:06:40 +0200", + "brakeman_version": "4.0.1" } diff --git a/config/locales/de.yml b/config/locales/de.yml index ec48bd5ff..7c0edff94 100644 --- a/config/locales/de.yml +++ b/config/locales/de.yml @@ -137,7 +137,7 @@ de: reject_media: Mediendateien ablehnen reject_media_hint: Entfernt lokal gespeicherte Mediendateien und verhindert deren künftiges Herunterladen. Für Sperren irrelevant severities: - none: Kein + noop: Kein silence: Stummschaltung suspend: Sperren severity: Schweregrad @@ -180,7 +180,7 @@ de: nsfw: 'false': Medienanhänge wieder anzeigen 'true': Medienanhänge verbergen - report: "Meldung #%{id}" + report: 'Meldung #%{id}' report_contents: Inhalt reported_account: Gemeldetes Konto reported_by: Gemeldet von @@ -386,7 +386,7 @@ de: body: "%{name} hat dich erwähnt:" subject: "%{name} hat dich erwähnt" reblog: - body: '%{name} hat deinen Beitrag geteilt:' + body: "%{name} hat deinen Beitrag geteilt:" subject: "%{name} hat deinen Beitrag geteilt" number: human: diff --git a/config/locales/en.yml b/config/locales/en.yml index 5d9557535..2059c5e2b 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -109,10 +109,17 @@ en: username: Username web: Web custom_emojis: + copied_msg: Successfully created local copy of the emoji + copy: Copy + copy_failed_msg: Could not make a local copy of that emoji created_msg: Emoji successfully created! delete: Delete destroyed_msg: Emojo successfully destroyed! + disable: Disable + disabled_msg: Successfully disabled that emoji emoji: Emoji + enable: Enable + enabled_msg: Successfully enabled that emoji image_hint: PNG up to 50KB new: title: Add new custom emoji diff --git a/config/routes.rb b/config/routes.rb index 959afc23f..cc1f66e52 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -139,7 +139,13 @@ Rails.application.routes.draw do resource :two_factor_authentication, only: [:destroy] end - resources :custom_emojis, only: [:index, :new, :create, :destroy] + resources :custom_emojis, only: [:index, :new, :create, :destroy] do + member do + post :copy + post :enable + post :disable + end + end end get '/admin', to: redirect('/admin/settings/edit', status: 302) diff --git a/db/migrate/20171005171936_add_disabled_to_custom_emojis.rb b/db/migrate/20171005171936_add_disabled_to_custom_emojis.rb new file mode 100644 index 000000000..067a7bee0 --- /dev/null +++ b/db/migrate/20171005171936_add_disabled_to_custom_emojis.rb @@ -0,0 +1,15 @@ +require Rails.root.join('lib', 'mastodon', 'migration_helpers') + +class AddDisabledToCustomEmojis < ActiveRecord::Migration[5.1] + include Mastodon::MigrationHelpers + + disable_ddl_transaction! + + def up + safety_assured { add_column_with_default :custom_emojis, :disabled, :bool, default: false } + end + + def down + remove_column :custom_emojis, :disabled + end +end diff --git a/db/schema.rb b/db/schema.rb index 337678c67..3358e2997 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: 20170928082043) do +ActiveRecord::Schema.define(version: 20171005171936) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -98,6 +98,7 @@ ActiveRecord::Schema.define(version: 20170928082043) do t.datetime "image_updated_at" t.datetime "created_at", null: false t.datetime "updated_at", null: false + t.boolean "disabled", default: false, null: false t.index ["shortcode", "domain"], name: "index_custom_emojis_on_shortcode_and_domain", unique: true end -- cgit From 633426b2616e8559acfa76f4294a51afcf434fc2 Mon Sep 17 00:00:00 2001 From: nullkal Date: Sun, 8 Oct 2017 03:26:43 +0900 Subject: Add moderation note (#5240) * Add moderation note * Add frozen_string_literal * Make rspec pass --- .../admin/account_moderation_notes_controller.rb | 31 ++++++++++++++++++++++ app/controllers/admin/accounts_controller.rb | 5 +++- .../admin/account_moderation_notes_helper.rb | 4 +++ app/models/account.rb | 4 +++ app/models/account_moderation_note.rb | 22 +++++++++++++++ .../_account_moderation_note.html.haml | 10 +++++++ app/views/admin/accounts/show.html.haml | 22 +++++++++++++++ config/locales/en.yml | 10 +++++++ config/routes.rb | 2 ++ ...171005102658_create_account_moderation_notes.rb | 12 +++++++++ db/schema.rb | 11 ++++++++ .../account_moderation_notes_controller_spec.rb | 4 +++ .../account_moderation_note_fabricator.rb | 4 +++ .../admin/account_moderation_notes_helper_spec.rb | 15 +++++++++++ spec/models/account_moderation_note_spec.rb | 5 ++++ 15 files changed, 160 insertions(+), 1 deletion(-) create mode 100644 app/controllers/admin/account_moderation_notes_controller.rb create mode 100644 app/helpers/admin/account_moderation_notes_helper.rb create mode 100644 app/models/account_moderation_note.rb create mode 100644 app/views/admin/account_moderation_notes/_account_moderation_note.html.haml create mode 100644 db/migrate/20171005102658_create_account_moderation_notes.rb create mode 100644 spec/controllers/admin/account_moderation_notes_controller_spec.rb create mode 100644 spec/fabricators/account_moderation_note_fabricator.rb create mode 100644 spec/helpers/admin/account_moderation_notes_helper_spec.rb create mode 100644 spec/models/account_moderation_note_spec.rb (limited to 'app/controllers/admin') diff --git a/app/controllers/admin/account_moderation_notes_controller.rb b/app/controllers/admin/account_moderation_notes_controller.rb new file mode 100644 index 000000000..414a875d0 --- /dev/null +++ b/app/controllers/admin/account_moderation_notes_controller.rb @@ -0,0 +1,31 @@ +# frozen_string_literal: true + +class Admin::AccountModerationNotesController < Admin::BaseController + def create + @account_moderation_note = current_account.account_moderation_notes.new(resource_params) + if @account_moderation_note.save + @target_account = @account_moderation_note.target_account + redirect_to admin_account_path(@target_account.id), notice: I18n.t('admin.account_moderation_notes.created_msg') + else + @account = @account_moderation_note.target_account + @moderation_notes = @account.targeted_moderation_notes.latest + render template: 'admin/accounts/show' + end + end + + def destroy + @account_moderation_note = AccountModerationNote.find(params[:id]) + @target_account = @account_moderation_note.target_account + @account_moderation_note.destroy + redirect_to admin_account_path(@target_account.id), notice: I18n.t('admin.account_moderation_notes.destroyed_msg') + end + + private + + def resource_params + params.require(:account_moderation_note).permit( + :content, + :target_account_id + ) + end +end diff --git a/app/controllers/admin/accounts_controller.rb b/app/controllers/admin/accounts_controller.rb index 54c659e1b..ffa4dc850 100644 --- a/app/controllers/admin/accounts_controller.rb +++ b/app/controllers/admin/accounts_controller.rb @@ -9,7 +9,10 @@ module Admin @accounts = filtered_accounts.page(params[:page]) end - def show; end + def show + @account_moderation_note = current_account.account_moderation_notes.new(target_account: @account) + @moderation_notes = @account.targeted_moderation_notes.latest + end def subscribe Pubsubhubbub::SubscribeWorker.perform_async(@account.id) diff --git a/app/helpers/admin/account_moderation_notes_helper.rb b/app/helpers/admin/account_moderation_notes_helper.rb new file mode 100644 index 000000000..b17c52264 --- /dev/null +++ b/app/helpers/admin/account_moderation_notes_helper.rb @@ -0,0 +1,4 @@ +# frozen_string_literal: true + +module Admin::AccountModerationNotesHelper +end diff --git a/app/models/account.rb b/app/models/account.rb index 54035d94a..88f16026d 100644 --- a/app/models/account.rb +++ b/app/models/account.rb @@ -90,6 +90,10 @@ class Account < ApplicationRecord has_many :reports has_many :targeted_reports, class_name: 'Report', foreign_key: :target_account_id + # Moderation notes + has_many :account_moderation_notes + has_many :targeted_moderation_notes, class_name: 'AccountModerationNote', foreign_key: :target_account_id + scope :remote, -> { where.not(domain: nil) } scope :local, -> { where(domain: nil) } scope :without_followers, -> { where(followers_count: 0) } diff --git a/app/models/account_moderation_note.rb b/app/models/account_moderation_note.rb new file mode 100644 index 000000000..be52d10b6 --- /dev/null +++ b/app/models/account_moderation_note.rb @@ -0,0 +1,22 @@ +# frozen_string_literal: true + +# == Schema Information +# +# Table name: account_moderation_notes +# +# id :integer not null, primary key +# content :text not null +# account_id :integer +# target_account_id :integer +# created_at :datetime not null +# updated_at :datetime not null +# + +class AccountModerationNote < ApplicationRecord + belongs_to :account + belongs_to :target_account, class_name: 'Account' + + scope :latest, -> { reorder('created_at DESC') } + + validates :content, presence: true, length: { maximum: 500 } +end diff --git a/app/views/admin/account_moderation_notes/_account_moderation_note.html.haml b/app/views/admin/account_moderation_notes/_account_moderation_note.html.haml new file mode 100644 index 000000000..4651630e9 --- /dev/null +++ b/app/views/admin/account_moderation_notes/_account_moderation_note.html.haml @@ -0,0 +1,10 @@ +%tr + %td + = simple_format(h(account_moderation_note.content)) + %td + = account_moderation_note.account.acct + %td + %time.formatted{ datetime: account_moderation_note.created_at.iso8601, title: l(account_moderation_note.created_at) } + = l account_moderation_note.created_at + %td + = link_to t('admin.account_moderation_notes.delete'), admin_account_moderation_note_path(account_moderation_note), method: :delete diff --git a/app/views/admin/accounts/show.html.haml b/app/views/admin/accounts/show.html.haml index 3775b6721..1f5c8fcf5 100644 --- a/app/views/admin/accounts/show.html.haml +++ b/app/views/admin/accounts/show.html.haml @@ -129,3 +129,25 @@ %tr %th= t('admin.accounts.followers_url') %td= link_to @account.followers_url, @account.followers_url + +%hr +%h3= t('admin.accounts.moderation_notes') + += simple_form_for @account_moderation_note, url: admin_account_moderation_notes_path do |f| + = render 'shared/error_messages', object: @account_moderation_note + + = f.input :content + = f.hidden_field :target_account_id + + .actions + = f.button :button, t('admin.account_moderation_notes.create'), type: :submit + +.table-wrapper + %table.table + %thead + %tr + %th + %th= t('admin.account_moderation_notes.account') + %th= t('admin.account_moderation_notes.created_at') + %tbody + = render @moderation_notes diff --git a/config/locales/en.yml b/config/locales/en.yml index 82041be24..7d2596fc6 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -76,6 +76,7 @@ en: silenced: Silenced suspended: Suspended title: Moderation + moderation_notes: Moderation notes most_recent_activity: Most recent activity most_recent_ip: Most recent IP not_subscribed: Not subscribed @@ -109,6 +110,15 @@ en: unsubscribe: Unsubscribe username: Username web: Web + + account_moderation_notes: + account: Moderator + created_at: Date + create: Create + created_msg: Moderation note successfully created! + delete: Delete + destroyed_msg: Moderation note successfully destroyed! + custom_emojis: copied_msg: Successfully created local copy of the emoji copy: Copy diff --git a/config/routes.rb b/config/routes.rb index bd7068b5c..5a6351f77 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -147,6 +147,8 @@ Rails.application.routes.draw do post :disable end end + + resources :account_moderation_notes, only: [:create, :destroy] end get '/admin', to: redirect('/admin/settings/edit', status: 302) diff --git a/db/migrate/20171005102658_create_account_moderation_notes.rb b/db/migrate/20171005102658_create_account_moderation_notes.rb new file mode 100644 index 000000000..d1802b5b3 --- /dev/null +++ b/db/migrate/20171005102658_create_account_moderation_notes.rb @@ -0,0 +1,12 @@ +class CreateAccountModerationNotes < ActiveRecord::Migration[5.1] + def change + create_table :account_moderation_notes do |t| + t.text :content, null: false + t.references :account + t.references :target_account + + t.timestamps + end + add_foreign_key :account_moderation_notes, :accounts, column: :target_account_id + end +end diff --git a/db/schema.rb b/db/schema.rb index 7180d3515..91f1b1acb 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -23,6 +23,16 @@ ActiveRecord::Schema.define(version: 20171006142024) do t.index ["account_id", "domain"], name: "index_account_domain_blocks_on_account_id_and_domain", unique: true end + create_table "account_moderation_notes", force: :cascade do |t| + t.text "content", null: false + t.bigint "account_id" + t.bigint "target_account_id" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.index ["account_id"], name: "index_account_moderation_notes_on_account_id" + t.index ["target_account_id"], name: "index_account_moderation_notes_on_target_account_id" + end + create_table "accounts", force: :cascade do |t| t.string "username", default: "", null: false t.string "domain" @@ -449,6 +459,7 @@ ActiveRecord::Schema.define(version: 20171006142024) do end add_foreign_key "account_domain_blocks", "accounts", name: "fk_206c6029bd", on_delete: :cascade + add_foreign_key "account_moderation_notes", "accounts", column: "target_account_id" add_foreign_key "blocks", "accounts", column: "target_account_id", name: "fk_9571bfabc1", on_delete: :cascade add_foreign_key "blocks", "accounts", name: "fk_4269e03e65", on_delete: :cascade add_foreign_key "conversation_mutes", "accounts", name: "fk_225b4212bb", on_delete: :cascade diff --git a/spec/controllers/admin/account_moderation_notes_controller_spec.rb b/spec/controllers/admin/account_moderation_notes_controller_spec.rb new file mode 100644 index 000000000..ca4e55c4d --- /dev/null +++ b/spec/controllers/admin/account_moderation_notes_controller_spec.rb @@ -0,0 +1,4 @@ +require 'rails_helper' + +RSpec.describe Admin::AccountModerationNotesController, type: :controller do +end diff --git a/spec/fabricators/account_moderation_note_fabricator.rb b/spec/fabricators/account_moderation_note_fabricator.rb new file mode 100644 index 000000000..9277af165 --- /dev/null +++ b/spec/fabricators/account_moderation_note_fabricator.rb @@ -0,0 +1,4 @@ +Fabricator(:account_moderation_note) do + content "MyText" + account nil +end diff --git a/spec/helpers/admin/account_moderation_notes_helper_spec.rb b/spec/helpers/admin/account_moderation_notes_helper_spec.rb new file mode 100644 index 000000000..01b60c851 --- /dev/null +++ b/spec/helpers/admin/account_moderation_notes_helper_spec.rb @@ -0,0 +1,15 @@ +require 'rails_helper' + +# Specs in this file have access to a helper object that includes +# the Admin::AccountModerationNotesHelper. For example: +# +# describe Admin::AccountModerationNotesHelper do +# describe "string concat" do +# it "concats two strings with spaces" do +# expect(helper.concat_strings("this","that")).to eq("this that") +# end +# end +# end +RSpec.describe Admin::AccountModerationNotesHelper, type: :helper do + pending "add some examples to (or delete) #{__FILE__}" +end diff --git a/spec/models/account_moderation_note_spec.rb b/spec/models/account_moderation_note_spec.rb new file mode 100644 index 000000000..c4be8c4af --- /dev/null +++ b/spec/models/account_moderation_note_spec.rb @@ -0,0 +1,5 @@ +require 'rails_helper' + +RSpec.describe AccountModerationNote, type: :model do + pending "add some examples to (or delete) #{__FILE__}" +end -- cgit From 61d3ecc8055cc9e72826e92638caa5f667023683 Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Tue, 10 Oct 2017 15:18:27 +0200 Subject: Fix custom emoji copy not copying file (#5298) --- app/controllers/admin/custom_emojis_controller.rb | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) (limited to 'app/controllers/admin') diff --git a/app/controllers/admin/custom_emojis_controller.rb b/app/controllers/admin/custom_emojis_controller.rb index dba9f1012..ca81f3255 100644 --- a/app/controllers/admin/custom_emojis_controller.rb +++ b/app/controllers/admin/custom_emojis_controller.rb @@ -28,8 +28,7 @@ module Admin end def copy - emoji = @custom_emoji.dup - emoji.domain = nil + emoji = CustomEmoji.new(domain: nil, shortcode: @custom_emoji.shortcode, image: @custom_emoji.image) if emoji.save redirect_to admin_custom_emojis_path, notice: I18n.t('admin.custom_emojis.copied_msg') -- cgit