Add moderator role and add pundit policies for admin actions (#5635)

* Add moderator role and add pundit policies for admin actions

* Add rake task for turning user into mod and revoking it again

* Fix handling of unauthorized exception

* Deliver new report e-mails to staff, not just admins

* Add promote/demote to admin UI, hide some actions conditionally

* Fix unused i18n
This commit is contained in:
Eugen Rochko 2017-11-11 20:23:33 +01:00 committed by GitHub
parent 2b1190065c
commit 7bb8b0b2fc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
44 changed files with 539 additions and 91 deletions

View File

@ -1,23 +1,28 @@
# frozen_string_literal: true
class Admin::AccountModerationNotesController < Admin::BaseController
module Admin
class AccountModerationNotesController < BaseController
before_action :set_account_moderation_note, only: [:destroy]
def create
authorize AccountModerationNote, :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')
redirect_to admin_account_path(@account_moderation_note.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
authorize @account_moderation_note, :destroy?
@account_moderation_note.destroy
redirect_to admin_account_path(@target_account.id), notice: I18n.t('admin.account_moderation_notes.destroyed_msg')
redirect_to admin_account_path(@account_moderation_note.target_account_id), notice: I18n.t('admin.account_moderation_notes.destroyed_msg')
end
private
@ -28,4 +33,9 @@ class Admin::AccountModerationNotesController < Admin::BaseController
:target_account_id
)
end
def set_account_moderation_note
@account_moderation_note = AccountModerationNote.find(params[:id])
end
end
end

View File

@ -7,40 +7,49 @@ module Admin
before_action :require_local_account!, only: [:enable, :disable, :memorialize]
def index
authorize :account, :index?
@accounts = filtered_accounts.page(params[:page])
end
def show
authorize @account, :show?
@account_moderation_note = current_account.account_moderation_notes.new(target_account: @account)
@moderation_notes = @account.targeted_moderation_notes.latest
end
def subscribe
authorize @account, :subscribe?
Pubsubhubbub::SubscribeWorker.perform_async(@account.id)
redirect_to admin_account_path(@account.id)
end
def unsubscribe
authorize @account, :unsubscribe?
Pubsubhubbub::UnsubscribeWorker.perform_async(@account.id)
redirect_to admin_account_path(@account.id)
end
def memorialize
authorize @account, :memorialize?
@account.memorialize!
redirect_to admin_account_path(@account.id)
end
def enable
authorize @account.user, :enable?
@account.user.enable!
redirect_to admin_account_path(@account.id)
end
def disable
authorize @account.user, :disable?
@account.user.disable!
redirect_to admin_account_path(@account.id)
end
def redownload
authorize @account, :redownload?
@account.reset_avatar!
@account.reset_header!
@account.save!

View File

@ -2,7 +2,9 @@
module Admin
class BaseController < ApplicationController
before_action :require_admin!
include Authorization
before_action :require_staff!
layout 'admin'
end

View File

@ -2,15 +2,18 @@
module Admin
class ConfirmationsController < BaseController
before_action :set_user
def create
account_user.confirm
authorize @user, :confirm?
@user.confirm!
redirect_to admin_accounts_path
end
private
def account_user
Account.find(params[:account_id]).user || raise(ActiveRecord::RecordNotFound)
def set_user
@user = Account.find(params[:account_id]).user || raise(ActiveRecord::RecordNotFound)
end
end
end

View File

@ -5,14 +5,18 @@ module Admin
before_action :set_custom_emoji, except: [:index, :new, :create]
def index
authorize :custom_emoji, :index?
@custom_emojis = filtered_custom_emojis.eager_load(:local_counterpart).page(params[:page])
end
def new
authorize :custom_emoji, :create?
@custom_emoji = CustomEmoji.new
end
def create
authorize :custom_emoji, :create?
@custom_emoji = CustomEmoji.new(resource_params)
if @custom_emoji.save
@ -23,6 +27,8 @@ module Admin
end
def update
authorize @custom_emoji, :update?
if @custom_emoji.update(resource_params)
redirect_to admin_custom_emojis_path, notice: I18n.t('admin.custom_emojis.updated_msg')
else
@ -31,11 +37,14 @@ module Admin
end
def destroy
authorize @custom_emoji, :destroy?
@custom_emoji.destroy
redirect_to admin_custom_emojis_path, notice: I18n.t('admin.custom_emojis.destroyed_msg')
end
def copy
authorize @custom_emoji, :copy?
emoji = CustomEmoji.find_or_create_by(domain: nil, shortcode: @custom_emoji.shortcode)
if emoji.update(image: @custom_emoji.image)
@ -48,11 +57,13 @@ module Admin
end
def enable
authorize @custom_emoji, :enable?
@custom_emoji.update!(disabled: false)
redirect_to admin_custom_emojis_path, notice: I18n.t('admin.custom_emojis.enabled_msg')
end
def disable
authorize @custom_emoji, :disable?
@custom_emoji.update!(disabled: true)
redirect_to admin_custom_emojis_path, notice: I18n.t('admin.custom_emojis.disabled_msg')
end

View File

@ -5,14 +5,18 @@ module Admin
before_action :set_domain_block, only: [:show, :destroy]
def index
authorize :domain_block, :index?
@domain_blocks = DomainBlock.page(params[:page])
end
def new
authorize :domain_block, :create?
@domain_block = DomainBlock.new
end
def create
authorize :domain_block, :create?
@domain_block = DomainBlock.new(resource_params)
if @domain_block.save
@ -23,9 +27,12 @@ module Admin
end
end
def show; end
def show
authorize @domain_block, :show?
end
def destroy
authorize @domain_block, :destroy?
UnblockDomainService.new.call(@domain_block, retroactive_unblock?)
redirect_to admin_domain_blocks_path, notice: I18n.t('admin.domain_blocks.destroyed_msg')
end

View File

@ -5,14 +5,18 @@ module Admin
before_action :set_email_domain_block, only: [:show, :destroy]
def index
authorize :email_domain_block, :index?
@email_domain_blocks = EmailDomainBlock.page(params[:page])
end
def new
authorize :email_domain_block, :create?
@email_domain_block = EmailDomainBlock.new
end
def create
authorize :email_domain_block, :create?
@email_domain_block = EmailDomainBlock.new(resource_params)
if @email_domain_block.save
@ -23,6 +27,7 @@ module Admin
end
def destroy
authorize @email_domain_block, :destroy?
@email_domain_block.destroy
redirect_to admin_email_domain_blocks_path, notice: I18n.t('admin.email_domain_blocks.destroyed_msg')
end

View File

@ -3,10 +3,12 @@
module Admin
class InstancesController < BaseController
def index
authorize :instance, :index?
@instances = ordered_instances
end
def resubscribe
authorize :instance, :resubscribe?
params.require(:by_domain)
Pubsubhubbub::SubscribeWorker.push_bulk(subscribeable_accounts.pluck(:id))
redirect_to admin_instances_path

View File

@ -2,19 +2,20 @@
module Admin
class ReportedStatusesController < BaseController
include Authorization
before_action :set_report
before_action :set_status, only: [:update, :destroy]
def create
authorize :status, :update?
@form = Form::StatusBatch.new(form_status_batch_params)
flash[:alert] = t('admin.statuses.failed_to_execute') unless @form.save
flash[:alert] = I18n.t('admin.statuses.failed_to_execute') unless @form.save
redirect_to admin_report_path(@report)
end
def update
authorize @status, :update?
@status.update(status_params)
redirect_to admin_report_path(@report)
end

View File

@ -5,14 +5,17 @@ module Admin
before_action :set_report, except: [:index]
def index
authorize :report, :index?
@reports = filtered_reports.page(params[:page])
end
def show
authorize @report, :show?
@form = Form::StatusBatch.new
end
def update
authorize @report, :update?
process_report
redirect_to admin_report_path(@report)
end

View File

@ -2,17 +2,18 @@
module Admin
class ResetsController < BaseController
before_action :set_account
before_action :set_user
def create
@account.user.send_reset_password_instructions
authorize @user, :reset_password?
@user.send_reset_password_instructions
redirect_to admin_accounts_path
end
private
def set_account
@account = Account.find(params[:account_id])
def set_user
@user = Account.find(params[:account_id]).user || raise(ActiveRecord::RecordNotFound)
end
end
end

View File

@ -0,0 +1,25 @@
# frozen_string_literal: true
module Admin
class RolesController < BaseController
before_action :set_user
def promote
authorize @user, :promote?
@user.promote!
redirect_to admin_account_path(@user.account_id)
end
def demote
authorize @user, :demote?
@user.demote!
redirect_to admin_account_path(@user.account_id)
end
private
def set_user
@user = Account.find(params[:account_id]).user || raise(ActiveRecord::RecordNotFound)
end
end
end

View File

@ -28,10 +28,13 @@ module Admin
).freeze
def edit
authorize :settings, :show?
@admin_settings = Form::AdminSettings.new
end
def update
authorize :settings, :update?
settings_params.each do |key, value|
if UPLOAD_SETTINGS.include?(key)
upload = SiteUpload.where(var: key).first_or_initialize(var: key)

View File

@ -5,11 +5,13 @@ module Admin
before_action :set_account
def create
authorize @account, :silence?
@account.update(silenced: true)
redirect_to admin_accounts_path
end
def destroy
authorize @account, :unsilence?
@account.update(silenced: false)
redirect_to admin_accounts_path
end

View File

@ -2,8 +2,6 @@
module Admin
class StatusesController < BaseController
include Authorization
helper_method :current_params
before_action :set_account
@ -12,24 +10,30 @@ module Admin
PER_PAGE = 20
def index
authorize :status, :index?
@statuses = @account.statuses
if params[:media]
account_media_status_ids = @account.media_attachments.attached.reorder(nil).select(:status_id).distinct
@statuses.merge!(Status.where(id: account_media_status_ids))
end
@statuses = @statuses.preload(:media_attachments, :mentions).page(params[:page]).per(PER_PAGE)
@statuses = @statuses.preload(:media_attachments, :mentions).page(params[:page]).per(PER_PAGE)
@form = Form::StatusBatch.new
end
def create
authorize :status, :update?
@form = Form::StatusBatch.new(form_status_batch_params)
flash[:alert] = t('admin.statuses.failed_to_execute') unless @form.save
flash[:alert] = I18n.t('admin.statuses.failed_to_execute') unless @form.save
redirect_to admin_account_statuses_path(@account.id, current_params)
end
def update
authorize @status, :update?
@status.update(status_params)
redirect_to admin_account_statuses_path(@account.id, current_params)
end
@ -60,6 +64,7 @@ module Admin
def current_params
page = (params[:page] || 1).to_i
{
media: params[:media],
page: page > 1 && page,

View File

@ -3,6 +3,7 @@
module Admin
class SubscriptionsController < BaseController
def index
authorize :subscription, :index?
@subscriptions = ordered_subscriptions.page(requested_page)
end

View File

@ -5,11 +5,13 @@ module Admin
before_action :set_account
def create
authorize @account, :suspend?
Admin::SuspensionWorker.perform_async(@account.id)
redirect_to admin_accounts_path
end
def destroy
authorize @account, :unsuspend?
@account.unsuspend!
redirect_to admin_accounts_path
end

View File

@ -5,6 +5,7 @@ module Admin
before_action :set_user
def destroy
authorize @user, :disable_2fa?
@user.disable_two_factor!
redirect_to admin_accounts_path
end

View File

@ -19,7 +19,7 @@ class Api::V1::ReportsController < Api::BaseController
comment: report_params[:comment]
)
User.admins.includes(:account).each { |u| AdminMailer.new_report(u.account, @report).deliver_later }
User.staff.includes(:account).each { |u| AdminMailer.new_report(u.account, @report).deliver_later }
render json: @report, serializer: REST::ReportSerializer
end

View File

@ -18,6 +18,7 @@ class ApplicationController < ActionController::Base
rescue_from ActionController::RoutingError, with: :not_found
rescue_from ActiveRecord::RecordNotFound, with: :not_found
rescue_from ActionController::InvalidAuthenticityToken, with: :unprocessable_entity
rescue_from Mastodon::NotPermittedError, with: :forbidden
before_action :store_current_location, except: :raise_not_found, unless: :devise_controller?
before_action :check_suspension, if: :user_signed_in?
@ -40,6 +41,10 @@ class ApplicationController < ActionController::Base
redirect_to root_path unless current_user&.admin?
end
def require_staff!
redirect_to root_path unless current_user&.staff?
end
def check_suspension
forbidden if current_user.account.suspended?
end

View File

@ -2,6 +2,7 @@
module Authorization
extend ActiveSupport::Concern
include Pundit
def pundit_user

View File

@ -35,6 +35,11 @@ module ApplicationHelper
Rails.env.production? ? site_title : "#{site_title} (Dev)"
end
def can?(action, record)
return false if record.nil?
policy(record).public_send("#{action}?")
end
def fa_icon(icon, attributes = {})
class_names = attributes[:class]&.split(' ') || []
class_names << 'fa'

View File

@ -32,6 +32,7 @@
# filtered_languages :string default([]), not null, is an Array
# account_id :integer not null
# disabled :boolean default(FALSE), not null
# moderator :boolean default(FALSE), not null
#
class User < ApplicationRecord
@ -55,6 +56,8 @@ class User < ApplicationRecord
scope :recent, -> { order(id: :desc) }
scope :admins, -> { where(admin: true) }
scope :moderators, -> { where(moderator: true) }
scope :staff, -> { admins.or(moderators) }
scope :confirmed, -> { where.not(confirmed_at: nil) }
scope :inactive, -> { where(arel_table[:current_sign_in_at].lt(ACTIVE_DURATION.ago)) }
scope :active, -> { confirmed.where(arel_table[:current_sign_in_at].gteq(ACTIVE_DURATION.ago)).joins(:account).where(accounts: { suspended: false }) }
@ -74,6 +77,20 @@ class User < ApplicationRecord
confirmed_at.present?
end
def staff?
admin? || moderator?
end
def role
if admin?
'admin'
elsif moderator?
'moderator'
else
'user'
end
end
def disable!
update!(disabled: true,
last_sign_in_at: current_sign_in_at,
@ -84,6 +101,27 @@ class User < ApplicationRecord
update!(disabled: false)
end
def confirm!
skip_confirmation!
save!
end
def promote!
if moderator?
update!(moderator: false, admin: true)
elsif !admin?
update!(moderator: true)
end
end
def demote!
if admin?
update!(admin: false, moderator: true)
elsif moderator?
update!(moderator: false)
end
end
def disable_two_factor!
self.otp_required_for_login = false
otp_backup_codes&.clear

View File

@ -0,0 +1,17 @@
# frozen_string_literal: true
class AccountModerationNotePolicy < ApplicationPolicy
def create?
staff?
end
def destroy?
admin? || owner?
end
private
def owner?
record.account_id == current_account&.id
end
end

View File

@ -0,0 +1,43 @@
# frozen_string_literal: true
class AccountPolicy < ApplicationPolicy
def index?
staff?
end
def show?
staff?
end
def suspend?
staff? && !record.user&.staff?
end
def unsuspend?
staff?
end
def silence?
staff? && !record.user&.staff?
end
def unsilence?
staff?
end
def redownload?
admin?
end
def subscribe?
admin?
end
def unsubscribe?
admin?
end
def memorialize?
admin? && !record.user&.admin?
end
end

View File

@ -0,0 +1,18 @@
# frozen_string_literal: true
class ApplicationPolicy
attr_reader :current_account, :record
def initialize(current_account, record)
@current_account = current_account
@record = record
end
delegate :admin?, :moderator?, :staff?, to: :current_user, allow_nil: true
private
def current_user
current_account&.user
end
end

View File

@ -0,0 +1,31 @@
# frozen_string_literal: true
class CustomEmojiPolicy < ApplicationPolicy
def index?
staff?
end
def create?
admin?
end
def update?
admin?
end
def copy?
admin?
end
def enable?
staff?
end
def disable?
staff?
end
def destroy?
admin?
end
end

View File

@ -0,0 +1,19 @@
# frozen_string_literal: true
class DomainBlockPolicy < ApplicationPolicy
def index?
admin?
end
def show?
admin?
end
def create?
admin?
end
def destroy?
admin?
end
end

View File

@ -0,0 +1,15 @@
# frozen_string_literal: true
class EmailDomainBlockPolicy < ApplicationPolicy
def index?
admin?
end
def create?
admin?
end
def destroy?
admin?
end
end

View File

@ -0,0 +1,11 @@
# frozen_string_literal: true
class InstancePolicy < ApplicationPolicy
def index?
admin?
end
def resubscribe?
admin?
end
end

View File

@ -0,0 +1,15 @@
# frozen_string_literal: true
class ReportPolicy < ApplicationPolicy
def update?
staff?
end
def index?
staff?
end
def show?
staff?
end
end

View File

@ -0,0 +1,11 @@
# frozen_string_literal: true
class SettingsPolicy < ApplicationPolicy
def update?
admin?
end
def show?
admin?
end
end

View File

@ -1,20 +1,17 @@
# frozen_string_literal: true
class StatusPolicy
attr_reader :account, :status
def initialize(account, status)
@account = account
@status = status
class StatusPolicy < ApplicationPolicy
def index?
staff?
end
def show?
if direct?
owned? || status.mentions.where(account: account).exists?
owned? || record.mentions.where(account: current_account).exists?
elsif private?
owned? || account&.following?(status.account) || status.mentions.where(account: account).exists?
owned? || current_account&.following?(author) || record.mentions.where(account: current_account).exists?
else
account.nil? || !status.account.blocking?(account)
current_account.nil? || !author.blocking?(current_account)
end
end
@ -23,26 +20,30 @@ class StatusPolicy
end
def destroy?
admin? || owned?
staff? || owned?
end
alias unreblog? destroy?
private
def admin?
account&.user&.admin?
def update?
staff?
end
private
def direct?
status.direct_visibility?
record.direct_visibility?
end
def owned?
status.account.id == account&.id
author.id == current_account&.id
end
def private?
status.private_visibility?
record.private_visibility?
end
def author
record.account
end
end

View File

@ -0,0 +1,7 @@
# frozen_string_literal: true
class SubscriptionPolicy < ApplicationPolicy
def index?
admin?
end
end

View File

@ -0,0 +1,41 @@
# frozen_string_literal: true
class UserPolicy < ApplicationPolicy
def reset_password?
staff? && !record.staff?
end
def disable_2fa?
admin? && !record.staff?
end
def confirm?
staff? && !record.confirmed?
end
def enable?
admin?
end
def disable?
admin? && !record.admin?
end
def promote?
admin? && promoteable?
end
def demote?
admin? && !record.admin? && demoteable?
end
private
def promoteable?
!record.staff? || !record.admin?
end
def demoteable?
record.staff?
end
end

View File

@ -7,4 +7,4 @@
%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
= link_to t('admin.account_moderation_notes.delete'), admin_account_moderation_note_path(account_moderation_note), method: :delete if can?(:destroy, account_moderation_note)

View File

@ -17,16 +17,20 @@
- if @account.local?
%tr
%th= t('admin.accounts.email')
%td= @account.user_email
%td
= @account.user_email
- if @account.user_confirmed?
= fa_icon('check')
%tr
%th= t('admin.accounts.login_status')
%td
- if @account.user&.disabled?
= t('admin.accounts.disabled')
= table_link_to 'unlock', t('admin.accounts.enable'), enable_admin_account_path(@account.id), method: :post
= table_link_to 'unlock', t('admin.accounts.enable'), enable_admin_account_path(@account.id), method: :post if can?(:enable, @account.user)
- else
= t('admin.accounts.enabled')
= table_link_to 'lock', t('admin.accounts.disable'), disable_admin_account_path(@account.id), method: :post
= table_link_to 'lock', t('admin.accounts.disable'), disable_admin_account_path(@account.id), method: :post if can?(:disable, @account.user)
%tr
%th= t('admin.accounts.most_recent_ip')
%td= @account.user_current_sign_in_ip
@ -71,28 +75,28 @@
%div{ style: 'overflow: hidden' }
%div{ style: 'float: right' }
- if @account.local?
= link_to t('admin.accounts.reset_password'), admin_account_reset_path(@account.id), method: :create, class: 'button'
= link_to t('admin.accounts.reset_password'), admin_account_reset_path(@account.id), method: :create, class: 'button' if can?(:reset_password, @account.user)
- if @account.user&.otp_required_for_login?
= link_to t('admin.accounts.disable_two_factor_authentication'), admin_user_two_factor_authentication_path(@account.user.id), method: :delete, class: 'button'
= link_to t('admin.accounts.disable_two_factor_authentication'), admin_user_two_factor_authentication_path(@account.user.id), method: :delete, class: 'button' if can?(:disable_2fa, @account.user)
- unless @account.memorial?
= link_to t('admin.accounts.memorialize'), memorialize_admin_account_path(@account.id), method: :post, data: { confirm: t('admin.accounts.are_you_sure') }, class: 'button'
= link_to t('admin.accounts.memorialize'), memorialize_admin_account_path(@account.id), method: :post, data: { confirm: t('admin.accounts.are_you_sure') }, class: 'button' if can?(:memorialize, @account)
- else
= link_to t('admin.accounts.redownload'), redownload_admin_account_path(@account.id), method: :post, class: 'button'
= link_to t('admin.accounts.redownload'), redownload_admin_account_path(@account.id), method: :post, class: 'button' if can?(:redownload, @account)
%div{ style: 'float: left' }
- if @account.silenced?
= link_to t('admin.accounts.undo_silenced'), admin_account_silence_path(@account.id), method: :delete, class: 'button'
= link_to t('admin.accounts.undo_silenced'), admin_account_silence_path(@account.id), method: :delete, class: 'button' if can?(:unsilence, @account)
- else
= link_to t('admin.accounts.silence'), admin_account_silence_path(@account.id), method: :post, class: 'button'
= link_to t('admin.accounts.silence'), admin_account_silence_path(@account.id), method: :post, class: 'button' if can?(:silence, @account)
- if @account.local?
- unless @account.user_confirmed?
= link_to t('admin.accounts.confirm'), admin_account_confirmation_path(@account.id), method: :post, class: 'button'
= link_to t('admin.accounts.confirm'), admin_account_confirmation_path(@account.id), method: :post, class: 'button' if can?(:confirm, @account.user)
- if @account.suspended?
= link_to t('admin.accounts.undo_suspension'), admin_account_suspension_path(@account.id), method: :delete, class: 'button'
= link_to t('admin.accounts.undo_suspension'), admin_account_suspension_path(@account.id), method: :delete, class: 'button' if can?(:unsuspend, @account)
- else
= link_to t('admin.accounts.perform_full_suspension'), admin_account_suspension_path(@account.id), method: :post, data: { confirm: t('admin.accounts.are_you_sure') }, class: 'button'
= link_to t('admin.accounts.perform_full_suspension'), admin_account_suspension_path(@account.id), method: :post, data: { confirm: t('admin.accounts.are_you_sure') }, class: 'button' if can?(:suspend, @account)
- unless @account.local?
%hr
@ -118,9 +122,9 @@
%div{ style: 'overflow: hidden' }
%div{ style: 'float: right' }
= link_to @account.subscribed? ? t('admin.accounts.resubscribe') : t('admin.accounts.subscribe'), subscribe_admin_account_path(@account.id), method: :post, class: 'button'
= link_to @account.subscribed? ? t('admin.accounts.resubscribe') : t('admin.accounts.subscribe'), subscribe_admin_account_path(@account.id), method: :post, class: 'button' if can?(:subscribe, @account)
- if @account.subscribed?
= link_to t('admin.accounts.unsubscribe'), unsubscribe_admin_account_path(@account.id), method: :post, class: 'button negative'
= link_to t('admin.accounts.unsubscribe'), unsubscribe_admin_account_path(@account.id), method: :post, class: 'button negative' if can?(:unsubscribe, @account)
%hr
%h3 ActivityPub
@ -141,6 +145,20 @@
%th= t('admin.accounts.followers_url')
%td= link_to @account.followers_url, @account.followers_url
- else
%hr
.table-wrapper
%table.table
%tbody
%tr
%th= t('admin.accounts.role')
%td
= t("admin.accounts.roles.#{@account.user&.role}")
%td<
= table_link_to 'angle-double-up', t('admin.accounts.promote'), promote_admin_account_role_path(@account.id), method: :post, data: { confirm: t('admin.accounts.are_you_sure') } if can?(:promote, @account.user)
= table_link_to 'angle-double-down', t('admin.accounts.demote'), demote_admin_account_role_path(@account.id), method: :post, data: { confirm: t('admin.accounts.are_you_sure') } if can?(:demote, @account.user)
%hr
%h3= t('admin.accounts.moderation_notes')

View File

@ -46,6 +46,7 @@ ignore_missing:
- 'terms.body_html'
- 'application_mailer.salutation'
- 'errors.500'
ignore_unused:
- 'activemodel.errors.*'
- 'activerecord.attributes.*'
@ -58,3 +59,4 @@ ignore_unused:
- 'errors.messages.*'
- 'activerecord.errors.models.doorkeeper/*'
- 'errors.429'
- 'admin.accounts.roles.*'

View File

@ -62,6 +62,7 @@ en:
by_domain: Domain
confirm: Confirm
confirmed: Confirmed
demote: Demote
disable: Disable
disable_two_factor_authentication: Disable 2FA
disabled: Disabled
@ -101,6 +102,7 @@ en:
outbox_url: Outbox URL
perform_full_suspension: Perform full suspension
profile_url: Profile URL
promote: Promote
protocol: Protocol
public: Public
push_subscription_expires: PuSH subscription expires
@ -108,6 +110,11 @@ en:
reset: Reset
reset_password: Reset password
resubscribe: Resubscribe
role: Permissions
roles:
admin: Administrator
moderator: Moderator
user: User
salmon_url: Salmon URL
search: Search
shared_inbox_url: Shared Inbox URL

View File

@ -20,16 +20,16 @@ SimpleNavigation::Configuration.run do |navigation|
development.item :your_apps, safe_join([fa_icon('list fw'), t('settings.your_apps')]), settings_applications_url, highlights_on: %r{/settings/applications}
end
primary.item :admin, safe_join([fa_icon('cogs fw'), t('admin.title')]), admin_reports_url, if: proc { current_user.admin? } do |admin|
primary.item :admin, safe_join([fa_icon('cogs fw'), t('admin.title')]), admin_reports_url, if: proc { current_user.staff? } do |admin|
admin.item :reports, safe_join([fa_icon('flag fw'), t('admin.reports.title')]), admin_reports_url, highlights_on: %r{/admin/reports}
admin.item :accounts, safe_join([fa_icon('users fw'), t('admin.accounts.title')]), admin_accounts_url, highlights_on: %r{/admin/accounts}
admin.item :instances, safe_join([fa_icon('cloud fw'), t('admin.instances.title')]), admin_instances_url, highlights_on: %r{/admin/instances}
admin.item :subscriptions, safe_join([fa_icon('paper-plane-o fw'), t('admin.subscriptions.title')]), admin_subscriptions_url
admin.item :domain_blocks, safe_join([fa_icon('lock fw'), t('admin.domain_blocks.title')]), admin_domain_blocks_url, highlights_on: %r{/admin/domain_blocks}
admin.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}
admin.item :sidekiq, safe_join([fa_icon('diamond fw'), 'Sidekiq']), sidekiq_url, link_html: { target: 'sidekiq' }
admin.item :pghero, safe_join([fa_icon('database fw'), 'PgHero']), pghero_url, link_html: { target: 'pghero' }
admin.item :settings, safe_join([fa_icon('cogs fw'), t('admin.settings.title')]), edit_admin_settings_url
admin.item :instances, safe_join([fa_icon('cloud fw'), t('admin.instances.title')]), admin_instances_url, highlights_on: %r{/admin/instances}, if: -> { current_user.admin? }
admin.item :subscriptions, safe_join([fa_icon('paper-plane-o fw'), t('admin.subscriptions.title')]), admin_subscriptions_url, if: -> { current_user.admin? }
admin.item :domain_blocks, safe_join([fa_icon('lock fw'), t('admin.domain_blocks.title')]), admin_domain_blocks_url, highlights_on: %r{/admin/domain_blocks}, if: -> { current_user.admin? }
admin.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? }
admin.item :sidekiq, safe_join([fa_icon('diamond fw'), 'Sidekiq']), sidekiq_url, link_html: { target: 'sidekiq' }, if: -> { current_user.admin? }
admin.item :pghero, safe_join([fa_icon('database fw'), 'PgHero']), pghero_url, link_html: { target: 'pghero' }, if: -> { current_user.admin? }
admin.item :settings, safe_join([fa_icon('cogs fw'), t('admin.settings.title')]), edit_admin_settings_url, if: -> { current_user.admin? }
admin.item :custom_emojis, safe_join([fa_icon('smile-o fw'), t('admin.custom_emojis.title')]), admin_custom_emojis_url, highlights_on: %r{/admin/custom_emojis}
end

View File

@ -137,6 +137,13 @@ Rails.application.routes.draw do
resource :suspension, only: [:create, :destroy]
resource :confirmation, only: [:create]
resources :statuses, only: [:index, :create, :update, :destroy]
resource :role do
member do
post :promote
post :demote
end
end
end
resources :users, only: [] do

View File

@ -0,0 +1,15 @@
require Rails.root.join('lib', 'mastodon', 'migration_helpers')
class AddModeratorToAccounts < ActiveRecord::Migration[5.1]
include Mastodon::MigrationHelpers
disable_ddl_transaction!
def up
safety_assured { add_column_with_default :users, :moderator, :bool, default: false }
end
def down
remove_column :users, :moderator
end
end

View File

@ -10,7 +10,7 @@
#
# It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema.define(version: 20171107143624) do
ActiveRecord::Schema.define(version: 20171109012327) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
@ -437,6 +437,7 @@ ActiveRecord::Schema.define(version: 20171107143624) do
t.string "filtered_languages", default: [], null: false, array: true
t.bigint "account_id", null: false
t.boolean "disabled", default: false, null: false
t.boolean "moderator", default: false, null: false
t.index ["account_id"], name: "index_users_on_account_id"
t.index ["confirmation_token"], name: "index_users_on_confirmation_token", unique: true
t.index ["email"], name: "index_users_on_email", unique: true

View File

@ -10,6 +10,7 @@ namespace :mastodon do
desc 'Turn a user into an admin, identified by the USERNAME environment variable'
task make_admin: :environment do
include RoutingHelper
account_username = ENV.fetch('USERNAME')
user = User.joins(:account).where(accounts: { username: account_username })
@ -17,7 +18,33 @@ namespace :mastodon do
user.update(admin: true)
puts "Congrats! #{account_username} is now an admin. \\o/\nNavigate to #{edit_admin_settings_url} to get started"
else
puts "User could not be found; please make sure an Account with the `#{account_username}` username exists."
puts "User could not be found; please make sure an account with the `#{account_username}` username exists."
end
end
desc 'Turn a user into a moderator, identified by the USERNAME environment variable'
task make_mod: :environment do
account_username = ENV.fetch('USERNAME')
user = User.joins(:account).where(accounts: { username: account_username })
if user.present?
user.update(moderator: true)
puts "Congrats! #{account_username} is now a moderator \\o/"
else
puts "User could not be found; please make sure an account with the `#{account_username}` username exists."
end
end
desc 'Remove admin and moderator privileges from user identified by the USERNAME environment variable'
task revoke_staff: :environment do
account_username = ENV.fetch('USERNAME')
user = User.joins(:account).where(accounts: { username: account_username })
if user.present?
user.update(moderator: false, admin: false)
puts "#{account_username} is no longer admin or moderator."
else
puts "User could not be found; please make sure an account with the `#{account_username}` username exists."
end
end