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
main
Eugen Rochko 7 years ago committed by GitHub
parent 2b1190065c
commit 7bb8b0b2fc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -1,31 +1,41 @@
# frozen_string_literal: true # frozen_string_literal: true
class Admin::AccountModerationNotesController < Admin::BaseController module Admin
def create class AccountModerationNotesController < BaseController
@account_moderation_note = current_account.account_moderation_notes.new(resource_params) before_action :set_account_moderation_note, only: [:destroy]
if @account_moderation_note.save
@target_account = @account_moderation_note.target_account def create
redirect_to admin_account_path(@target_account.id), notice: I18n.t('admin.account_moderation_notes.created_msg') authorize AccountModerationNote, :create?
else
@account = @account_moderation_note.target_account @account_moderation_note = current_account.account_moderation_notes.new(resource_params)
@moderation_notes = @account.targeted_moderation_notes.latest
render template: 'admin/accounts/show' if @account_moderation_note.save
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 end
end
def destroy def destroy
@account_moderation_note = AccountModerationNote.find(params[:id]) authorize @account_moderation_note, :destroy?
@target_account = @account_moderation_note.target_account @account_moderation_note.destroy
@account_moderation_note.destroy redirect_to admin_account_path(@account_moderation_note.target_account_id), notice: I18n.t('admin.account_moderation_notes.destroyed_msg')
redirect_to admin_account_path(@target_account.id), notice: I18n.t('admin.account_moderation_notes.destroyed_msg') end
end
private private
def resource_params def resource_params
params.require(:account_moderation_note).permit( params.require(:account_moderation_note).permit(
:content, :content,
:target_account_id :target_account_id
) )
end
def set_account_moderation_note
@account_moderation_note = AccountModerationNote.find(params[:id])
end
end end
end end

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

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

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

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

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

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

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

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

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

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

@ -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

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

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

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

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

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

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

@ -19,7 +19,7 @@ class Api::V1::ReportsController < Api::BaseController
comment: report_params[:comment] 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 render json: @report, serializer: REST::ReportSerializer
end end

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

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

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

@ -32,6 +32,7 @@
# filtered_languages :string default([]), not null, is an Array # filtered_languages :string default([]), not null, is an Array
# account_id :integer not null # account_id :integer not null
# disabled :boolean default(FALSE), not null # disabled :boolean default(FALSE), not null
# moderator :boolean default(FALSE), not null
# #
class User < ApplicationRecord class User < ApplicationRecord
@ -53,8 +54,10 @@ class User < ApplicationRecord
validates :locale, inclusion: I18n.available_locales.map(&:to_s), if: :locale? validates :locale, inclusion: I18n.available_locales.map(&:to_s), if: :locale?
validates_with BlacklistedEmailValidator, if: :email_changed? validates_with BlacklistedEmailValidator, if: :email_changed?
scope :recent, -> { order(id: :desc) } scope :recent, -> { order(id: :desc) }
scope :admins, -> { where(admin: true) } scope :admins, -> { where(admin: true) }
scope :moderators, -> { where(moderator: true) }
scope :staff, -> { admins.or(moderators) }
scope :confirmed, -> { where.not(confirmed_at: nil) } scope :confirmed, -> { where.not(confirmed_at: nil) }
scope :inactive, -> { where(arel_table[:current_sign_in_at].lt(ACTIVE_DURATION.ago)) } 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 }) } 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? confirmed_at.present?
end end
def staff?
admin? || moderator?
end
def role
if admin?
'admin'
elsif moderator?
'moderator'
else
'user'
end
end
def disable! def disable!
update!(disabled: true, update!(disabled: true,
last_sign_in_at: current_sign_in_at, last_sign_in_at: current_sign_in_at,
@ -84,6 +101,27 @@ class User < ApplicationRecord
update!(disabled: false) update!(disabled: false)
end 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! def disable_two_factor!
self.otp_required_for_login = false self.otp_required_for_login = false
otp_backup_codes&.clear otp_backup_codes&.clear

@ -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

@ -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

@ -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

@ -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

@ -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

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

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

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

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

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

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

@ -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

@ -7,4 +7,4 @@
%time.formatted{ datetime: account_moderation_note.created_at.iso8601, title: l(account_moderation_note.created_at) } %time.formatted{ datetime: account_moderation_note.created_at.iso8601, title: l(account_moderation_note.created_at) }
= l account_moderation_note.created_at = l account_moderation_note.created_at
%td %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)

@ -17,16 +17,20 @@
- if @account.local? - if @account.local?
%tr %tr
%th= t('admin.accounts.email') %th= t('admin.accounts.email')
%td= @account.user_email %td
= @account.user_email
- if @account.user_confirmed?
= fa_icon('check')
%tr %tr
%th= t('admin.accounts.login_status') %th= t('admin.accounts.login_status')
%td %td
- if @account.user&.disabled? - if @account.user&.disabled?
= t('admin.accounts.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 - else
= t('admin.accounts.enabled') = 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 %tr
%th= t('admin.accounts.most_recent_ip') %th= t('admin.accounts.most_recent_ip')
%td= @account.user_current_sign_in_ip %td= @account.user_current_sign_in_ip
@ -71,28 +75,28 @@
%div{ style: 'overflow: hidden' } %div{ style: 'overflow: hidden' }
%div{ style: 'float: right' } %div{ style: 'float: right' }
- if @account.local? - 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? - 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? - 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 - 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' } %div{ style: 'float: left' }
- if @account.silenced? - 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 - 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? - if @account.local?
- unless @account.user_confirmed? - 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? - 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 - 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? - unless @account.local?
%hr %hr
@ -118,9 +122,9 @@
%div{ style: 'overflow: hidden' } %div{ style: 'overflow: hidden' }
%div{ style: 'float: right' } %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? - 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 %hr
%h3 ActivityPub %h3 ActivityPub
@ -141,6 +145,20 @@
%th= t('admin.accounts.followers_url') %th= t('admin.accounts.followers_url')
%td= link_to @account.followers_url, @account.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 %hr
%h3= t('admin.accounts.moderation_notes') %h3= t('admin.accounts.moderation_notes')

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

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

@ -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} development.item :your_apps, safe_join([fa_icon('list fw'), t('settings.your_apps')]), settings_applications_url, highlights_on: %r{/settings/applications}
end 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 :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 :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 :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 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} 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} 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' } 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' } 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 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} 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 end

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

@ -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

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

@ -10,14 +10,41 @@ namespace :mastodon do
desc 'Turn a user into an admin, identified by the USERNAME environment variable' desc 'Turn a user into an admin, identified by the USERNAME environment variable'
task make_admin: :environment do task make_admin: :environment do
include RoutingHelper include RoutingHelper
account_username = ENV.fetch('USERNAME') account_username = ENV.fetch('USERNAME')
user = User.joins(:account).where(accounts: { username: account_username }) user = User.joins(:account).where(accounts: { username: account_username })
if user.present? if user.present?
user.update(admin: true) user.update(admin: true)
puts "Congrats! #{account_username} is now an admin. \\o/\nNavigate to #{edit_admin_settings_url} to get started" puts "Congrats! #{account_username} is now an admin. \\o/\nNavigate to #{edit_admin_settings_url} to get started"
else 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
end end

Loading…
Cancel
Save