forked from treehouse/mastodon
Add customizable user roles (#18641)
* Add customizable user roles * Various fixes and improvements * Add migration for old settings and fix tootctl role managementrebase/4.0.0rc1
parent
1b4054256f
commit
44b2ee3485
|
@ -67,7 +67,7 @@ Lint/UselessAccessModifier:
|
|||
- class_methods
|
||||
|
||||
Metrics/AbcSize:
|
||||
Max: 100
|
||||
Max: 115
|
||||
Exclude:
|
||||
- 'lib/mastodon/*_cli.rb'
|
||||
|
||||
|
@ -84,7 +84,7 @@ Metrics/BlockNesting:
|
|||
|
||||
Metrics/ClassLength:
|
||||
CountComments: false
|
||||
Max: 400
|
||||
Max: 500
|
||||
Exclude:
|
||||
- 'lib/mastodon/*_cli.rb'
|
||||
|
||||
|
|
|
@ -5,11 +5,15 @@ module Admin
|
|||
before_action :set_account
|
||||
|
||||
def new
|
||||
authorize @account, :show?
|
||||
|
||||
@account_action = Admin::AccountAction.new(type: params[:type], report_id: params[:report_id], send_email_notification: true, include_statuses: true)
|
||||
@warning_presets = AccountWarningPreset.all
|
||||
end
|
||||
|
||||
def create
|
||||
authorize @account, :show?
|
||||
|
||||
account_action = Admin::AccountAction.new(resource_params)
|
||||
account_action.target_account = @account
|
||||
account_action.current_account = current_account
|
||||
|
|
|
@ -14,6 +14,8 @@ module Admin
|
|||
end
|
||||
|
||||
def batch
|
||||
authorize :account, :index?
|
||||
|
||||
@form = Form::AccountBatch.new(form_account_batch_params.merge(current_account: current_account, action: action_from_button))
|
||||
@form.save
|
||||
rescue ActionController::ParameterMissing
|
||||
|
|
|
@ -4,7 +4,10 @@ module Admin
|
|||
class ActionLogsController < BaseController
|
||||
before_action :set_action_logs
|
||||
|
||||
def index; end
|
||||
def index
|
||||
authorize :audit_log, :index?
|
||||
@auditable_accounts = Account.where(id: Admin::ActionLog.reorder(nil).select('distinct account_id')).select(:id, :username)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
|
|
|
@ -7,8 +7,8 @@ module Admin
|
|||
|
||||
layout 'admin'
|
||||
|
||||
before_action :require_staff!
|
||||
before_action :set_body_classes
|
||||
after_action :verify_authorized
|
||||
|
||||
private
|
||||
|
||||
|
|
|
@ -29,6 +29,8 @@ module Admin
|
|||
end
|
||||
|
||||
def batch
|
||||
authorize :custom_emoji, :index?
|
||||
|
||||
@form = Form::CustomEmojiBatch.new(form_custom_emoji_batch_params.merge(current_account: current_account, action: action_from_button))
|
||||
@form.save
|
||||
rescue ActionController::ParameterMissing
|
||||
|
|
|
@ -5,7 +5,9 @@ module Admin
|
|||
include Redisable
|
||||
|
||||
def index
|
||||
@system_checks = Admin::SystemCheck.perform
|
||||
authorize :dashboard, :index?
|
||||
|
||||
@system_checks = Admin::SystemCheck.perform(current_user)
|
||||
@time_period = (29.days.ago.to_date...Time.now.utc.to_date)
|
||||
@pending_users_count = User.pending.count
|
||||
@pending_reports_count = Report.unresolved.count
|
||||
|
|
|
@ -12,6 +12,8 @@ module Admin
|
|||
end
|
||||
|
||||
def batch
|
||||
authorize :email_domain_block, :index?
|
||||
|
||||
@form = Form::EmailDomainBlockBatch.new(form_email_domain_block_batch_params.merge(current_account: current_account, action: action_from_button))
|
||||
@form.save
|
||||
rescue ActionController::ParameterMissing
|
||||
|
|
|
@ -12,6 +12,8 @@ module Admin
|
|||
end
|
||||
|
||||
def update
|
||||
authorize :follow_recommendation, :show?
|
||||
|
||||
@form = Form::AccountBatch.new(form_account_batch_params.merge(current_account: current_account, action: action_from_button))
|
||||
@form.save
|
||||
rescue ActionController::ParameterMissing
|
||||
|
|
|
@ -29,6 +29,8 @@ module Admin
|
|||
end
|
||||
|
||||
def batch
|
||||
authorize :ip_block, :index?
|
||||
|
||||
@form = Form::IpBlockBatch.new(form_ip_block_batch_params.merge(current_account: current_account, action: action_from_button))
|
||||
@form.save
|
||||
rescue ActionController::ParameterMissing
|
||||
|
|
|
@ -7,7 +7,7 @@ module Admin
|
|||
PER_PAGE = 40
|
||||
|
||||
def index
|
||||
authorize :account, :index?
|
||||
authorize @account, :show?
|
||||
|
||||
@accounts = RelationshipFilter.new(@account, filter_params).results.includes(:account_stat, user: [:ips, :invite_request]).page(params[:page]).per(PER_PAGE)
|
||||
@form = Form::AccountBatch.new
|
||||
|
|
|
@ -2,20 +2,63 @@
|
|||
|
||||
module Admin
|
||||
class RolesController < BaseController
|
||||
before_action :set_user
|
||||
before_action :set_role, except: [:index, :new, :create]
|
||||
|
||||
def promote
|
||||
authorize @user, :promote?
|
||||
@user.promote!
|
||||
log_action :promote, @user
|
||||
redirect_to admin_account_path(@user.account_id)
|
||||
def index
|
||||
authorize :user_role, :index?
|
||||
|
||||
@roles = UserRole.order(position: :desc).page(params[:page])
|
||||
end
|
||||
|
||||
def demote
|
||||
authorize @user, :demote?
|
||||
@user.demote!
|
||||
log_action :demote, @user
|
||||
redirect_to admin_account_path(@user.account_id)
|
||||
def new
|
||||
authorize :user_role, :create?
|
||||
|
||||
@role = UserRole.new
|
||||
end
|
||||
|
||||
def create
|
||||
authorize :user_role, :create?
|
||||
|
||||
@role = UserRole.new(resource_params)
|
||||
@role.current_account = current_account
|
||||
|
||||
if @role.save
|
||||
redirect_to admin_roles_path
|
||||
else
|
||||
render :new
|
||||
end
|
||||
end
|
||||
|
||||
def edit
|
||||
authorize @role, :update?
|
||||
end
|
||||
|
||||
def update
|
||||
authorize @role, :update?
|
||||
|
||||
@role.current_account = current_account
|
||||
|
||||
if @role.update(resource_params)
|
||||
redirect_to admin_roles_path
|
||||
else
|
||||
render :edit
|
||||
end
|
||||
end
|
||||
|
||||
def destroy
|
||||
authorize @role, :destroy?
|
||||
@role.destroy!
|
||||
redirect_to admin_roles_path
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def set_role
|
||||
@role = UserRole.find(params[:id])
|
||||
end
|
||||
|
||||
def resource_params
|
||||
params.require(:user_role).permit(:name, :color, :highlighted, :position, permissions_as_keys: [])
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -14,6 +14,8 @@ module Admin
|
|||
end
|
||||
|
||||
def batch
|
||||
authorize :status, :index?
|
||||
|
||||
@status_batch_action = Admin::StatusBatchAction.new(admin_status_batch_action_params.merge(current_account: current_account, report_id: params[:report_id], type: action_from_button))
|
||||
@status_batch_action.save!
|
||||
rescue ActionController::ParameterMissing
|
||||
|
|
|
@ -1,20 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Admin
|
||||
class SubscriptionsController < BaseController
|
||||
def index
|
||||
authorize :subscription, :index?
|
||||
@subscriptions = ordered_subscriptions.page(requested_page)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def ordered_subscriptions
|
||||
Subscription.order(id: :desc).includes(:account)
|
||||
end
|
||||
|
||||
def requested_page
|
||||
params[:page].to_i
|
||||
end
|
||||
end
|
||||
end
|
|
@ -2,13 +2,15 @@
|
|||
|
||||
class Admin::Trends::Links::PreviewCardProvidersController < Admin::BaseController
|
||||
def index
|
||||
authorize :preview_card_provider, :index?
|
||||
authorize :preview_card_provider, :review?
|
||||
|
||||
@preview_card_providers = filtered_preview_card_providers.page(params[:page])
|
||||
@form = Trends::PreviewCardProviderBatch.new
|
||||
end
|
||||
|
||||
def batch
|
||||
authorize :preview_card_provider, :review?
|
||||
|
||||
@form = Trends::PreviewCardProviderBatch.new(trends_preview_card_provider_batch_params.merge(current_account: current_account, action: action_from_button))
|
||||
@form.save
|
||||
rescue ActionController::ParameterMissing
|
||||
|
|
|
@ -2,13 +2,15 @@
|
|||
|
||||
class Admin::Trends::LinksController < Admin::BaseController
|
||||
def index
|
||||
authorize :preview_card, :index?
|
||||
authorize :preview_card, :review?
|
||||
|
||||
@preview_cards = filtered_preview_cards.page(params[:page])
|
||||
@form = Trends::PreviewCardBatch.new
|
||||
end
|
||||
|
||||
def batch
|
||||
authorize :preview_card, :review?
|
||||
|
||||
@form = Trends::PreviewCardBatch.new(trends_preview_card_batch_params.merge(current_account: current_account, action: action_from_button))
|
||||
@form.save
|
||||
rescue ActionController::ParameterMissing
|
||||
|
|
|
@ -2,13 +2,15 @@
|
|||
|
||||
class Admin::Trends::StatusesController < Admin::BaseController
|
||||
def index
|
||||
authorize :status, :index?
|
||||
authorize :status, :review?
|
||||
|
||||
@statuses = filtered_statuses.page(params[:page])
|
||||
@form = Trends::StatusBatch.new
|
||||
end
|
||||
|
||||
def batch
|
||||
authorize :status, :review?
|
||||
|
||||
@form = Trends::StatusBatch.new(trends_status_batch_params.merge(current_account: current_account, action: action_from_button))
|
||||
@form.save
|
||||
rescue ActionController::ParameterMissing
|
||||
|
|
|
@ -2,13 +2,15 @@
|
|||
|
||||
class Admin::Trends::TagsController < Admin::BaseController
|
||||
def index
|
||||
authorize :tag, :index?
|
||||
authorize :tag, :review?
|
||||
|
||||
@tags = filtered_tags.page(params[:page])
|
||||
@form = Trends::TagBatch.new
|
||||
end
|
||||
|
||||
def batch
|
||||
authorize :tag, :review?
|
||||
|
||||
@form = Trends::TagBatch.new(trends_tag_batch_params.merge(current_account: current_account, action: action_from_button))
|
||||
@form.save
|
||||
rescue ActionController::ParameterMissing
|
||||
|
|
|
@ -0,0 +1,33 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Admin
|
||||
class Users::RolesController < BaseController
|
||||
before_action :set_user
|
||||
|
||||
def show
|
||||
authorize @user, :change_role?
|
||||
end
|
||||
|
||||
def update
|
||||
authorize @user, :change_role?
|
||||
|
||||
@user.current_account = current_account
|
||||
|
||||
if @user.update(resource_params)
|
||||
redirect_to admin_account_path(@user.account_id), notice: I18n.t('admin.accounts.change_role.changed_msg')
|
||||
else
|
||||
render :show
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def set_user
|
||||
@user = User.find(params[:user_id])
|
||||
end
|
||||
|
||||
def resource_params
|
||||
params.require(:user).permit(:role_id)
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,7 +1,7 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Admin
|
||||
class TwoFactorAuthenticationsController < BaseController
|
||||
class Users::TwoFactorAuthenticationsController < BaseController
|
||||
before_action :set_target_user
|
||||
|
||||
def destroy
|
|
@ -1,11 +1,16 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class Api::V1::Admin::AccountActionsController < Api::BaseController
|
||||
include Authorization
|
||||
|
||||
before_action -> { authorize_if_got_token! :'admin:write', :'admin:write:accounts' }
|
||||
before_action :require_staff!
|
||||
before_action :set_account
|
||||
|
||||
after_action :verify_authorized
|
||||
|
||||
def create
|
||||
authorize @account, :show?
|
||||
|
||||
account_action = Admin::AccountAction.new(resource_params)
|
||||
account_action.target_account = @account
|
||||
account_action.current_account = current_account
|
||||
|
|
|
@ -8,11 +8,11 @@ class Api::V1::Admin::AccountsController < Api::BaseController
|
|||
|
||||
before_action -> { authorize_if_got_token! :'admin:read', :'admin:read:accounts' }, only: [:index, :show]
|
||||
before_action -> { authorize_if_got_token! :'admin:write', :'admin:write:accounts' }, except: [:index, :show]
|
||||
before_action :require_staff!
|
||||
before_action :set_accounts, only: :index
|
||||
before_action :set_account, except: :index
|
||||
before_action :require_local_account!, only: [:enable, :approve, :reject]
|
||||
|
||||
after_action :verify_authorized
|
||||
after_action :insert_pagination_headers, only: :index
|
||||
|
||||
FILTER_PARAMS = %i(
|
||||
|
@ -119,7 +119,9 @@ class Api::V1::Admin::AccountsController < Api::BaseController
|
|||
translated_params[:status] = status.to_s if params[status].present?
|
||||
end
|
||||
|
||||
translated_params[:permissions] = 'staff' if params[:staff].present?
|
||||
if params[:staff].present?
|
||||
translated_params[:role_ids] = UserRole.that_can(:manage_reports).map(&:id)
|
||||
end
|
||||
|
||||
translated_params
|
||||
end
|
||||
|
|
|
@ -1,11 +1,15 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class Api::V1::Admin::DimensionsController < Api::BaseController
|
||||
include Authorization
|
||||
|
||||
before_action -> { authorize_if_got_token! :'admin:read' }
|
||||
before_action :require_staff!
|
||||
before_action :set_dimensions
|
||||
|
||||
after_action :verify_authorized
|
||||
|
||||
def create
|
||||
authorize :dashboard, :index?
|
||||
render json: @dimensions, each_serializer: REST::Admin::DimensionSerializer
|
||||
end
|
||||
|
||||
|
|
|
@ -8,10 +8,10 @@ class Api::V1::Admin::DomainAllowsController < Api::BaseController
|
|||
|
||||
before_action -> { authorize_if_got_token! :'admin:read', :'admin:read:domain_allows' }, only: [:index, :show]
|
||||
before_action -> { authorize_if_got_token! :'admin:write', :'admin:write:domain_allows' }, except: [:index, :show]
|
||||
before_action :require_staff!
|
||||
before_action :set_domain_allows, only: :index
|
||||
before_action :set_domain_allow, only: [:show, :destroy]
|
||||
|
||||
after_action :verify_authorized
|
||||
after_action :insert_pagination_headers, only: :index
|
||||
|
||||
PAGINATION_PARAMS = %i(limit).freeze
|
||||
|
|
|
@ -8,10 +8,10 @@ class Api::V1::Admin::DomainBlocksController < Api::BaseController
|
|||
|
||||
before_action -> { authorize_if_got_token! :'admin:read', :'admin:read:domain_blocks' }, only: [:index, :show]
|
||||
before_action -> { authorize_if_got_token! :'admin:write', :'admin:write:domain_blocks' }, except: [:index, :show]
|
||||
before_action :require_staff!
|
||||
before_action :set_domain_blocks, only: :index
|
||||
before_action :set_domain_block, only: [:show, :update, :destroy]
|
||||
|
||||
after_action :verify_authorized
|
||||
after_action :insert_pagination_headers, only: :index
|
||||
|
||||
PAGINATION_PARAMS = %i(limit).freeze
|
||||
|
|
|
@ -1,11 +1,15 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class Api::V1::Admin::MeasuresController < Api::BaseController
|
||||
include Authorization
|
||||
|
||||
before_action -> { authorize_if_got_token! :'admin:read' }
|
||||
before_action :require_staff!
|
||||
before_action :set_measures
|
||||
|
||||
after_action :verify_authorized
|
||||
|
||||
def create
|
||||
authorize :dashboard, :index?
|
||||
render json: @measures, each_serializer: REST::Admin::MeasureSerializer
|
||||
end
|
||||
|
||||
|
|
|
@ -8,10 +8,10 @@ class Api::V1::Admin::ReportsController < Api::BaseController
|
|||
|
||||
before_action -> { authorize_if_got_token! :'admin:read', :'admin:read:reports' }, only: [:index, :show]
|
||||
before_action -> { authorize_if_got_token! :'admin:write', :'admin:write:reports' }, except: [:index, :show]
|
||||
before_action :require_staff!
|
||||
before_action :set_reports, only: :index
|
||||
before_action :set_report, except: :index
|
||||
|
||||
after_action :verify_authorized
|
||||
after_action :insert_pagination_headers, only: :index
|
||||
|
||||
FILTER_PARAMS = %i(
|
||||
|
|
|
@ -1,11 +1,15 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class Api::V1::Admin::RetentionController < Api::BaseController
|
||||
include Authorization
|
||||
|
||||
before_action -> { authorize_if_got_token! :'admin:read' }
|
||||
before_action :require_staff!
|
||||
before_action :set_cohorts
|
||||
|
||||
after_action :verify_authorized
|
||||
|
||||
def create
|
||||
authorize :dashboard, :index?
|
||||
render json: @cohorts, each_serializer: REST::Admin::CohortSerializer
|
||||
end
|
||||
|
||||
|
|
|
@ -1,17 +1,19 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class Api::V1::Admin::Trends::LinksController < Api::BaseController
|
||||
class Api::V1::Admin::Trends::LinksController < Api::V1::Trends::LinksController
|
||||
before_action -> { authorize_if_got_token! :'admin:read' }
|
||||
before_action :require_staff!
|
||||
before_action :set_links
|
||||
|
||||
def index
|
||||
render json: @links, each_serializer: REST::Trends::LinkSerializer
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def set_links
|
||||
@links = Trends.links.query.limit(limit_param(10))
|
||||
def enabled?
|
||||
super || current_user&.can?(:manage_taxonomies)
|
||||
end
|
||||
|
||||
def links_from_trends
|
||||
if current_user&.can?(:manage_taxonomies)
|
||||
Trends.links.query
|
||||
else
|
||||
super
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,17 +1,19 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class Api::V1::Admin::Trends::StatusesController < Api::BaseController
|
||||
class Api::V1::Admin::Trends::StatusesController < Api::V1::Trends::StatusesController
|
||||
before_action -> { authorize_if_got_token! :'admin:read' }
|
||||
before_action :require_staff!
|
||||
before_action :set_statuses
|
||||
|
||||
def index
|
||||
render json: @statuses, each_serializer: REST::StatusSerializer
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def set_statuses
|
||||
@statuses = cache_collection(Trends.statuses.query.limit(limit_param(DEFAULT_STATUSES_LIMIT)), Status)
|
||||
def enabled?
|
||||
super || current_user&.can?(:manage_taxonomies)
|
||||
end
|
||||
|
||||
def statuses_from_trends
|
||||
if current_user&.can?(:manage_taxonomies)
|
||||
Trends.statuses.query
|
||||
else
|
||||
super
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,17 +1,19 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class Api::V1::Admin::Trends::TagsController < Api::BaseController
|
||||
class Api::V1::Admin::Trends::TagsController < Api::V1::Trends::TagsController
|
||||
before_action -> { authorize_if_got_token! :'admin:read' }
|
||||
before_action :require_staff!
|
||||
before_action :set_tags
|
||||
|
||||
def index
|
||||
render json: @tags, each_serializer: REST::Admin::TagSerializer
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def set_tags
|
||||
@tags = Trends.tags.query.limit(limit_param(10))
|
||||
def enabled?
|
||||
super || current_user&.can?(:manage_taxonomies)
|
||||
end
|
||||
|
||||
def tags_from_trends
|
||||
if current_user&.can?(:manage_taxonomies)
|
||||
Trends.tags.query
|
||||
else
|
||||
super
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -13,10 +13,14 @@ class Api::V1::Trends::LinksController < Api::BaseController
|
|||
|
||||
private
|
||||
|
||||
def enabled?
|
||||
Setting.trends
|
||||
end
|
||||
|
||||
def set_links
|
||||
@links = begin
|
||||
if Setting.trends
|
||||
links_from_trends
|
||||
if enabled?
|
||||
links_from_trends.offset(offset_param).limit(limit_param(DEFAULT_LINKS_LIMIT))
|
||||
else
|
||||
[]
|
||||
end
|
||||
|
@ -24,7 +28,7 @@ class Api::V1::Trends::LinksController < Api::BaseController
|
|||
end
|
||||
|
||||
def links_from_trends
|
||||
Trends.links.query.allowed.in_locale(content_locale).offset(offset_param).limit(limit_param(DEFAULT_LINKS_LIMIT))
|
||||
Trends.links.query.allowed.in_locale(content_locale)
|
||||
end
|
||||
|
||||
def insert_pagination_headers
|
||||
|
|
|
@ -11,10 +11,14 @@ class Api::V1::Trends::StatusesController < Api::BaseController
|
|||
|
||||
private
|
||||
|
||||
def enabled?
|
||||
Setting.trends
|
||||
end
|
||||
|
||||
def set_statuses
|
||||
@statuses = begin
|
||||
if Setting.trends
|
||||
cache_collection(statuses_from_trends, Status)
|
||||
if enabled?
|
||||
cache_collection(statuses_from_trends.offset(offset_param).limit(limit_param(DEFAULT_STATUSES_LIMIT)), Status)
|
||||
else
|
||||
[]
|
||||
end
|
||||
|
@ -24,7 +28,7 @@ class Api::V1::Trends::StatusesController < Api::BaseController
|
|||
def statuses_from_trends
|
||||
scope = Trends.statuses.query.allowed.in_locale(content_locale)
|
||||
scope = scope.filtered_for(current_account) if user_signed_in?
|
||||
scope.offset(offset_param).limit(limit_param(DEFAULT_STATUSES_LIMIT))
|
||||
scope
|
||||
end
|
||||
|
||||
def insert_pagination_headers
|
||||
|
|
|
@ -13,16 +13,24 @@ class Api::V1::Trends::TagsController < Api::BaseController
|
|||
|
||||
private
|
||||
|
||||
def enabled?
|
||||
Setting.trends
|
||||
end
|
||||
|
||||
def set_tags
|
||||
@tags = begin
|
||||
if Setting.trends
|
||||
Trends.tags.query.allowed.offset(offset_param).limit(limit_param(DEFAULT_TAGS_LIMIT))
|
||||
if enabled?
|
||||
tags_from_trends.offset(offset_param).limit(limit_param(DEFAULT_TAGS_LIMIT))
|
||||
else
|
||||
[]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def tags_from_trends
|
||||
Trends.tags.query.allowed
|
||||
end
|
||||
|
||||
def insert_pagination_headers
|
||||
set_pagination_headers(next_path, prev_path)
|
||||
end
|
||||
|
|
|
@ -11,6 +11,7 @@ class Api::V2::Admin::AccountsController < Api::V1::Admin::AccountsController
|
|||
email
|
||||
ip
|
||||
invited_by
|
||||
role_ids
|
||||
).freeze
|
||||
|
||||
PAGINATION_PARAMS = (%i(limit) + FILTER_PARAMS).freeze
|
||||
|
@ -18,7 +19,17 @@ class Api::V2::Admin::AccountsController < Api::V1::Admin::AccountsController
|
|||
private
|
||||
|
||||
def filtered_accounts
|
||||
AccountFilter.new(filter_params).results
|
||||
AccountFilter.new(translated_filter_params).results
|
||||
end
|
||||
|
||||
def translated_filter_params
|
||||
translated_params = filter_params.slice(*AccountFilter::KEYS)
|
||||
|
||||
if params[:permissions] == 'staff'
|
||||
translated_params[:role_ids] = UserRole.that_can(:manage_reports).map(&:id)
|
||||
end
|
||||
|
||||
translated_params
|
||||
end
|
||||
|
||||
def filter_params
|
||||
|
|
|
@ -56,14 +56,6 @@ class ApplicationController < ActionController::Base
|
|||
store_location_for(:user, request.url) unless [:json, :rss].include?(request.format&.to_sym)
|
||||
end
|
||||
|
||||
def require_admin!
|
||||
forbidden unless current_user&.admin?
|
||||
end
|
||||
|
||||
def require_staff!
|
||||
forbidden unless current_user&.staff?
|
||||
end
|
||||
|
||||
def require_functional!
|
||||
redirect_to edit_user_registration_path unless current_user.functional?
|
||||
end
|
||||
|
|
|
@ -13,6 +13,6 @@ class CustomCssController < ApplicationController
|
|||
def show
|
||||
expires_in 3.minutes, public: true
|
||||
request.session_options[:skip] = true
|
||||
render plain: Setting.custom_css || '', content_type: 'text/css'
|
||||
render content_type: 'text/css'
|
||||
end
|
||||
end
|
||||
|
|
|
@ -61,21 +61,13 @@ module AccountsHelper
|
|||
end
|
||||
end
|
||||
|
||||
def account_badge(account, all: false)
|
||||
def account_badge(account)
|
||||
if account.bot?
|
||||
content_tag(:div, content_tag(:div, t('accounts.roles.bot'), class: 'account-role bot'), class: 'roles')
|
||||
elsif account.group?
|
||||
content_tag(:div, content_tag(:div, t('accounts.roles.group'), class: 'account-role group'), class: 'roles')
|
||||
elsif (Setting.show_staff_badge && account.user_staff?) || all
|
||||
content_tag(:div, class: 'roles') do
|
||||
if all && !account.user_staff?
|
||||
content_tag(:div, t('admin.accounts.roles.user'), class: 'account-role')
|
||||
elsif account.user_admin?
|
||||
content_tag(:div, t('accounts.roles.admin'), class: 'account-role admin')
|
||||
elsif account.user_moderator?
|
||||
content_tag(:div, t('accounts.roles.moderator'), class: 'account-role moderator')
|
||||
end
|
||||
end
|
||||
elsif account.user_role&.highlighted?
|
||||
content_tag(:div, content_tag(:div, account.user_role.name, class: "account-role user-role-#{account.user_role.id}"), class: 'roles')
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -6,8 +6,9 @@ import IconButton from './icon_button';
|
|||
import DropdownMenuContainer from '../containers/dropdown_menu_container';
|
||||
import { defineMessages, injectIntl } from 'react-intl';
|
||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||
import { me, isStaff } from '../initial_state';
|
||||
import { me } from '../initial_state';
|
||||
import classNames from 'classnames';
|
||||
import { PERMISSION_MANAGE_USERS } from 'mastodon/permissions';
|
||||
|
||||
const messages = defineMessages({
|
||||
delete: { id: 'status.delete', defaultMessage: 'Delete' },
|
||||
|
@ -55,6 +56,7 @@ class StatusActionBar extends ImmutablePureComponent {
|
|||
|
||||
static contextTypes = {
|
||||
router: PropTypes.object,
|
||||
identity: PropTypes.object,
|
||||
};
|
||||
|
||||
static propTypes = {
|
||||
|
@ -306,7 +308,7 @@ class StatusActionBar extends ImmutablePureComponent {
|
|||
}
|
||||
}
|
||||
|
||||
if (isStaff) {
|
||||
if ((this.context.identity.permissions & PERMISSION_MANAGE_USERS) === PERMISSION_MANAGE_USERS) {
|
||||
menu.push(null);
|
||||
menu.push({ text: intl.formatMessage(messages.admin_account, { name: account.get('username') }), href: `/admin/accounts/${status.getIn(['account', 'id'])}` });
|
||||
menu.push({ text: intl.formatMessage(messages.admin_status), href: `/admin/accounts/${status.getIn(['account', 'id'])}/statuses?id=${status.get('id')}` });
|
||||
|
|
|
@ -26,6 +26,7 @@ const createIdentityContext = state => ({
|
|||
signedIn: !!state.meta.me,
|
||||
accountId: state.meta.me,
|
||||
accessToken: state.meta.access_token,
|
||||
permissions: state.role.permissions,
|
||||
});
|
||||
|
||||
export default class Mastodon extends React.PureComponent {
|
||||
|
|
|
@ -4,7 +4,7 @@ import PropTypes from 'prop-types';
|
|||
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
|
||||
import Button from 'mastodon/components/button';
|
||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||
import { autoPlayGif, me, isStaff } from 'mastodon/initial_state';
|
||||
import { autoPlayGif, me } from 'mastodon/initial_state';
|
||||
import classNames from 'classnames';
|
||||
import Icon from 'mastodon/components/icon';
|
||||
import IconButton from 'mastodon/components/icon_button';
|
||||
|
@ -14,6 +14,7 @@ import ShortNumber from 'mastodon/components/short_number';
|
|||
import { NavLink } from 'react-router-dom';
|
||||
import DropdownMenuContainer from 'mastodon/containers/dropdown_menu_container';
|
||||
import AccountNoteContainer from '../containers/account_note_container';
|
||||
import { PERMISSION_MANAGE_USERS } from 'mastodon/permissions';
|
||||
|
||||
const messages = defineMessages({
|
||||
unfollow: { id: 'account.unfollow', defaultMessage: 'Unfollow' },
|
||||
|
@ -64,6 +65,10 @@ const dateFormatOptions = {
|
|||
export default @injectIntl
|
||||
class Header extends ImmutablePureComponent {
|
||||
|
||||
static contextTypes = {
|
||||
identity: PropTypes.object,
|
||||
};
|
||||
|
||||
static propTypes = {
|
||||
account: ImmutablePropTypes.map,
|
||||
identity_props: ImmutablePropTypes.list,
|
||||
|
@ -241,7 +246,7 @@ class Header extends ImmutablePureComponent {
|
|||
}
|
||||
}
|
||||
|
||||
if (account.get('id') !== me && isStaff) {
|
||||
if (account.get('id') !== me && (this.context.identity.permissions & PERMISSION_MANAGE_USERS) === PERMISSION_MANAGE_USERS) {
|
||||
menu.push(null);
|
||||
menu.push({ text: intl.formatMessage(messages.admin_account, { name: account.get('username') }), href: `/admin/accounts/${account.get('id')}` });
|
||||
}
|
||||
|
|
|
@ -5,10 +5,14 @@ import { FormattedMessage } from 'react-intl';
|
|||
import ClearColumnButton from './clear_column_button';
|
||||
import GrantPermissionButton from './grant_permission_button';
|
||||
import SettingToggle from './setting_toggle';
|
||||
import { isStaff } from 'mastodon/initial_state';
|
||||
import { PERMISSION_MANAGE_USERS, PERMISSION_MANAGE_REPORTS } from 'mastodon/permissions';
|
||||
|
||||
export default class ColumnSettings extends React.PureComponent {
|
||||
|
||||
static contextTypes = {
|
||||
identity: PropTypes.object,
|
||||
};
|
||||
|
||||
static propTypes = {
|
||||
settings: ImmutablePropTypes.map.isRequired,
|
||||
pushSettings: ImmutablePropTypes.map.isRequired,
|
||||
|
@ -166,7 +170,7 @@ export default class ColumnSettings extends React.PureComponent {
|
|||
</div>
|
||||
</div>
|
||||
|
||||
{isStaff && (
|
||||
{(this.context.identity.permissions & PERMISSION_MANAGE_USERS === PERMISSION_MANAGE_USERS) && (
|
||||
<div role='group' aria-labelledby='notifications-admin-sign-up'>
|
||||
<span id='notifications-status' className='column-settings__section'><FormattedMessage id='notifications.column_settings.admin.sign_up' defaultMessage='New sign-ups:' /></span>
|
||||
|
||||
|
@ -179,7 +183,7 @@ export default class ColumnSettings extends React.PureComponent {
|
|||
</div>
|
||||
)}
|
||||
|
||||
{isStaff && (
|
||||
{(this.context.identity.permissions & PERMISSION_MANAGE_REPORTS === PERMISSION_MANAGE_REPORTS) && (
|
||||
<div role='group' aria-labelledby='notifications-admin-report'>
|
||||
<span id='notifications-status' className='column-settings__section'><FormattedMessage id='notifications.column_settings.admin.report' defaultMessage='New reports:' /></span>
|
||||
|
||||
|
|
|
@ -5,8 +5,9 @@ import IconButton from '../../../components/icon_button';
|
|||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
import DropdownMenuContainer from '../../../containers/dropdown_menu_container';
|
||||
import { defineMessages, injectIntl } from 'react-intl';
|
||||
import { me, isStaff } from '../../../initial_state';
|
||||
import { me } from '../../../initial_state';
|
||||
import classNames from 'classnames';
|
||||
import { PERMISSION_MANAGE_USERS } from 'mastodon/permissions';
|
||||
|
||||
const messages = defineMessages({
|
||||
delete: { id: 'status.delete', defaultMessage: 'Delete' },
|
||||
|
@ -50,6 +51,7 @@ class ActionBar extends React.PureComponent {
|
|||
|
||||
static contextTypes = {
|
||||
router: PropTypes.object,
|
||||
identity: PropTypes.object,
|
||||
};
|
||||
|
||||
static propTypes = {
|
||||
|
@ -248,7 +250,7 @@ class ActionBar extends React.PureComponent {
|
|||
}
|
||||
}
|
||||
|
||||
if (isStaff) {
|
||||
if ((this.context.identity.permissions & PERMISSION_MANAGE_USERS) === PERMISSION_MANAGE_USERS) {
|
||||
menu.push(null);
|
||||
menu.push({ text: intl.formatMessage(messages.admin_account, { name: status.getIn(['account', 'username']) }), href: `/admin/accounts/${status.getIn(['account', 'id'])}` });
|
||||
menu.push({ text: intl.formatMessage(messages.admin_status), href: `/admin/accounts/${status.getIn(['account', 'id'])}/statuses?id=${status.get('id')}` });
|
||||
|
|
|
@ -3,9 +3,10 @@ import React from 'react';
|
|||
import PropTypes from 'prop-types';
|
||||
import { FormattedMessage, defineMessages, injectIntl } from 'react-intl';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { invitesEnabled, limitedFederationMode, version, repository, source_url, profile_directory as profileDirectory } from 'mastodon/initial_state';
|
||||
import { limitedFederationMode, version, repository, source_url, profile_directory as profileDirectory } from 'mastodon/initial_state';
|
||||
import { logOut } from 'mastodon/utils/log_out';
|
||||
import { openModal } from 'mastodon/actions/modal';
|
||||
import { PERMISSION_INVITE_USERS } from 'mastodon/permissions';
|
||||
|
||||
const messages = defineMessages({
|
||||
logoutMessage: { id: 'confirmations.logout.message', defaultMessage: 'Are you sure you want to log out?' },
|
||||
|
@ -27,6 +28,10 @@ export default @injectIntl
|
|||
@connect(null, mapDispatchToProps)
|
||||
class LinkFooter extends React.PureComponent {
|
||||
|
||||
static contextTypes = {
|
||||
identity: PropTypes.object,
|
||||
};
|
||||
|
||||
static propTypes = {
|
||||
withHotkeys: PropTypes.bool,
|
||||
onLogout: PropTypes.func.isRequired,
|
||||
|
@ -48,7 +53,7 @@ class LinkFooter extends React.PureComponent {
|
|||
return (
|
||||
<div className='getting-started__footer'>
|
||||
<ul>
|
||||
{invitesEnabled && <li><a href='/invites' target='_blank'><FormattedMessage id='getting_started.invite' defaultMessage='Invite people' /></a> · </li>}
|
||||
{((this.context.identity.permissions & PERMISSION_INVITE_USERS) === PERMISSION_INVITE_USERS) && <li><a href='/invites' target='_blank'><FormattedMessage id='getting_started.invite' defaultMessage='Invite people' /></a> · </li>}
|
||||
{withHotkeys && <li><Link to='/keyboard-shortcuts'><FormattedMessage id='navigation_bar.keyboard_shortcuts' defaultMessage='Hotkeys' /></Link> · </li>}
|
||||
<li><a href='/auth/edit'><FormattedMessage id='getting_started.security' defaultMessage='Security' /></a> · </li>
|
||||
{!limitedFederationMode && <li><a href='/about/more' target='_blank'><FormattedMessage id='navigation_bar.info' defaultMessage='About this server' /></a> · </li>}
|
||||
|
|
|
@ -12,14 +12,12 @@ export const boostModal = getMeta('boost_modal');
|
|||
export const deleteModal = getMeta('delete_modal');
|
||||
export const me = getMeta('me');
|
||||
export const searchEnabled = getMeta('search_enabled');
|
||||
export const invitesEnabled = getMeta('invites_enabled');
|
||||
export const limitedFederationMode = getMeta('limited_federation_mode');
|
||||
export const repository = getMeta('repository');
|
||||
export const source_url = getMeta('source_url');
|
||||
export const version = getMeta('version');
|
||||
export const mascot = getMeta('mascot');
|
||||
export const profile_directory = getMeta('profile_directory');
|
||||
export const isStaff = getMeta('is_staff');
|
||||
export const forceSingleColumn = !getMeta('advanced_layout');
|
||||
export const useBlurhash = getMeta('use_blurhash');
|
||||
export const usePendingItems = getMeta('use_pending_items');
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
export const PERMISSION_INVITE_USERS = 0x0000000000010000;
|
||||
export const PERMISSION_MANAGE_USERS = 0x0000000000000400;
|
||||
export const PERMISSION_MANAGE_REPORTS = 0x0000000000000010;
|
|
@ -7,12 +7,13 @@ const initialState = ImmutableMap({
|
|||
streaming_api_base_url: null,
|
||||
access_token: null,
|
||||
layout: layoutFromWindow(),
|
||||
permissions: '0',
|
||||
});
|
||||
|
||||
export default function meta(state = initialState, action) {
|
||||
switch(action.type) {
|
||||
case STORE_HYDRATE:
|
||||
return state.merge(action.state.get('meta'));
|
||||
return state.merge(action.state.get('meta')).set('permissions', action.state.getIn(['role', 'permissions']));
|
||||
case APP_LAYOUT_CHANGE:
|
||||
return state.set('layout', action.layout);
|
||||
default:
|
||||
|
|
|
@ -924,6 +924,10 @@ a.name-tag,
|
|||
margin-top: 15px;
|
||||
}
|
||||
|
||||
.user-role {
|
||||
color: var(--user-role-accent);
|
||||
}
|
||||
|
||||
.announcements-list,
|
||||
.filters-list {
|
||||
border: 1px solid lighten($ui-base-color, 4%);
|
||||
|
@ -960,6 +964,17 @@ a.name-tag,
|
|||
&__meta {
|
||||
padding: 0 15px;
|
||||
color: $dark-text-color;
|
||||
|
||||
a {
|
||||
color: inherit;
|
||||
text-decoration: underline;
|
||||
|
||||
&:hover,
|
||||
&:focus,
|
||||
&:active {
|
||||
text-decoration: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&__action-bar {
|
||||
|
|
|
@ -256,6 +256,10 @@ code {
|
|||
}
|
||||
}
|
||||
|
||||
.input.with_block_label.user_role_permissions_as_keys ul {
|
||||
columns: unset;
|
||||
}
|
||||
|
||||
.input.datetime .label_input select {
|
||||
display: inline-block;
|
||||
width: auto;
|
||||
|
|
|
@ -8,11 +8,11 @@ class Admin::SystemCheck
|
|||
Admin::SystemCheck::ElasticsearchCheck,
|
||||
].freeze
|
||||
|
||||
def self.perform
|
||||
def self.perform(current_user)
|
||||
ACTIVE_CHECKS.each_with_object([]) do |klass, arr|
|
||||
check = klass.new
|
||||
check = klass.new(current_user)
|
||||
|
||||
if check.pass?
|
||||
if check.skip? || check.pass?
|
||||
arr
|
||||
else
|
||||
arr << check.message
|
||||
|
|
|
@ -1,6 +1,16 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class Admin::SystemCheck::BaseCheck
|
||||
attr_reader :current_user
|
||||
|
||||
def initialize(current_user)
|
||||
@current_user = current_user
|
||||
end
|
||||
|
||||
def skip?
|
||||
false
|
||||
end
|
||||
|
||||
def pass?
|
||||
raise NotImplementedError
|
||||
end
|
||||
|
|
|
@ -1,6 +1,10 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class Admin::SystemCheck::DatabaseSchemaCheck < Admin::SystemCheck::BaseCheck
|
||||
def skip?
|
||||
!current_user.can?(:view_devops)
|
||||
end
|
||||
|
||||
def pass?
|
||||
!ActiveRecord::Base.connection.migration_context.needs_migration?
|
||||
end
|
||||
|
|
|
@ -1,6 +1,10 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class Admin::SystemCheck::ElasticsearchCheck < Admin::SystemCheck::BaseCheck
|
||||
def skip?
|
||||
!current_user.can?(:view_devops)
|
||||
end
|
||||
|
||||
def pass?
|
||||
return true unless Chewy.enabled?
|
||||
|
||||
|
@ -32,8 +36,4 @@ class Admin::SystemCheck::ElasticsearchCheck < Admin::SystemCheck::BaseCheck
|
|||
def compatible_version?
|
||||
Gem::Version.new(running_version) >= Gem::Version.new(required_version)
|
||||
end
|
||||
|
||||
def missing_queues
|
||||
@missing_queues ||= Sidekiq::ProcessSet.new.reduce(SIDEKIQ_QUEUES) { |queues, process| queues - process['queues'] }
|
||||
end
|
||||
end
|
||||
|
|
|
@ -3,6 +3,10 @@
|
|||
class Admin::SystemCheck::RulesCheck < Admin::SystemCheck::BaseCheck
|
||||
include RoutingHelper
|
||||
|
||||
def skip?
|
||||
!current_user.can?(:manage_rules)
|
||||
end
|
||||
|
||||
def pass?
|
||||
Rule.kept.exists?
|
||||
end
|
||||
|
|
|
@ -9,6 +9,10 @@ class Admin::SystemCheck::SidekiqProcessCheck < Admin::SystemCheck::BaseCheck
|
|||
scheduler
|
||||
).freeze
|
||||
|
||||
def skip?
|
||||
!current_user.can?(:view_devops)
|
||||
end
|
||||
|
||||
def pass?
|
||||
missing_queues.empty?
|
||||
end
|
||||
|
|
|
@ -116,7 +116,7 @@ class Account < ApplicationRecord
|
|||
scope :by_recent_status, -> { order(Arel.sql('(case when account_stats.last_status_at is null then 1 else 0 end) asc, account_stats.last_status_at desc, accounts.id desc')) }
|
||||