aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorEugen Rochko <eugen@zeonfederated.com>2022-07-05 02:41:40 +0200
committerGitHub <noreply@github.com>2022-07-05 02:41:40 +0200
commit44b2ee3485ba0845e5910cefcb4b1e2f84f34470 (patch)
treecc91189c9b36aaf0a04d339455c6d238992753a9
parent1b4054256f9d3302b44f71627a23bb0902578867 (diff)
downloadmastodon-44b2ee3485ba0845e5910cefcb4b1e2f84f34470.tar
mastodon-44b2ee3485ba0845e5910cefcb4b1e2f84f34470.tar.gz
mastodon-44b2ee3485ba0845e5910cefcb4b1e2f84f34470.tar.bz2
mastodon-44b2ee3485ba0845e5910cefcb4b1e2f84f34470.zip
Add customizable user roles (#18641)
* Add customizable user roles * Various fixes and improvements * Add migration for old settings and fix tootctl role management
-rw-r--r--.rubocop.yml4
-rw-r--r--app/controllers/admin/account_actions_controller.rb4
-rw-r--r--app/controllers/admin/accounts_controller.rb2
-rw-r--r--app/controllers/admin/action_logs_controller.rb5
-rw-r--r--app/controllers/admin/base_controller.rb2
-rw-r--r--app/controllers/admin/custom_emojis_controller.rb2
-rw-r--r--app/controllers/admin/dashboard_controller.rb4
-rw-r--r--app/controllers/admin/email_domain_blocks_controller.rb2
-rw-r--r--app/controllers/admin/follow_recommendations_controller.rb2
-rw-r--r--app/controllers/admin/ip_blocks_controller.rb2
-rw-r--r--app/controllers/admin/relationships_controller.rb2
-rw-r--r--app/controllers/admin/roles_controller.rb65
-rw-r--r--app/controllers/admin/statuses_controller.rb2
-rw-r--r--app/controllers/admin/subscriptions_controller.rb20
-rw-r--r--app/controllers/admin/trends/links/preview_card_providers_controller.rb4
-rw-r--r--app/controllers/admin/trends/links_controller.rb4
-rw-r--r--app/controllers/admin/trends/statuses_controller.rb4
-rw-r--r--app/controllers/admin/trends/tags_controller.rb4
-rw-r--r--app/controllers/admin/users/roles_controller.rb33
-rw-r--r--app/controllers/admin/users/two_factor_authentications_controller.rb (renamed from app/controllers/admin/two_factor_authentications_controller.rb)2
-rw-r--r--app/controllers/api/v1/admin/account_actions_controller.rb7
-rw-r--r--app/controllers/api/v1/admin/accounts_controller.rb6
-rw-r--r--app/controllers/api/v1/admin/dimensions_controller.rb6
-rw-r--r--app/controllers/api/v1/admin/domain_allows_controller.rb2
-rw-r--r--app/controllers/api/v1/admin/domain_blocks_controller.rb2
-rw-r--r--app/controllers/api/v1/admin/measures_controller.rb6
-rw-r--r--app/controllers/api/v1/admin/reports_controller.rb2
-rw-r--r--app/controllers/api/v1/admin/retention_controller.rb6
-rw-r--r--app/controllers/api/v1/admin/trends/links_controller.rb20
-rw-r--r--app/controllers/api/v1/admin/trends/statuses_controller.rb20
-rw-r--r--app/controllers/api/v1/admin/trends/tags_controller.rb20
-rw-r--r--app/controllers/api/v1/trends/links_controller.rb10
-rw-r--r--app/controllers/api/v1/trends/statuses_controller.rb10
-rw-r--r--app/controllers/api/v1/trends/tags_controller.rb12
-rw-r--r--app/controllers/api/v2/admin/accounts_controller.rb13
-rw-r--r--app/controllers/application_controller.rb8
-rw-r--r--app/controllers/custom_css_controller.rb2
-rw-r--r--app/helpers/accounts_helper.rb14
-rw-r--r--app/javascript/mastodon/components/status_action_bar.js6
-rw-r--r--app/javascript/mastodon/containers/mastodon.js1
-rw-r--r--app/javascript/mastodon/features/account/components/header.js9
-rw-r--r--app/javascript/mastodon/features/notifications/components/column_settings.js10
-rw-r--r--app/javascript/mastodon/features/status/components/action_bar.js6
-rw-r--r--app/javascript/mastodon/features/ui/components/link_footer.js9
-rw-r--r--app/javascript/mastodon/initial_state.js2
-rw-r--r--app/javascript/mastodon/permissions.js3
-rw-r--r--app/javascript/mastodon/reducers/meta.js3
-rw-r--r--app/javascript/styles/mastodon/admin.scss15
-rw-r--r--app/javascript/styles/mastodon/forms.scss4
-rw-r--r--app/lib/admin/system_check.rb6
-rw-r--r--app/lib/admin/system_check/base_check.rb10
-rw-r--r--app/lib/admin/system_check/database_schema_check.rb4
-rw-r--r--app/lib/admin/system_check/elasticsearch_check.rb8
-rw-r--r--app/lib/admin/system_check/rules_check.rb4
-rw-r--r--app/lib/admin/system_check/sidekiq_process_check.rb4
-rw-r--r--app/models/account.rb9
-rw-r--r--app/models/account_filter.rb27
-rw-r--r--app/models/concerns/user_roles.rb68
-rw-r--r--app/models/form/admin_settings.rb4
-rw-r--r--app/models/trends.rb2
-rw-r--r--app/models/user.rb38
-rw-r--r--app/models/user_role.rb179
-rw-r--r--app/policies/account_moderation_note_policy.rb4
-rw-r--r--app/policies/account_policy.rb40
-rw-r--r--app/policies/account_warning_policy.rb2
-rw-r--r--app/policies/account_warning_preset_policy.rb8
-rw-r--r--app/policies/announcement_policy.rb8
-rw-r--r--app/policies/appeal_policy.rb8
-rw-r--r--app/policies/application_policy.rb6
-rw-r--r--app/policies/audit_log_policy.rb7
-rw-r--r--app/policies/custom_emoji_policy.rb14
-rw-r--r--app/policies/dashboard_policy.rb7
-rw-r--r--app/policies/delivery_policy.rb6
-rw-r--r--app/policies/domain_allow_policy.rb8
-rw-r--r--app/policies/domain_block_policy.rb10
-rw-r--r--app/policies/email_domain_block_policy.rb6
-rw-r--r--app/policies/follow_recommendation_policy.rb6
-rw-r--r--app/policies/instance_policy.rb6
-rw-r--r--app/policies/invite_policy.rb12
-rw-r--r--app/policies/ip_block_policy.rb6
-rw-r--r--app/policies/preview_card_policy.rb4
-rw-r--r--app/policies/preview_card_provider_policy.rb4
-rw-r--r--app/policies/relay_policy.rb2
-rw-r--r--app/policies/report_note_policy.rb4
-rw-r--r--app/policies/report_policy.rb6
-rw-r--r--app/policies/rule_policy.rb8
-rw-r--r--app/policies/settings_policy.rb6
-rw-r--r--app/policies/status_policy.rb8
-rw-r--r--app/policies/tag_policy.rb8
-rw-r--r--app/policies/user_policy.rb38
-rw-r--r--app/policies/user_role_policy.rb19
-rw-r--r--app/policies/webhook_policy.rb16
-rw-r--r--app/presenters/initial_state_presenter.rb4
-rw-r--r--app/serializers/initial_state_serializer.rb3
-rw-r--r--app/serializers/rest/credential_account_serializer.rb6
-rw-r--r--app/serializers/rest/instance_serializer.rb2
-rw-r--r--app/serializers/rest/role_serializer.rb13
-rw-r--r--app/services/account_search_service.rb4
-rw-r--r--app/services/appeal_service.rb2
-rw-r--r--app/services/bootstrap_timeline_service.rb2
-rw-r--r--app/services/notify_service.rb2
-rw-r--r--app/services/report_service.rb2
-rw-r--r--app/views/admin/accounts/index.html.haml55
-rw-r--r--app/views/admin/accounts/show.html.haml9
-rw-r--r--app/views/admin/action_logs/index.html.haml2
-rw-r--r--app/views/admin/instances/show.html.haml49
-rw-r--r--app/views/admin/roles/_form.html.haml37
-rw-r--r--app/views/admin/roles/_role.html.haml18
-rw-r--r--app/views/admin/roles/edit.html.haml8
-rw-r--r--app/views/admin/roles/index.html.haml17
-rw-r--r--app/views/admin/roles/new.html.haml4
-rw-r--r--app/views/admin/settings/edit.html.haml6
-rw-r--r--app/views/admin/tags/show.html.haml77
-rw-r--r--app/views/admin/users/roles/show.html.haml9
-rw-r--r--app/views/custom_css/show.css.erb10
-rwxr-xr-xapp/views/layouts/application.html.haml4
-rw-r--r--app/views/settings/preferences/notifications/show.html.haml10
-rw-r--r--config/application.rb1
-rw-r--r--config/locales/activerecord.en.yml9
-rw-r--r--config/locales/en.yml85
-rw-r--r--config/locales/simple_form.en.yml15
-rw-r--r--config/navigation.rb81
-rw-r--r--config/roles.yml35
-rw-r--r--config/routes.rb13
-rw-r--r--db/migrate/20220611210335_create_user_roles.rb13
-rw-r--r--db/migrate/20220611212541_add_role_id_to_users.rb8
-rw-r--r--db/post_migrate/20220617202502_migrate_roles.rb26
-rw-r--r--db/post_migrate/20220704024901_migrate_settings_to_user_roles.rb41
-rw-r--r--db/schema.rb15
-rw-r--r--db/seeds.rb12
-rw-r--r--db/seeds/01_web_app.rb1
-rw-r--r--db/seeds/02_instance_actor.rb1
-rw-r--r--db/seeds/03_roles.rb9
-rw-r--r--db/seeds/04_admin.rb8
-rw-r--r--lib/mastodon/accounts_cli.rb41
-rw-r--r--lib/simple_navigation/item_extensions.rb15
-rw-r--r--spec/controllers/admin/account_moderation_notes_controller_spec.rb2
-rw-r--r--spec/controllers/admin/accounts_controller_spec.rb52
-rw-r--r--spec/controllers/admin/action_logs_controller_spec.rb2
-rw-r--r--spec/controllers/admin/base_controller_spec.rb7
-rw-r--r--spec/controllers/admin/change_email_controller_spec.rb2
-rw-r--r--spec/controllers/admin/confirmations_controller_spec.rb2
-rw-r--r--spec/controllers/admin/custom_emojis_controller_spec.rb2
-rw-r--r--spec/controllers/admin/dashboard_controller_spec.rb2
-rw-r--r--spec/controllers/admin/disputes/appeals_controller_spec.rb4
-rw-r--r--spec/controllers/admin/domain_blocks_controller_spec.rb2
-rw-r--r--spec/controllers/admin/email_domain_blocks_controller_spec.rb2
-rw-r--r--spec/controllers/admin/instances_controller_spec.rb8
-rw-r--r--spec/controllers/admin/invites_controller_spec.rb2
-rw-r--r--spec/controllers/admin/report_notes_controller_spec.rb2
-rw-r--r--spec/controllers/admin/reports_controller_spec.rb2
-rw-r--r--spec/controllers/admin/resets_controller_spec.rb2
-rw-r--r--spec/controllers/admin/roles_controller_spec.rb244
-rw-r--r--spec/controllers/admin/settings_controller_spec.rb2
-rw-r--r--spec/controllers/admin/statuses_controller_spec.rb2
-rw-r--r--spec/controllers/admin/tags_controller_spec.rb2
-rw-r--r--spec/controllers/admin/users/roles_controller.rb81
-rw-r--r--spec/controllers/admin/users/two_factor_authentications_controller_spec.rb (renamed from spec/controllers/admin/two_factor_authentications_controller_spec.rb)5
-rw-r--r--spec/controllers/api/v1/admin/account_actions_controller_spec.rb6
-rw-r--r--spec/controllers/api/v1/admin/accounts_controller_spec.rb20
-rw-r--r--spec/controllers/api/v1/admin/domain_allows_controller_spec.rb20
-rw-r--r--spec/controllers/api/v1/admin/domain_blocks_controller_spec.rb20
-rw-r--r--spec/controllers/api/v1/admin/reports_controller_spec.rb16
-rw-r--r--spec/controllers/api/v1/reports_controller_spec.rb2
-rw-r--r--spec/controllers/api/v2/admin/accounts_controller_spec.rb6
-rw-r--r--spec/controllers/application_controller_spec.rb64
-rw-r--r--spec/controllers/disputes/appeals_controller_spec.rb2
-rw-r--r--spec/controllers/invites_controller_spec.rb40
-rw-r--r--spec/fabricators/user_role_fabricator.rb5
-rw-r--r--spec/models/account_spec.rb14
-rw-r--r--spec/models/admin/account_action_spec.rb2
-rw-r--r--spec/models/user_role_spec.rb189
-rw-r--r--spec/models/user_spec.rb159
-rw-r--r--spec/policies/account_moderation_note_policy_spec.rb4
-rw-r--r--spec/policies/account_policy_spec.rb8
-rw-r--r--spec/policies/custom_emoji_policy_spec.rb2
-rw-r--r--spec/policies/domain_block_policy_spec.rb2
-rw-r--r--spec/policies/email_domain_block_policy_spec.rb2
-rw-r--r--spec/policies/instance_policy_spec.rb2
-rw-r--r--spec/policies/invite_policy_spec.rb54
-rw-r--r--spec/policies/relay_policy_spec.rb2
-rw-r--r--spec/policies/report_note_policy_spec.rb5
-rw-r--r--spec/policies/report_policy_spec.rb2
-rw-r--r--spec/policies/settings_policy_spec.rb2
-rw-r--r--spec/policies/status_policy_spec.rb2
-rw-r--r--spec/policies/tag_policy_spec.rb2
-rw-r--r--spec/policies/user_policy_spec.rb55
187 files changed, 1945 insertions, 1032 deletions
diff --git a/.rubocop.yml b/.rubocop.yml
index 9e3ff21f2..8dc2d1c47 100644
--- a/.rubocop.yml
+++ b/.rubocop.yml
@@ -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'
diff --git a/app/controllers/admin/account_actions_controller.rb b/app/controllers/admin/account_actions_controller.rb
index ea56fa0ac..3f2e28b6a 100644
--- a/app/controllers/admin/account_actions_controller.rb
+++ b/app/controllers/admin/account_actions_controller.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
diff --git a/app/controllers/admin/accounts_controller.rb b/app/controllers/admin/accounts_controller.rb
index e0ae71b9f..46c9aba91 100644
--- a/app/controllers/admin/accounts_controller.rb
+++ b/app/controllers/admin/accounts_controller.rb
@@ -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
diff --git a/app/controllers/admin/action_logs_controller.rb b/app/controllers/admin/action_logs_controller.rb
index 2d77620df..42edec15a 100644
--- a/app/controllers/admin/action_logs_controller.rb
+++ b/app/controllers/admin/action_logs_controller.rb
@@ -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
diff --git a/app/controllers/admin/base_controller.rb b/app/controllers/admin/base_controller.rb
index 7b81a2b01..5b7a7ec11 100644
--- a/app/controllers/admin/base_controller.rb
+++ b/app/controllers/admin/base_controller.rb
@@ -7,8 +7,8 @@ module Admin
layout 'admin'
- before_action :require_staff!
before_action :set_body_classes
+ after_action :verify_authorized
private
diff --git a/app/controllers/admin/custom_emojis_controller.rb b/app/controllers/admin/custom_emojis_controller.rb
index 71efb543e..2c33e9f8f 100644
--- a/app/controllers/admin/custom_emojis_controller.rb
+++ b/app/controllers/admin/custom_emojis_controller.rb
@@ -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
diff --git a/app/controllers/admin/dashboard_controller.rb b/app/controllers/admin/dashboard_controller.rb
index da9c6dd16..924b623ad 100644
--- a/app/controllers/admin/dashboard_controller.rb
+++ b/app/controllers/admin/dashboard_controller.rb
@@ -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
diff --git a/app/controllers/admin/email_domain_blocks_controller.rb b/app/controllers/admin/email_domain_blocks_controller.rb
index a4bbbba5b..593457b94 100644
--- a/app/controllers/admin/email_domain_blocks_controller.rb
+++ b/app/controllers/admin/email_domain_blocks_controller.rb
@@ -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
diff --git a/app/controllers/admin/follow_recommendations_controller.rb b/app/controllers/admin/follow_recommendations_controller.rb
index e3eac62b3..841e3cc7f 100644
--- a/app/controllers/admin/follow_recommendations_controller.rb
+++ b/app/controllers/admin/follow_recommendations_controller.rb
@@ -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
diff --git a/app/controllers/admin/ip_blocks_controller.rb b/app/controllers/admin/ip_blocks_controller.rb
index 92b8b0d2b..a87520f4e 100644
--- a/app/controllers/admin/ip_blocks_controller.rb
+++ b/app/controllers/admin/ip_blocks_controller.rb
@@ -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
diff --git a/app/controllers/admin/relationships_controller.rb b/app/controllers/admin/relationships_controller.rb
index 085ded21c..67645f054 100644
--- a/app/controllers/admin/relationships_controller.rb
+++ b/app/controllers/admin/relationships_controller.rb
@@ -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
diff --git a/app/controllers/admin/roles_controller.rb b/app/controllers/admin/roles_controller.rb
index 13f56e9be..3e502ccc4 100644
--- a/app/controllers/admin/roles_controller.rb
+++ b/app/controllers/admin/roles_controller.rb
@@ -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 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 demote
- authorize @user, :demote?
- @user.demote!
- log_action :demote, @user
- redirect_to admin_account_path(@user.account_id)
+ def resource_params
+ params.require(:user_role).permit(:name, :color, :highlighted, :position, permissions_as_keys: [])
end
end
end
diff --git a/app/controllers/admin/statuses_controller.rb b/app/controllers/admin/statuses_controller.rb
index 817c0caa9..084921ceb 100644
--- a/app/controllers/admin/statuses_controller.rb
+++ b/app/controllers/admin/statuses_controller.rb
@@ -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
diff --git a/app/controllers/admin/subscriptions_controller.rb b/app/controllers/admin/subscriptions_controller.rb
deleted file mode 100644
index 40500ef43..000000000
--- a/app/controllers/admin/subscriptions_controller.rb
+++ /dev/null
@@ -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
diff --git a/app/controllers/admin/trends/links/preview_card_providers_controller.rb b/app/controllers/admin/trends/links/preview_card_providers_controller.rb
index 40a466cd6..97dee8eca 100644
--- a/app/controllers/admin/trends/links/preview_card_providers_controller.rb
+++ b/app/controllers/admin/trends/links/preview_card_providers_controller.rb
@@ -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
diff --git a/app/controllers/admin/trends/links_controller.rb b/app/controllers/admin/trends/links_controller.rb
index 434eec5fe..a497eae41 100644
--- a/app/controllers/admin/trends/links_controller.rb
+++ b/app/controllers/admin/trends/links_controller.rb
@@ -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
diff --git a/app/controllers/admin/trends/statuses_controller.rb b/app/controllers/admin/trends/statuses_controller.rb
index 766242738..c538962f9 100644
--- a/app/controllers/admin/trends/statuses_controller.rb
+++ b/app/controllers/admin/trends/statuses_controller.rb
@@ -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
diff --git a/app/controllers/admin/trends/tags_controller.rb b/app/controllers/admin/trends/tags_controller.rb
index f4d1ec0d1..98dd6c8ec 100644
--- a/app/controllers/admin/trends/tags_controller.rb
+++ b/app/controllers/admin/trends/tags_controller.rb
@@ -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
diff --git a/app/controllers/admin/users/roles_controller.rb b/app/controllers/admin/users/roles_controller.rb
new file mode 100644
index 000000000..0db50cee9
--- /dev/null
+++ b/app/controllers/admin/users/roles_controller.rb
@@ -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
diff --git a/app/controllers/admin/two_factor_authentications_controller.rb b/app/controllers/admin/users/two_factor_authentications_controller.rb
index f7fb7eb8f..5e3fb2b3c 100644
--- a/app/controllers/admin/two_factor_authentications_controller.rb
+++ b/app/controllers/admin/users/two_factor_authentications_controller.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
module Admin
- class TwoFactorAuthenticationsController < BaseController
+ class Users::TwoFactorAuthenticationsController < BaseController
before_action :set_target_user
def destroy
diff --git a/app/controllers/api/v1/admin/account_actions_controller.rb b/app/controllers/api/v1/admin/account_actions_controller.rb
index 6c9e04402..7249797a4 100644
--- a/app/controllers/api/v1/admin/account_actions_controller.rb
+++ b/app/controllers/api/v1/admin/account_actions_controller.rb
@@ -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
diff --git a/app/controllers/api/v1/admin/accounts_controller.rb b/app/controllers/api/v1/admin/accounts_controller.rb
index 65ed69f7b..0dee02e94 100644
--- a/app/controllers/api/v1/admin/accounts_controller.rb
+++ b/app/controllers/api/v1/admin/accounts_controller.rb
@@ -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
diff --git a/app/controllers/api/v1/admin/dimensions_controller.rb b/app/controllers/api/v1/admin/dimensions_controller.rb
index 49a5be1c3..4a72ad08b 100644
--- a/app/controllers/api/v1/admin/dimensions_controller.rb
+++ b/app/controllers/api/v1/admin/dimensions_controller.rb
@@ -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
diff --git a/app/controllers/api/v1/admin/domain_allows_controller.rb b/app/controllers/api/v1/admin/domain_allows_controller.rb
index 838978ddb..59aa807d6 100644
--- a/app/controllers/api/v1/admin/domain_allows_controller.rb
+++ b/app/controllers/api/v1/admin/domain_allows_controller.rb
@@ -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
diff --git a/app/controllers/api/v1/admin/domain_blocks_controller.rb b/app/controllers/api/v1/admin/domain_blocks_controller.rb
index 229870eee..de8fd9d08 100644
--- a/app/controllers/api/v1/admin/domain_blocks_controller.rb
+++ b/app/controllers/api/v1/admin/domain_blocks_controller.rb
@@ -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
diff --git a/app/controllers/api/v1/admin/measures_controller.rb b/app/controllers/api/v1/admin/measures_controller.rb
index da95d3422..d78d7e10b 100644
--- a/app/controllers/api/v1/admin/measures_controller.rb
+++ b/app/controllers/api/v1/admin/measures_controller.rb
@@ -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
diff --git a/app/controllers/api/v1/admin/reports_controller.rb b/app/controllers/api/v1/admin/reports_controller.rb
index 865ba3d23..9dfb181a2 100644
--- a/app/controllers/api/v1/admin/reports_controller.rb
+++ b/app/controllers/api/v1/admin/reports_controller.rb
@@ -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(
diff --git a/app/controllers/api/v1/admin/retention_controller.rb b/app/controllers/api/v1/admin/retention_controller.rb
index 98d1a3d81..59d6b8388 100644
--- a/app/controllers/api/v1/admin/retention_controller.rb
+++ b/app/controllers/api/v1/admin/retention_controller.rb
@@ -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
diff --git a/app/controllers/api/v1/admin/trends/links_controller.rb b/app/controllers/api/v1/admin/trends/links_controller.rb
index 0a191fe4b..cc6388980 100644
--- a/app/controllers/api/v1/admin/trends/links_controller.rb
+++ b/app/controllers/api/v1/admin/trends/links_controller.rb
@@ -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
diff --git a/app/controllers/api/v1/admin/trends/statuses_controller.rb b/app/controllers/api/v1/admin/trends/statuses_controller.rb
index cb145f165..c39f77363 100644
--- a/app/controllers/api/v1/admin/trends/statuses_controller.rb
+++ b/app/controllers/api/v1/admin/trends/statuses_controller.rb
@@ -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
diff --git a/app/controllers/api/v1/admin/trends/tags_controller.rb b/app/controllers/api/v1/admin/trends/tags_controller.rb
index 9c28b0412..f3c0c4b6b 100644
--- a/app/controllers/api/v1/admin/trends/tags_controller.rb
+++ b/app/controllers/api/v1/admin/trends/tags_controller.rb
@@ -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
diff --git a/app/controllers/api/v1/trends/links_controller.rb b/app/controllers/api/v1/trends/links_controller.rb
index 2385fe438..1a9f918f2 100644
--- a/app/controllers/api/v1/trends/links_controller.rb
+++ b/app/controllers/api/v1/trends/links_controller.rb
@@ -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
diff --git a/app/controllers/api/v1/trends/statuses_controller.rb b/app/controllers/api/v1/trends/statuses_controller.rb
index 1f2fff582..c275d5fc8 100644
--- a/app/controllers/api/v1/trends/statuses_controller.rb
+++ b/app/controllers/api/v1/trends/statuses_controller.rb
@@ -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
diff --git a/app/controllers/api/v1/trends/tags_controller.rb b/app/controllers/api/v1/trends/tags_controller.rb
index 38003f599..41f9ffac1 100644
--- a/app/controllers/api/v1/trends/tags_controller.rb
+++ b/app/controllers/api/v1/trends/tags_controller.rb
@@ -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
diff --git a/app/controllers/api/v2/admin/accounts_controller.rb b/app/controllers/api/v2/admin/accounts_controller.rb
index a89e6835e..bcc1a0733 100644
--- a/app/controllers/api/v2/admin/accounts_controller.rb
+++ b/app/controllers/api/v2/admin/accounts_controller.rb
@@ -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
diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb
index 3d2f8280b..615536b96 100644
--- a/app/controllers/application_controller.rb
+++ b/app/controllers/application_controller.rb
@@ -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
diff --git a/app/controllers/custom_css_controller.rb b/app/controllers/custom_css_controller.rb
index e1dc5eaf6..9270c467d 100644
--- a/app/controllers/custom_css_controller.rb
+++ b/app/controllers/custom_css_controller.rb
@@ -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
diff --git a/app/helpers/accounts_helper.rb b/app/helpers/accounts_helper.rb
index d37634964..59664373d 100644
--- a/app/helpers/accounts_helper.rb
+++ b/app/helpers/accounts_helper.rb
@@ -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
diff --git a/app/javascript/mastodon/components/status_action_bar.js b/app/javascript/mastodon/components/status_action_bar.js
index ab8755be0..d44da482d 100644
--- a/app/javascript/mastodon/components/status_action_bar.js
+++ b/app/javascript/mastodon/components/status_action_bar.js
@@ -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')}` });
diff --git a/app/javascript/mastodon/containers/mastodon.js b/app/javascript/mastodon/containers/mastodon.js
index 0c3f6afa8..f4bef4686 100644
--- a/app/javascript/mastodon/containers/mastodon.js
+++ b/app/javascript/mastodon/containers/mastodon.js
@@ -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 {
diff --git a/app/javascript/mastodon/features/account/components/header.js b/app/javascript/mastodon/features/account/components/header.js
index 8e6b9f063..1ad9341c7 100644
--- a/app/javascript/mastodon/features/account/components/header.js
+++ b/app/javascript/mastodon/features/account/components/header.js
@@ -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')}` });
}
diff --git a/app/javascript/mastodon/features/notifications/components/column_settings.js b/app/javascript/mastodon/features/notifications/components/column_settings.js
index 61df79b46..b1618c1b4 100644
--- a/app/javascript/mastodon/features/notifications/components/column_settings.js
+++ b/app/javascript/mastodon/features/notifications/components/column_settings.js
@@ -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>
diff --git a/app/javascript/mastodon/features/status/components/action_bar.js b/app/javascript/mastodon/features/status/components/action_bar.js
index edaff959e..50bda69f8 100644
--- a/app/javascript/mastodon/features/status/components/action_bar.js
+++ b/app/javascript/mastodon/features/status/components/action_bar.js
@@ -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')}` });
diff --git a/app/javascript/mastodon/features/ui/components/link_footer.js b/app/javascript/mastodon/features/ui/components/link_footer.js
index edf1104c4..bbb9b122a 100644
--- a/app/javascript/mastodon/features/ui/components/link_footer.js
+++ b/app/javascript/mastodon/features/ui/components/link_footer.js
@@ -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>}
diff --git a/app/javascript/mastodon/initial_state.js b/app/javascript/mastodon/initial_state.js
index a6d54f134..709975270 100644
--- a/app/javascript/mastodon/initial_state.js
+++ b/app/javascript/mastodon/initial_state.js
@@ -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');
diff --git a/app/javascript/mastodon/permissions.js b/app/javascript/mastodon/permissions.js
new file mode 100644
index 000000000..752ddd6c5
--- /dev/null
+++ b/app/javascript/mastodon/permissions.js
@@ -0,0 +1,3 @@
+export const PERMISSION_INVITE_USERS = 0x0000000000010000;
+export const PERMISSION_MANAGE_USERS = 0x0000000000000400;
+export const PERMISSION_MANAGE_REPORTS = 0x0000000000000010;
diff --git a/app/javascript/mastodon/reducers/meta.js b/app/javascript/mastodon/reducers/meta.js
index 65becc44f..5040a340f 100644
--- a/app/javascript/mastodon/reducers/meta.js
+++ b/app/javascript/mastodon/reducers/meta.js
@@ -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:
diff --git a/app/javascript/styles/mastodon/admin.scss b/app/javascript/styles/mastodon/admin.scss
index 4ce5cd101..1c5494cde 100644
--- a/app/javascript/styles/mastodon/admin.scss
+++ b/app/javascript/styles/mastodon/admin.scss
@@ -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 {
diff --git a/app/javascript/styles/mastodon/forms.scss b/app/javascript/styles/mastodon/forms.scss
index da699dd25..990903859 100644
--- a/app/javascript/styles/mastodon/forms.scss
+++ b/app/javascript/styles/mastodon/forms.scss
@@ -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;
diff --git a/app/lib/admin/system_check.rb b/app/lib/admin/system_check.rb
index 877a42ef6..f512635ab 100644
--- a/app/lib/admin/system_check.rb
+++ b/app/lib/admin/system_check.rb
@@ -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
diff --git a/app/lib/admin/system_check/base_check.rb b/app/lib/admin/system_check/base_check.rb
index fcad8daca..c2974c218 100644
--- a/app/lib/admin/system_check/base_check.rb
+++ b/app/lib/admin/system_check/base_check.rb
@@ -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
diff --git a/app/lib/admin/system_check/database_schema_check.rb b/app/lib/admin/system_check/database_schema_check.rb
index b93d1954e..c2f01fd55 100644
--- a/app/lib/admin/system_check/database_schema_check.rb
+++ b/app/lib/admin/system_check/database_schema_check.rb
@@ -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
diff --git a/app/lib/admin/system_check/elasticsearch_check.rb b/app/lib/admin/system_check/elasticsearch_check.rb
index 1b48a5415..8aee18267 100644
--- a/app/lib/admin/system_check/elasticsearch_check.rb
+++ b/app/lib/admin/system_check/elasticsearch_check.rb
@@ -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
diff --git a/app/lib/admin/system_check/rules_check.rb b/app/lib/admin/system_check/rules_check.rb
index 1fbdf955d..8206a5df3 100644
--- a/app/lib/admin/system_check/rules_check.rb
+++ b/app/lib/admin/system_check/rules_check.rb
@@ -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
diff --git a/app/lib/admin/system_check/sidekiq_process_check.rb b/app/lib/admin/system_check/sidekiq_process_check.rb
index 22446edaf..648811d6c 100644
--- a/app/lib/admin/system_check/sidekiq_process_check.rb
+++ b/app/lib/admin/system_check/sidekiq_process_check.rb
@@ -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
diff --git a/app/models/account.rb b/app/models/account.rb
index 730ef6293..628692d22 100644
--- a/app/models/account.rb
+++ b/app/models/account.rb
@@ -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')) }
scope :by_recent_sign_in, -> { order(Arel.sql('(case when users.current_sign_in_at is null then 1 else 0 end) asc, users.current_sign_in_at desc, accounts.id desc')) }
scope :popular, -> { order('account_stats.followers_count desc') }
- scope :by_domain_and_subdomains, ->(domain) { where(domain: domain).or(where(arel_table[:domain].matches('%.' + domain))) }
+ scope :by_domain_and_subdomains, ->(domain) { where(domain: domain).or(where(arel_table[:domain].matches("%.#{domain}"))) }
scope :not_excluded_by_account, ->(account) { where.not(id: account.excluded_from_timeline_account_ids) }
scope :not_domain_blocked_by_account, ->(account) { where(arel_table[:domain].eq(nil).or(arel_table[:domain].not_in(account.excluded_from_timeline_domains))) }
@@ -132,9 +132,6 @@ class Account < ApplicationRecord
:unconfirmed?,
:unconfirmed_or_pending?,
:role,
- :admin?,
- :moderator?,
- :staff?,
:locale,
:shows_application?,
to: :user,
@@ -454,7 +451,7 @@ class Account < ApplicationRecord
DeliveryFailureTracker.without_unavailable(urls)
end
- def search_for(terms, limit = 10, offset = 0)
+ def search_for(terms, limit: 10, offset: 0)
tsquery = generate_query_for_search(terms)
sql = <<-SQL.squish
@@ -476,7 +473,7 @@ class Account < ApplicationRecord
records
end
- def advanced_search_for(terms, account, limit = 10, following = false, offset = 0)
+ def advanced_search_for(terms, account, limit: 10, following: false, offset: 0)
tsquery = generate_query_for_search(terms)
sql = advanced_search_for_sql_template(following)
records = find_by_sql([sql, id: account.id, limit: limit, offset: offset, tsquery: tsquery])
diff --git a/app/models/account_filter.rb b/app/models/account_filter.rb
index ec309ce09..e214e0bad 100644
--- a/app/models/account_filter.rb
+++ b/app/models/account_filter.rb
@@ -4,7 +4,7 @@ class AccountFilter
KEYS = %i(
origin
status
- permissions
+ role_ids
username
by_domain
display_name
@@ -26,7 +26,7 @@ class AccountFilter
params.each do |key, value|
next if key.to_s == 'page'
- scope.merge!(scope_for(key, value.to_s.strip)) if value.present?
+ scope.merge!(scope_for(key, value)) if value.present?
end
scope
@@ -38,18 +38,18 @@ class AccountFilter
case key.to_s
when 'origin'
origin_scope(value)
- when 'permissions'
- permissions_scope(value)
+ when 'role_ids'
+ role_scope(value)
when 'status'
status_scope(value)
when 'by_domain'
- Account.where(domain: value)
+ Account.where(domain: value.to_s)
when 'username'
- Account.matches_username(value)
+ Account.matches_username(value.to_s)
when 'display_name'
- Account.matches_display_name(value)
+ Account.matches_display_name(value.to_s)
when 'email'
- accounts_with_users.merge(User.matches_email(value))
+ accounts_with_users.merge(User.matches_email(value.to_s))
when 'ip'
valid_ip?(value) ? accounts_with_users.merge(User.matches_ip(value).group('users.id, accounts.id')) : Account.none
when 'invited_by'
@@ -104,13 +104,8 @@ class AccountFilter
Account.left_joins(user: :invite).merge(Invite.where(user_id: value.to_s))
end
- def permissions_scope(value)
- case value.to_s
- when 'staff'
- accounts_with_users.merge(User.staff)
- else
- raise "Unknown permissions: #{value}"
- end
+ def role_scope(value)
+ accounts_with_users.merge(User.where(role_id: Array(value).map(&:to_s)))
end
def accounts_with_users
@@ -118,7 +113,7 @@ class AccountFilter
end
def valid_ip?(value)
- IPAddr.new(value) && true
+ IPAddr.new(value.to_s) && true
rescue IPAddr::InvalidAddressError
false
end
diff --git a/app/models/concerns/user_roles.rb b/app/models/concerns/user_roles.rb
deleted file mode 100644
index a42b4a172..000000000
--- a/app/models/concerns/user_roles.rb
+++ /dev/null
@@ -1,68 +0,0 @@
-# frozen_string_literal: true
-
-module UserRoles
- extend ActiveSupport::Concern
-
- included do
- scope :admins, -> { where(admin: true) }
- scope :moderators, -> { where(moderator: true) }
- scope :staff, -> { admins.or(moderators) }
- end
-
- def staff?
- admin? || moderator?
- end
-
- def role=(value)
- case value
- when 'admin'
- self.admin = true
- self.moderator = false
- when 'moderator'
- self.admin = false
- self.moderator = true
- else
- self.admin = false
- self.moderator = false
- end
- end
-
- def role
- if admin?
- 'admin'
- elsif moderator?
- 'moderator'
- else
- 'user'
- end
- end
-
- def role?(role)
- case role
- when 'user'
- true
- when 'moderator'
- staff?
- when 'admin'
- admin?
- else
- false
- end
- 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
-end
diff --git a/app/models/form/admin_settings.rb b/app/models/form/admin_settings.rb
index 6fc7c56fd..97fabc6ac 100644
--- a/app/models/form/admin_settings.rb
+++ b/app/models/form/admin_settings.rb
@@ -15,10 +15,8 @@ class Form::AdminSettings
closed_registrations_message
open_deletion
timeline_preview
- show_staff_badge
bootstrap_timeline_accounts
theme
- min_invite_role
activity_api_enabled
peers_api_enabled
show_known_fediverse_at_about_page
@@ -39,7 +37,6 @@ class Form::AdminSettings
BOOLEAN_KEYS = %i(
open_deletion
timeline_preview
- show_staff_badge
activity_api_enabled
peers_api_enabled
show_known_fediverse_at_about_page
@@ -62,7 +59,6 @@ class Form::AdminSettings
validates :site_short_description, :site_description, html: { wrap_with: :p }
validates :site_extended_description, :site_terms, :closed_registrations_message, html: true
validates :registrations_mode, inclusion: { in: %w(open approved none) }
- validates :min_invite_role, inclusion: { in: %w(disabled user moderator admin) }
validates :site_contact_email, :site_contact_username, presence: true
validates :site_contact_username, existing_username: true
validates :bootstrap_timeline_accounts, existing_username: { multiple: true }
diff --git a/app/models/trends.rb b/app/models/trends.rb
index f8864e55f..d886be89a 100644
--- a/app/models/trends.rb
+++ b/app/models/trends.rb
@@ -34,7 +34,7 @@ module Trends
return if links_requiring_review.empty? && tags_requiring_review.empty? && statuses_requiring_review.empty?
- User.staff.includes(:account).find_each do |user|
+ User.those_who_can(:manage_taxonomies).includes(:account).find_each do |user|
AdminMailer.new_trends(user.account, links_requiring_review, tags_requiring_review, statuses_requiring_review).deliver_later! if user.allows_trends_review_emails?
end
end
diff --git a/app/models/user.rb b/app/models/user.rb
index 81f6a58f6..60abaf77e 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -37,6 +37,7 @@
# sign_in_token_sent_at :datetime
# webauthn_id :string
# sign_up_ip :inet
+# role_id :bigint(8)
#
class User < ApplicationRecord
@@ -50,7 +51,6 @@ class User < ApplicationRecord
)
include Settings::Extend
- include UserRoles
include Redisable
include LanguagesHelper
@@ -79,6 +79,7 @@ class User < ApplicationRecord
belongs_to :account, inverse_of: :user
belongs_to :invite, counter_cache: :uses, optional: true
belongs_to :created_by_application, class_name: 'Doorkeeper::Application', optional: true
+ belongs_to :role, class_name: 'UserRole', optional: true
accepts_nested_attributes_for :account
has_many :applications, class_name: 'Doorkeeper::Application', as: :owner
@@ -103,6 +104,7 @@ class User < ApplicationRecord
validates_with RegistrationFormTimeValidator, on: :create
validates :website, absence: true, on: :create
validates :confirm_password, absence: true, on: :create
+ validate :validate_role_elevation
scope :recent, -> { order(id: :desc) }
scope :pending, -> { where(approved: false) }
@@ -117,6 +119,7 @@ class User < ApplicationRecord
scope :emailable, -> { confirmed.enabled.joins(:account).merge(Account.searchable) }
before_validation :sanitize_languages
+ before_validation :sanitize_role
before_create :set_approved
after_commit :send_pending_devise_notifications
after_create_commit :trigger_webhooks
@@ -135,8 +138,28 @@ class User < ApplicationRecord
:disable_swiping, :always_send_emails,
to: :settings, prefix: :setting, allow_nil: false
+ delegate :can?, to: :role
+
attr_reader :invite_code
- attr_writer :external, :bypass_invite_request_check
+ attr_writer :external, :bypass_invite_request_check, :current_account
+
+ def self.those_who_can(*any_of_privileges)
+ matching_role_ids = UserRole.that_can(*any_of_privileges).map(&:id)
+
+ if matching_role_ids.empty?
+ none
+ else
+ where(role_id: matching_role_ids)
+ end
+ end
+
+ def role
+ if role_id.nil?
+ UserRole.everyone
+ else
+ super
+ end
+ end
def confirmed?
confirmed_at.present?
@@ -441,6 +464,11 @@ class User < ApplicationRecord
self.chosen_languages = nil if chosen_languages.empty?
end
+ def sanitize_role
+ return if role.nil?
+ self.role = nil if role.everyone?
+ end
+
def prepare_new_user!
BootstrapTimelineWorker.perform_async(account_id)
ActivityTracker.increment('activity:accounts:local')
@@ -453,7 +481,7 @@ class User < ApplicationRecord
end
def notify_staff_about_pending_account!
- User.staff.includes(:account).find_each do |u|
+ User.those_who_can(:manage_users).includes(:account).find_each do |u|
next unless u.allows_pending_account_emails?
AdminMailer.new_pending_account(u.account, self).deliver_later
end
@@ -471,6 +499,10 @@ class User < ApplicationRecord
email_changed? && !external? && !(Rails.env.test? || Rails.env.development?)
end
+ def validate_role_elevation
+ errors.add(:role_id, :elevated) if defined?(@current_account) && role&.overrides?(@current_account&.user_role)
+ end
+
def invite_text_required?
Setting.require_invite_text && !invited? && !external? && !bypass_invite_request_check?
end
diff --git a/app/models/user_role.rb b/app/models/user_role.rb
new file mode 100644
index 000000000..833b96d71
--- /dev/null
+++ b/app/models/user_role.rb
@@ -0,0 +1,179 @@
+# frozen_string_literal: true
+
+# == Schema Information
+#
+# Table name: user_roles
+#
+# id :bigint(8) not null, primary key
+# name :string default(""), not null
+# color :string default(""), not null
+# position :integer default(0), not null
+# permissions :bigint(8) default(0), not null
+# highlighted :boolean default(FALSE), not null
+# created_at :datetime not null
+# updated_at :datetime not null
+#
+
+class UserRole < ApplicationRecord
+ FLAGS = {
+ administrator: (1 << 0),
+ view_devops: (1 << 1),
+ view_audit_log: (1 << 2),
+ view_dashboard: (1 << 3),
+ manage_reports: (1 << 4),
+ manage_federation: (1 << 5),
+ manage_settings: (1 << 6),
+ manage_blocks: (1 << 7),
+ manage_taxonomies: (1 << 8),
+ manage_appeals: (1 << 9),
+ manage_users: (1 << 10),
+ manage_invites: (1 << 11),
+ manage_rules: (1 << 12),
+ manage_announcements: (1 << 13),
+ manage_custom_emojis: (1 << 14),
+ manage_webhooks: (1 << 15),
+ invite_users: (1 << 16),
+ manage_roles: (1 << 17),
+ manage_user_access: (1 << 18),
+ delete_user_data: (1 << 19),
+ }.freeze
+
+ module Flags
+ NONE = 0
+ ALL = FLAGS.values.reduce(&:|)
+
+ DEFAULT = FLAGS[:invite_users]
+
+ CATEGORIES = {
+ invites: %i(
+ invite_users
+ ).freeze,
+
+ moderation: %w(
+ view_dashboard
+ view_audit_log
+ manage_users
+ manage_user_access
+ delete_user_data
+ manage_reports
+ manage_appeals
+ manage_federation
+ manage_blocks
+ manage_taxonomies
+ manage_invites
+ ).freeze,
+
+ administration: %w(
+ manage_settings
+ manage_rules
+ manage_roles
+ manage_webhooks
+ manage_custom_emojis
+ manage_announcements
+ ).freeze,
+
+ devops: %w(
+ view_devops
+ ).freeze,
+
+ special: %i(
+ administrator
+ ).freeze,
+ }.freeze
+ end
+
+ attr_writer :current_account
+
+ validates :name, presence: true, unless: :everyone?
+ validates :color, format: { with: /\A#?(?:[A-F0-9]{3}){1,2}\z/i }, unless: -> { color.blank? }
+
+ validate :validate_permissions_elevation
+ validate :validate_position_elevation
+ validate :validate_dangerous_permissions
+
+ before_validation :set_position
+
+ scope :assignable, -> { where.not(id: -99).order(position: :asc) }
+
+ has_many :users, inverse_of: :role, foreign_key: 'role_id', dependent: :nullify
+
+ def self.nobody
+ @nobody ||= UserRole.new(permissions: Flags::NONE, position: -1)
+ end
+
+ def self.everyone
+ UserRole.find(-99)
+ rescue ActiveRecord::RecordNotFound
+ UserRole.create!(id: -99, permissions: Flags::DEFAULT)
+ end
+
+ def self.that_can(*any_of_privileges)
+ all.select { |role| role.can?(*any_of_privileges) }
+ end
+
+ def everyone?
+ id == -99
+ end
+
+ def nobody?
+ id.nil?
+ end
+
+ def permissions_as_keys
+ FLAGS.keys.select { |privilege| permissions & FLAGS[privilege] == FLAGS[privilege] }.map(&:to_s)
+ end
+
+ def permissions_as_keys=(value)
+ self.permissions = value.map(&:presence).compact.reduce(Flags::NONE) { |bitmask, privilege| FLAGS.key?(privilege.to_sym) ? (bitmask | FLAGS[privilege.to_sym]) : bitmask }
+ end
+
+ def can?(*any_of_privileges)
+ any_of_privileges.any? { |privilege| in_permissions?(privilege) }
+ end
+
+ def overrides?(other_role)
+ other_role.nil? || position > other_role.position
+ end
+
+ def computed_permissions
+ # If called on the everyone role, no further computation needed
+ return permissions if everyone?
+
+ # If called on the nobody role, no permissions are there to be given
+ return Flags::NONE if nobody?
+
+ # Otherwise, compute permissions based on special conditions
+ @computed_permissions ||= begin
+ permissions = self.class.everyone.permissions | self.permissions
+
+ if permissions & FLAGS[:administrator] == FLAGS[:administrator]
+ Flags::ALL
+ else
+ permissions
+ end
+ end
+ end
+
+ private
+
+ def in_permissions?(privilege)
+ raise ArgumentError, "Unknown privilege: #{privilege}" unless FLAGS.key?(privilege)
+ computed_permissions & FLAGS[privilege] == FLAGS[privilege]
+ end
+
+ def set_position
+ self.position = -1 if everyone?
+ end
+
+ def validate_permissions_elevation
+ errors.add(:permissions_as_keys, :elevated) if defined?(@current_account) && @current_account.user_role.computed_permissions & permissions != permissions
+ end
+
+ def validate_position_elevation
+ errors.add(:position, :elevated) if defined?(@current_account) && @current_account.user_role.position < position
+ end
+
+ def validate_dangerous_permissions
+ errors.add(:permissions_as_keys, :dangerous) if everyone? && Flags::DEFAULT & permissions != permissions
+ end
+end
diff --git a/app/policies/account_moderation_note_policy.rb b/app/policies/account_moderation_note_policy.rb
index 885411a5b..310ce854c 100644
--- a/app/policies/account_moderation_note_policy.rb
+++ b/app/policies/account_moderation_note_policy.rb
@@ -2,11 +2,11 @@
class AccountModerationNotePolicy < ApplicationPolicy
def create?
- staff?
+ role.can?(:manage_reports)
end
def destroy?
- admin? || owner?
+ owner? || (role.can?(:manage_reports) && role.overrides?(record.account.user_role))
end
private
diff --git a/app/policies/account_policy.rb b/app/policies/account_policy.rb
index cc23771e7..a744af81d 100644
--- a/app/policies/account_policy.rb
+++ b/app/policies/account_policy.rb
@@ -2,74 +2,66 @@
class AccountPolicy < ApplicationPolicy
def index?
- staff?
+ role.can?(:manage_users)
end
def show?
- staff?
+ role.can?(:manage_users)
end
def warn?
- staff? && !record.user&.staff?
+ role.can?(:manage_users, :manage_reports) && role.overrides?(record.user_role)
end
def suspend?
- staff? && !record.user&.staff? && !record.instance_actor?
+ role.can?(:manage_users, :manage_reports) && role.overrides?(record.user_role) && !record.instance_actor?
end
def destroy?
- record.suspended_temporarily? && admin?
+ record.suspended_temporarily? && role.can?(:delete_user_data)
end
def unsuspend?
- staff? && record.suspension_origin_local?
+ role.can?(:manage_users) && record.suspension_origin_local?
end
def sensitive?
- staff? && !record.user&.staff?
+ role.can?(:manage_users, :manage_reports) && role.overrides?(record.user_role)
end
def unsensitive?
- staff?
+ role.can?(:manage_users)
end
def silence?
- staff? && !record.user&.staff?
+ role.can?(:manage_users, :manage_reports) && role.overrides?(record.user_role)
end
def unsilence?
- staff?
+ role.can?(:manage_users)
end
def redownload?
- admin?
+ role.can?(:manage_federation)
end
def remove_avatar?
- staff?
+ role.can?(:manage_users, :manage_reports) && role.overrides?(record.user_role)
end
def remove_header?
- staff?
- end
-
- def subscribe?
- admin?
- end
-
- def unsubscribe?
- admin?
+ role.can?(:manage_users, :manage_reports) && role.overrides?(record.user_role)
end
def memorialize?
- admin? && !record.user&.admin? && !record.instance_actor?
+ role.can?(:delete_user_data) && role.overrides?(record.user_role) && !record.instance_actor?
end
def unblock_email?
- staff?
+ role.can?(:manage_users)
end
def review?
- staff?
+ role.can?(:manage_taxonomies)
end
end
diff --git a/app/policies/account_warning_policy.rb b/app/policies/account_warning_policy.rb
index 65707dfa7..4f8df7420 100644
--- a/app/policies/account_warning_policy.rb
+++ b/app/policies/account_warning_policy.rb
@@ -2,7 +2,7 @@
class AccountWarningPolicy < ApplicationPolicy
def show?
- target? || staff?
+ target? || role.can?(:manage_appeals)
end
def appeal?
diff --git a/app/policies/account_warning_preset_policy.rb b/app/policies/account_warning_preset_policy.rb
index bccbd33ef..59514e951 100644
--- a/app/policies/account_warning_preset_policy.rb
+++ b/app/policies/account_warning_preset_policy.rb
@@ -2,18 +2,18 @@
class AccountWarningPresetPolicy < ApplicationPolicy
def index?
- staff?
+ role.can?(:manage_settings)
end
def create?
- staff?
+ role.can?(:manage_settings)
end
def update?
- staff?
+ role.can?(:manage_settings)
end
def destroy?
- staff?
+ role.can?(:manage_settings)
end
end
diff --git a/app/policies/announcement_policy.rb b/app/policies/announcement_policy.rb
index 0a4e4575c..b5dc6a18a 100644
--- a/app/policies/announcement_policy.rb
+++ b/app/policies/announcement_policy.rb
@@ -2,18 +2,18 @@
class AnnouncementPolicy < ApplicationPolicy
def index?
- staff?
+ role.can?(:manage_announcements)
end
def create?
- admin?
+ role.can?(:manage_announcements)
end
def update?
- admin?
+ role.can?(:manage_announcements)
end
def destroy?
- admin?
+ role.can?(:manage_announcements)
end
end
diff --git a/app/policies/appeal_policy.rb b/app/policies/appeal_policy.rb
index a25187172..7466b334b 100644
--- a/app/policies/appeal_policy.rb
+++ b/app/policies/appeal_policy.rb
@@ -2,12 +2,14 @@
class AppealPolicy < ApplicationPolicy
def index?
- staff?
+ role.can?(:manage_appeals)
end
def approve?
- record.pending? && staff?
+ record.pending? && role.can?(:manage_appeals)
end
- alias reject? approve?
+ def reject?
+ record.pending? && role.can?(:manage_appeals)
+ end
end
diff --git a/app/policies/application_policy.rb b/app/policies/application_policy.rb
index d1de5e81a..163b81e9e 100644
--- a/app/policies/application_policy.rb
+++ b/app/policies/application_policy.rb
@@ -8,8 +8,6 @@ class ApplicationPolicy
@record = record
end
- delegate :admin?, :moderator?, :staff?, to: :current_user, allow_nil: true
-
private
def current_user
@@ -19,4 +17,8 @@ class ApplicationPolicy
def user_signed_in?
!current_user.nil?
end
+
+ def role
+ current_user&.role || UserRole.nobody
+ end
end
diff --git a/app/policies/audit_log_policy.rb b/app/policies/audit_log_policy.rb
new file mode 100644
index 000000000..f78aa9a8e
--- /dev/null
+++ b/app/policies/audit_log_policy.rb
@@ -0,0 +1,7 @@
+# frozen_string_literal: true
+
+class AuditLogPolicy < ApplicationPolicy
+ def index?
+ role.can?(:view_audit_log)
+ end
+end
diff --git a/app/policies/custom_emoji_policy.rb b/app/policies/custom_emoji_policy.rb
index a8c3cbc73..18de71c19 100644
--- a/app/policies/custom_emoji_policy.rb
+++ b/app/policies/custom_emoji_policy.rb
@@ -2,30 +2,30 @@
class CustomEmojiPolicy < ApplicationPolicy
def index?
- staff?
+ role.can?(:manage_custom_emojis)
end
def create?
- admin?
+ role.can?(:manage_custom_emojis)
end
def update?
- admin?
+ role.can?(:manage_custom_emojis)
end
def copy?
- admin?
+ role.can?(:manage_custom_emojis)
end
def enable?
- staff?
+ role.can?(:manage_custom_emojis)
end
def disable?
- staff?
+ role.can?(:manage_custom_emojis)
end
def destroy?
- admin?
+ role.can?(:manage_custom_emojis)
end
end
diff --git a/app/policies/dashboard_policy.rb b/app/policies/dashboard_policy.rb
new file mode 100644
index 000000000..3df1c3088
--- /dev/null
+++ b/app/policies/dashboard_policy.rb
@@ -0,0 +1,7 @@
+# frozen_string_literal: true
+
+class DashboardPolicy < ApplicationPolicy
+ def index?
+ role.can?(:view_dashboard)
+ end
+end
diff --git a/app/policies/delivery_policy.rb b/app/policies/delivery_policy.rb
index 24d06c168..f6ba2eb18 100644
--- a/app/policies/delivery_policy.rb
+++ b/app/policies/delivery_policy.rb
@@ -2,14 +2,14 @@
class DeliveryPolicy < ApplicationPolicy
def clear_delivery_errors?
- admin?
+ role.can?(:manage_federation)
end
def restart_delivery?
- admin?
+ role.can?(:manage_federation)
end
def stop_delivery?
- admin?
+ role.can?(:manage_federation)
end
end
diff --git a/app/policies/domain_allow_policy.rb b/app/policies/domain_allow_policy.rb
index 7a5b5d780..45c797ecd 100644
--- a/app/policies/domain_allow_policy.rb
+++ b/app/policies/domain_allow_policy.rb
@@ -2,18 +2,18 @@
class DomainAllowPolicy < ApplicationPolicy
def index?
- admin?
+ role.can?(:manage_federation)
end
def show?
- admin?
+ role.can?(:manage_federation)
end
def create?
- admin?
+ role.can?(:manage_federation)
end
def destroy?
- admin?
+ role.can?(:manage_federation)
end
end
diff --git a/app/policies/domain_block_policy.rb b/app/policies/domain_block_policy.rb
index 543259cce..0fea2e035 100644
--- a/app/policies/domain_block_policy.rb
+++ b/app/policies/domain_block_policy.rb
@@ -2,22 +2,22 @@
class DomainBlockPolicy < ApplicationPolicy
def index?
- admin?
+ role.can?(:manage_federation)
end
def show?
- admin?
+ role.can?(:manage_federation)
end
def create?
- admin?
+ role.can?(:manage_federation)
end
def update?
- admin?
+ role.can?(:manage_federation)
end
def destroy?
- admin?
+ role.can?(:manage_federation)
end
end
diff --git a/app/policies/email_domain_block_policy.rb b/app/policies/email_domain_block_policy.rb
index 5a75ee183..1a0ddfa87 100644
--- a/app/policies/email_domain_block_policy.rb
+++ b/app/policies/email_domain_block_policy.rb
@@ -2,14 +2,14 @@
class EmailDomainBlockPolicy < ApplicationPolicy
def index?
- admin?
+ role.can?(:manage_blocks)
end
def create?
- admin?
+ role.can?(:manage_blocks)
end
def destroy?
- admin?
+ role.can?(:manage_blocks)
end
end
diff --git a/app/policies/follow_recommendation_policy.rb b/app/policies/follow_recommendation_policy.rb
index 68cd0e547..9245733ea 100644
--- a/app/policies/follow_recommendation_policy.rb
+++ b/app/policies/follow_recommendation_policy.rb
@@ -2,14 +2,14 @@
class FollowRecommendationPolicy < ApplicationPolicy
def show?
- staff?
+ role.can?(:manage_taxonomies)
end
def suppress?
- staff?
+ role.can?(:manage_taxonomies)
end
def unsuppress?
- staff?
+ role.can?(:manage_taxonomies)
end
end
diff --git a/app/policies/instance_policy.rb b/app/policies/instance_policy.rb
index 801ca162e..b15e123fe 100644
--- a/app/policies/instance_policy.rb
+++ b/app/policies/instance_policy.rb
@@ -2,14 +2,14 @@
class InstancePolicy < ApplicationPolicy
def index?
- admin?
+ role.can?(:manage_federation)
end
def show?
- admin?
+ role.can?(:manage_federation)
end
def destroy?
- admin?
+ role.can?(:manage_federation)
end
end
diff --git a/app/policies/invite_policy.rb b/app/policies/invite_policy.rb
index 14236f78b..24eacd08e 100644
--- a/app/policies/invite_policy.rb
+++ b/app/policies/invite_policy.rb
@@ -2,19 +2,19 @@
class InvitePolicy < ApplicationPolicy
def index?
- staff?
+ role.can?(:manage_invites)
end
def create?
- min_required_role?
+ role.can?(:invite_users)
end
def deactivate_all?
- admin?
+ role.can?(:manage_invites)
end
def destroy?
- owner? || (Setting.min_invite_role == 'admin' ? admin? : staff?)
+ owner? || role.can?(:manage_invites)
end
private
@@ -22,8 +22,4 @@ class InvitePolicy < ApplicationPolicy
def owner?
record.user_id == current_user&.id
end
-
- def min_required_role?
- current_user&.role?(Setting.min_invite_role)
- end
end
diff --git a/app/policies/ip_block_policy.rb b/app/policies/ip_block_policy.rb
index 34dbd746a..1abc97ad8 100644
--- a/app/policies/ip_block_policy.rb
+++ b/app/policies/ip_block_policy.rb
@@ -2,14 +2,14 @@
class IpBlockPolicy < ApplicationPolicy
def index?
- admin?
+ role.can?(:manage_blocks)
end
def create?
- admin?
+ role.can?(:manage_blocks)
end
def destroy?
- admin?
+ role.can?(:manage_blocks)
end
end
diff --git a/app/policies/preview_card_policy.rb b/app/policies/preview_card_policy.rb
index 0410987e4..a7bb41634 100644
--- a/app/policies/preview_card_policy.rb
+++ b/app/policies/preview_card_policy.rb
@@ -2,10 +2,10 @@
class PreviewCardPolicy < ApplicationPolicy
def index?
- staff?
+ role.can?(:manage_taxonomies)
end
def review?
- staff?
+ role.can?(:manage_taxonomies)
end
end
diff --git a/app/policies/preview_card_provider_policy.rb b/app/policies/preview_card_provider_policy.rb
index 44d2ad5cf..131ccb5dd 100644
--- a/app/policies/preview_card_provider_policy.rb
+++ b/app/policies/preview_card_provider_policy.rb
@@ -2,10 +2,10 @@
class PreviewCardProviderPolicy < ApplicationPolicy
def index?
- staff?
+ role.can?(:manage_taxonomies)
end
def review?
- staff?
+ role.can?(:manage_taxonomies)
end
end
diff --git a/app/policies/relay_policy.rb b/app/policies/relay_policy.rb
index bd75e2197..4305bcfaa 100644
--- a/app/policies/relay_policy.rb
+++ b/app/policies/relay_policy.rb
@@ -2,6 +2,6 @@
class RelayPolicy < ApplicationPolicy
def update?
- admin?
+ role.can?(:manage_federation)
end
end
diff --git a/app/policies/report_note_policy.rb b/app/policies/report_note_policy.rb
index 694bc096b..dc31416e8 100644
--- a/app/policies/report_note_policy.rb
+++ b/app/policies/report_note_policy.rb
@@ -2,11 +2,11 @@
class ReportNotePolicy < ApplicationPolicy
def create?
- staff?
+ role.can?(:manage_reports)
end
def destroy?
- admin? || owner?
+ owner? || (role.can?(:manage_reports) && role.overrides?(record.account.user_role))
end
private
diff --git a/app/policies/report_policy.rb b/app/policies/report_policy.rb
index 95b5c30c8..c9f7639bd 100644
--- a/app/policies/report_policy.rb
+++ b/app/policies/report_policy.rb
@@ -2,14 +2,14 @@
class ReportPolicy < ApplicationPolicy
def update?
- staff?
+ role.can?(:manage_reports)
end
def index?
- staff?
+ role.can?(:manage_reports)
end
def show?
- staff?
+ role.can?(:manage_reports)
end
end
diff --git a/app/policies/rule_policy.rb b/app/policies/rule_policy.rb
index 6a4def009..51b2a6977 100644
--- a/app/policies/rule_policy.rb
+++ b/app/policies/rule_policy.rb
@@ -2,18 +2,18 @@
class RulePolicy < ApplicationPolicy
def index?
- staff?
+ role.can?(:manage_rules)
end
def create?
- admin?
+ role.can?(:manage_rules)
end
def update?
- admin?
+ role.can?(:manage_rules)
end
def destroy?
- admin?
+ role.can?(:manage_rules)
end
end
diff --git a/app/policies/settings_policy.rb b/app/policies/settings_policy.rb
index 874f97bab..2b052af27 100644
--- a/app/policies/settings_policy.rb
+++ b/app/policies/settings_policy.rb
@@ -2,14 +2,14 @@
class SettingsPolicy < ApplicationPolicy
def update?
- admin?
+ role.can?(:manage_settings)
end
def show?
- admin?
+ role.can?(:manage_settings)
end
def destroy?
- admin?
+ role.can?(:manage_settings)
end
end
diff --git a/app/policies/status_policy.rb b/app/policies/status_policy.rb
index 400f1ec79..2f48b5d70 100644
--- a/app/policies/status_policy.rb
+++ b/app/policies/status_policy.rb
@@ -8,7 +8,7 @@ class StatusPolicy < ApplicationPolicy
end
def index?
- staff?
+ role.can?(:manage_reports, :manage_users)
end
def show?
@@ -32,17 +32,17 @@ class StatusPolicy < ApplicationPolicy
end
def destroy?
- staff? || owned?
+ role.can?(:manage_reports) || owned?
end
alias unreblog? destroy?
def update?
- staff? || owned?
+ role.can?(:manage_reports) || owned?
end
def review?
- staff?
+ role.can?(:manage_taxonomies)
end
private
diff --git a/app/policies/tag_policy.rb b/app/policies/tag_policy.rb
index bdfcec0c9..bb1d37d6c 100644
--- a/app/policies/tag_policy.rb
+++ b/app/policies/tag_policy.rb
@@ -2,18 +2,18 @@
class TagPolicy < ApplicationPolicy
def index?
- staff?
+ role.can?(:manage_taxonomies)
end
def show?
- staff?
+ role.can?(:manage_taxonomies)
end
def update?
- staff?
+ role.can?(:manage_taxonomies)
end
def review?
- staff?
+ role.can?(:manage_taxonomies)
end
end
diff --git a/app/policies/user_policy.rb b/app/policies/user_policy.rb
index 140905e1f..6751b8b8f 100644
--- a/app/policies/user_policy.rb
+++ b/app/policies/user_policy.rb
@@ -2,52 +2,38 @@
class UserPolicy < ApplicationPolicy
def reset_password?
- staff? && !record.staff?
+ role.can?(:manage_user_access) && role.overrides?(record.role)
end
def change_email?
- staff? && !record.staff?
+ role.can?(:manage_user_access) && role.overrides?(record.role)
end
def disable_2fa?
- admin? && !record.staff?
+ role.can?(:manage_user_access) && role.overrides?(record.role)
+ end
+
+ def change_role?
+ role.can?(:manage_roles) && role.overrides?(record.role)
end
def confirm?
- staff? && !record.confirmed?
+ role.can?(:manage_user_access) && !record.confirmed?
end
def enable?
- staff?
+ role.can?(:manage_users)
end
def approve?
- staff? && !record.approved?
+ role.can?(:manage_users) && !record.approved?
end
def reject?
- staff? && !record.approved?
+ role.can?(:manage_users) && !record.approved?
end
def disable?
- staff? && !record.admin?
- end
-
- def promote?
- admin? && promotable?
- end
-
- def demote?
- admin? && !record.admin? && demoteable?
- end
-
- private
-
- def promotable?
- record.approved? && (!record.staff? || !record.admin?)
- end
-
- def demoteable?
- record.staff?
+ role.can?(:manage_users) && role.overrides?(record.role)
end
end
diff --git a/app/policies/user_role_policy.rb b/app/policies/user_role_policy.rb
new file mode 100644
index 000000000..7019637fc
--- /dev/null
+++ b/app/policies/user_role_policy.rb
@@ -0,0 +1,19 @@
+# frozen_string_literal: true
+
+class UserRolePolicy < ApplicationPolicy
+ def index?
+ role.can?(:manage_roles)
+ end
+
+ def create?
+ role.can?(:manage_roles)
+ end
+
+ def update?
+ role.can?(:manage_roles) && role.overrides?(record)
+ end
+
+ def destroy?
+ !record.everyone? && role.can?(:manage_roles) && role.overrides?(record) && role.id != record.id
+ end
+end
diff --git a/app/policies/webhook_policy.rb b/app/policies/webhook_policy.rb
index 2c55703a1..a2199a333 100644
--- a/app/policies/webhook_policy.rb
+++ b/app/policies/webhook_policy.rb
@@ -2,34 +2,34 @@
class WebhookPolicy < ApplicationPolicy
def index?
- admin?
+ role.can?(:manage_webhooks)
end
def create?
- admin?
+ role.can?(:manage_webhooks)
end
def show?
- admin?
+ role.can?(:manage_webhooks)
end
def update?
- admin?
+ role.can?(:manage_webhooks)
end
def enable?
- admin?
+ role.can?(:manage_webhooks)
end
def disable?
- admin?
+ role.can?(:manage_webhooks)
end
def rotate_secret?
- admin?
+ role.can?(:manage_webhooks)
end
def destroy?
- admin?
+ role.can?(:manage_webhooks)
end
end
diff --git a/app/presenters/initial_state_presenter.rb b/app/presenters/initial_state_presenter.rb
index 06482935c..129ea2a46 100644
--- a/app/presenters/initial_state_presenter.rb
+++ b/app/presenters/initial_state_presenter.rb
@@ -3,4 +3,8 @@
class InitialStatePresenter < ActiveModelSerializers::Model
attributes :settings, :push_subscription, :token,
:current_account, :admin, :text, :visibility
+
+ def role
+ current_account&.user_role
+ end
end
diff --git a/app/serializers/initial_state_serializer.rb b/app/serializers/initial_state_serializer.rb
index 34190a91d..5eda87757 100644
--- a/app/serializers/initial_state_serializer.rb
+++ b/app/serializers/initial_state_serializer.rb
@@ -6,6 +6,7 @@ class InitialStateSerializer < ActiveModel::Serializer
:languages
has_one :push_subscription, serializer: REST::WebPushSubscriptionSerializer
+ has_one :role, serializer: REST::RoleSerializer
def meta
store = {
@@ -19,7 +20,6 @@ class InitialStateSerializer < ActiveModel::Serializer
repository: Mastodon::Version.repository,
source_url: Mastodon::Version.source_url,
version: Mastodon::Version.to_s,
- invites_enabled: Setting.min_invite_role == 'user',
limited_federation_mode: Rails.configuration.x.whitelist_mode,
mascot: instance_presenter.mascot&.file&.url,
profile_directory: Setting.profile_directory,
@@ -39,7 +39,6 @@ class InitialStateSerializer < ActiveModel::Serializer
store[:advanced_layout] = object.current_account.user.setting_advanced_layout
store[:use_blurhash] = object.current_account.user.setting_use_blurhash
store[:use_pending_items] = object.current_account.user.setting_use_pending_items
- store[:is_staff] = object.current_account.user.staff?
store[:trends] = Setting.trends && object.current_account.user.setting_trends
store[:crop_images] = object.current_account.user.setting_crop_images
else
diff --git a/app/serializers/rest/credential_account_serializer.rb b/app/serializers/rest/credential_account_serializer.rb
index be0d763dc..27e1db207 100644
--- a/app/serializers/rest/credential_account_serializer.rb
+++ b/app/serializers/rest/credential_account_serializer.rb
@@ -3,6 +3,8 @@
class REST::CredentialAccountSerializer < REST::AccountSerializer
attributes :source
+ has_one :role, serializer: REST::RoleSerializer
+
def source
user = object.user
@@ -15,4 +17,8 @@ class REST::CredentialAccountSerializer < REST::AccountSerializer
follow_requests_count: FollowRequest.where(target_account: object).limit(40).count,
}
end
+
+ def role
+ object.user_role
+ end
end
diff --git a/app/serializers/rest/instance_serializer.rb b/app/serializers/rest/instance_serializer.rb
index f5dec0dac..9cc245422 100644
--- a/app/serializers/rest/instance_serializer.rb
+++ b/app/serializers/rest/instance_serializer.rb
@@ -93,7 +93,7 @@ class REST::InstanceSerializer < ActiveModel::Serializer
end
def invites_enabled
- Setting.min_invite_role == 'user'
+ UserRole.everyone.can?(:invite_users)
end
private
diff --git a/app/serializers/rest/role_serializer.rb b/app/serializers/rest/role_serializer.rb
new file mode 100644
index 000000000..5b81c6e04
--- /dev/null
+++ b/app/serializers/rest/role_serializer.rb
@@ -0,0 +1,13 @@
+# frozen_string_literal: true
+
+class REST::RoleSerializer < ActiveModel::Serializer
+ attributes :id, :name, :permissions, :color, :highlighted
+
+ def id
+ object.id.to_s
+ end
+
+ def permissions
+ object.computed_permissions.to_s
+ end
+end
diff --git a/app/services/account_search_service.rb b/app/services/account_search_service.rb
index 6fe4b6593..4dcae20eb 100644
--- a/app/services/account_search_service.rb
+++ b/app/services/account_search_service.rb
@@ -61,11 +61,11 @@ class AccountSearchService < BaseService
end
def advanced_search_results
- Account.advanced_search_for(terms_for_query, account, limit_for_non_exact_results, options[:following], offset)
+ Account.advanced_search_for(terms_for_query, account, limit: limit_for_non_exact_results, following: options[:following], offset: offset)
end
def simple_search_results
- Account.search_for(terms_for_query, limit_for_non_exact_results, offset)
+ Account.search_for(terms_for_query, limit: limit_for_non_exact_results, offset: offset)
end
def from_elasticsearch
diff --git a/app/services/appeal_service.rb b/app/services/appeal_service.rb
index cef9be05f..399a053d6 100644
--- a/app/services/appeal_service.rb
+++ b/app/services/appeal_service.rb
@@ -22,7 +22,7 @@ class AppealService < BaseService
end
def notify_staff!
- User.staff.includes(:account).each do |u|
+ User.those_who_can(:manage_appeals).includes(:account).each do |u|
AdminMailer.new_appeal(u.account, @appeal).deliver_later if u.allows_appeal_emails?
end
end
diff --git a/app/services/bootstrap_timeline_service.rb b/app/services/bootstrap_timeline_service.rb
index a02e55a6d..126c0fa2e 100644
--- a/app/services/bootstrap_timeline_service.rb
+++ b/app/services/bootstrap_timeline_service.rb
@@ -17,7 +17,7 @@ class BootstrapTimelineService < BaseService
end
def notify_staff!
- User.staff.includes(:account).find_each do |user|
+ User.those_who_can(:manage_users).includes(:account).find_each do |user|
LocalNotificationWorker.perform_async(user.account_id, @source_account.id, 'Account', 'admin.sign_up')
end
end
diff --git a/app/services/notify_service.rb b/app/services/notify_service.rb
index d30b33876..c7454fc60 100644
--- a/app/services/notify_service.rb
+++ b/app/services/notify_service.rb
@@ -76,7 +76,7 @@ class NotifyService < BaseService
end
def from_staff?
- @notification.from_account.local? && @notification.from_account.user.present? && @notification.from_account.user.staff?
+ @notification.from_account.local? && @notification.from_account.user.present? && @notification.from_account.user_role&.overrides?(@recipient.user_role)
end
def optional_non_following_and_direct?
diff --git a/app/services/report_service.rb b/app/services/report_service.rb
index bd67ff8d3..8c92cf334 100644
--- a/app/services/report_service.rb
+++ b/app/services/report_service.rb
@@ -38,7 +38,7 @@ class ReportService < BaseService
def notify_staff!
return if @report.unresolved_siblings?
- User.staff.includes(:account).each do |u|
+ User.those_who_can(:manage_reports).includes(:account).each do |u|
LocalNotificationWorker.perform_async(u.account_id, @report.id, 'Report', 'admin.report')
AdminMailer.new_report(u.account, @report).deliver_later if u.allows_report_emails?
end
diff --git a/app/views/admin/accounts/index.html.haml b/app/views/admin/accounts/index.html.haml
index 60e4894d0..7560fac7a 100644
--- a/app/views/admin/accounts/index.html.haml
+++ b/app/views/admin/accounts/index.html.haml
@@ -4,45 +4,36 @@
- content_for :header_tags do
= javascript_pack_tag 'admin', async: true, crossorigin: 'anonymous'
-.filters
- .filter-subset
- %strong= t('admin.accounts.location.title')
- %ul
- %li= filter_link_to t('generic.all'), origin: nil
- %li= filter_link_to t('admin.accounts.location.local'), origin: 'local'
- %li= filter_link_to t('admin.accounts.location.remote'), origin: 'remote'
- .filter-subset
- %strong= t('admin.accounts.moderation.title')
- %ul
- %li= filter_link_to t('generic.all'), status: nil
- %li= filter_link_to t('admin.accounts.moderation.active'), status: 'active'
- %li= filter_link_to t('admin.accounts.moderation.suspended'), status: 'suspended'
- %li= filter_link_to safe_join([t('admin.accounts.moderation.pending'), "(#{number_with_delimiter(User.pending.count)})"], ' '), status: 'pending'
- .filter-subset
- %strong= t('admin.accounts.role')
- %ul
- %li= filter_link_to t('admin.accounts.moderation.all'), permissions: nil
- %li= filter_link_to t('admin.accounts.roles.staff'), permissions: 'staff'
- .filter-subset
- %strong= t 'generic.order_by'
- %ul
- %li= filter_link_to t('relationships.most_recent'), order: nil
- %li= filter_link_to t('relationships.last_active'), order: 'active'
-
= form_tag admin_accounts_url, method: 'GET', class: 'simple_form' do
- .fields-group
- - (AccountFilter::KEYS - %i(origin status permissions)).each do |key|
- - if params[key].present?
- = hidden_field_tag key, params[key]
+ .filters
+ .filter-subset.filter-subset--with-select
+ %strong= t('admin.accounts.location.title')
+ .input.select.optional
+ = select_tag :origin, options_for_select([[t('admin.accounts.location.local'), 'local'], [t('admin.accounts.location.remote'), 'remote']], params[:origin]), prompt: I18n.t('generic.all')
+ .filter-subset.filter-subset--with-select
+ %strong= t('admin.accounts.moderation.title')
+ .input.select.optional
+ = select_tag :status, options_for_select([[t('admin.accounts.moderation.active'), 'active'], [t('admin.accounts.moderation.silenced'), 'silenced'], [t('admin.accounts.moderation.suspended'), 'suspended'], [safe_join([t('admin.accounts.moderation.pending'), "(#{number_with_delimiter(User.pending.count)})"], ' '), 'pending']], params[:status]), prompt: I18n.t('generic.all')
+ .filter-subset.filter-subset--with-select
+ %strong= t('admin.accounts.role')
+ .input.select.optional
+ = select_tag :role_ids, options_from_collection_for_select(UserRole.assignable, :id, :name, params[:role_ids]), prompt: I18n.t('admin.accounts.moderation.all')
+ .filter-subset.filter-subset--with-select
+ %strong= t 'generic.order_by'
+ .input.select
+ = select_tag :order, options_for_select([[t('relationships.most_recent'), nil], [t('relationships.last_active'), 'active']], params[:order])
+ .fields-group
- %i(username by_domain display_name email ip).each do |key|
- unless key == :by_domain && params[:origin] != 'remote'
.input.string.optional
= text_field_tag key, params[key], class: 'string optional', placeholder: I18n.t("admin.accounts.#{key}")
- .actions
- %button.button= t('admin.accounts.search')
- = link_to t('admin.accounts.reset'), admin_accounts_path, class: 'button negative'
+ .actions
+ %button.button= t('admin.accounts.search')
+ = link_to t('admin.accounts.reset'), admin_accounts_path, class: 'button negative'
+
+%hr.spacer/
= form_for(@form, url: batch_admin_accounts_path) do |f|
= hidden_field_tag :page, params[:page] || 1
diff --git a/app/views/admin/accounts/show.html.haml b/app/views/admin/accounts/show.html.haml
index a69832b04..dc3b35956 100644
--- a/app/views/admin/accounts/show.html.haml
+++ b/app/views/admin/accounts/show.html.haml
@@ -92,10 +92,13 @@
%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)
+ - if @account.user_role&.everyone?
+ = t('admin.accounts.no_role_assigned')
+ - else
+ = @account.user_role&.name
+ %td
+ = table_link_to 'vcard', t('admin.accounts.change_role.label'), admin_user_role_path(@account.user) if can?(:change_role, @account.user)
%tr
%th{ rowspan: can?(:create, :email_domain_block) ? 3 : 2 }= t('admin.accounts.email')
diff --git a/app/views/admin/action_logs/index.html.haml b/app/views/admin/action_logs/index.html.haml
index f611bfe9d..d8b7132f5 100644
--- a/app/views/admin/action_logs/index.html.haml
+++ b/app/views/admin/action_logs/index.html.haml
@@ -11,7 +11,7 @@
.filter-subset.filter-subset--with-select
%strong= t('admin.action_logs.filter_by_user')
.input.select.optional
- = select_tag :account_id, options_from_collection_for_select(Account.joins(:user).merge(User.staff), :id, :username, params[:account_id]), prompt: I18n.t('admin.accounts.moderation.all')
+ = select_tag :account_id, options_from_collection_for_select(@auditable_accounts, :id, :username, params[:account_id]), prompt: I18n.t('admin.accounts.moderation.all')
.filter-subset.filter-subset--with-select
%strong= t('admin.action_logs.filter_by_action')
diff --git a/app/views/admin/instances/show.html.haml b/app/views/admin/instances/show.html.haml
index ef4de602d..ab290912e 100644
--- a/app/views/admin/instances/show.html.haml
+++ b/app/views/admin/instances/show.html.haml
@@ -4,32 +4,33 @@
- content_for :header_tags do
= javascript_pack_tag 'admin', async: true, crossorigin: 'anonymous'
-- content_for :heading_actions do
- = l(@time_period.first)
- = ' - '
- = l(@time_period.last)
+- if current_user.can?(:view_dashboard)
+ - content_for :heading_actions do
+ = l(@time_period.first)
+ = ' - '
+ = l(@time_period.last)
-%p
- = fa_icon 'info fw'
- = t('admin.instances.totals_time_period_hint_html')
+ %p
+ = fa_icon 'info fw'
+ = t('admin.instances.totals_time_period_hint_html')
-.dashboard
- .dashboard__item
- = react_admin_component :counter, measure: 'instance_accounts', start_at: @time_period.first, end_at: @time_period.last, params: { domain: @instance.domain }, label: t('admin.instances.dashboard.instance_accounts_measure'), href: admin_accounts_path(origin: 'remote', by_domain: @instance.domain)
- .dashboard__item
- = react_admin_component :counter, measure: 'instance_statuses', start_at: @time_period.first, end_at: @time_period.last, params: { domain: @instance.domain }, label: t('admin.instances.dashboard.instance_statuses_measure')
- .dashboard__item
- = react_admin_component :counter, measure: 'instance_media_attachments', start_at: @time_period.first, end_at: @time_period.last, params: { domain: @instance.domain }, label: t('admin.instances.dashboard.instance_media_attachments_measure')
- .dashboard__item
- = react_admin_component :counter, measure: 'instance_follows', start_at: @time_period.first, end_at: @time_period.last, params: { domain: @instance.domain }, label: t('admin.instances.dashboard.instance_follows_measure')
- .dashboard__item
- = react_admin_component :counter, measure: 'instance_followers', start_at: @time_period.first, end_at: @time_period.last, params: { domain: @instance.domain }, label: t('admin.instances.dashboard.instance_followers_measure')
- .dashboard__item
- = react_admin_component :counter, measure: 'instance_reports', start_at: @time_period.first, end_at: @time_period.last, params: { domain: @instance.domain }, label: t('admin.instances.dashboard.instance_reports_measure'), href: admin_reports_path(by_target_domain: @instance.domain)
- .dashboard__item
- = react_admin_component :dimension, dimension: 'instance_accounts', start_at: @time_period.first, end_at: @time_period.last, params: { domain: @instance.domain }, limit: 8, label: t('admin.instances.dashboard.instance_accounts_dimension')
- .dashboard__item
- = react_admin_component :dimension, dimension: 'instance_languages', start_at: @time_period.first, end_at: @time_period.last, params: { domain: @instance.domain }, limit: 8, label: t('admin.instances.dashboard.instance_languages_dimension')
+ .dashboard
+ .dashboard__item
+ = react_admin_component :counter, measure: 'instance_accounts', start_at: @time_period.first, end_at: @time_period.last, params: { domain: @instance.domain }, label: t('admin.instances.dashboard.instance_accounts_measure'), href: admin_accounts_path(origin: 'remote', by_domain: @instance.domain)
+ .dashboard__item
+ = react_admin_component :counter, measure: 'instance_statuses', start_at: @time_period.first, end_at: @time_period.last, params: { domain: @instance.domain }, label: t('admin.instances.dashboard.instance_statuses_measure')
+ .dashboard__item
+ = react_admin_component :counter, measure: 'instance_media_attachments', start_at: @time_period.first, end_at: @time_period.last, params: { domain: @instance.domain }, label: t('admin.instances.dashboard.instance_media_attachments_measure')
+ .dashboard__item
+ = react_admin_component :counter, measure: 'instance_follows', start_at: @time_period.first, end_at: @time_period.last, params: { domain: @instance.domain }, label: t('admin.instances.dashboard.instance_follows_measure')
+ .dashboard__item
+ = react_admin_component :counter, measure: 'instance_followers', start_at: @time_period.first, end_at: @time_period.last, params: { domain: @instance.domain }, label: t('admin.instances.dashboard.instance_followers_measure')
+ .dashboard__item
+ = react_admin_component :counter, measure: 'instance_reports', start_at: @time_period.first, end_at: @time_period.last, params: { domain: @instance.domain }, label: t('admin.instances.dashboard.instance_reports_measure'), href: admin_reports_path(by_target_domain: @instance.domain)
+ .dashboard__item
+ = react_admin_component :dimension, dimension: 'instance_accounts', start_at: @time_period.first, end_at: @time_period.last, params: { domain: @instance.domain }, limit: 8, label: t('admin.instances.dashboard.instance_accounts_dimension')
+ .dashboard__item
+ = react_admin_component :dimension, dimension: 'instance_languages', start_at: @time_period.first, end_at: @time_period.last, params: { domain: @instance.domain }, limit: 8, label: t('admin.instances.dashboard.instance_languages_dimension')
%hr.spacer/
diff --git a/app/views/admin/roles/_form.html.haml b/app/views/admin/roles/_form.html.haml
new file mode 100644
index 000000000..68607ce68
--- /dev/null
+++ b/app/views/admin/roles/_form.html.haml
@@ -0,0 +1,37 @@
+= simple_form_for @role, url: @role.new_record? ? admin_roles_path : admin_role_path(@role) do |f|
+ = render 'shared/error_messages', object: @role
+
+ - if @role.everyone?
+ .flash-message.info
+ = t('admin.roles.everyone_full_description_html')
+ - else
+ .fields-group
+ = f.input :name, wrapper: :with_label
+
+ .fields-group
+ = f.input :position, wrapper: :with_label
+
+ .fields-group
+ = f.input :color, wrapper: :with_label, input_html: { placeholder: '#000000' }
+
+ %hr.spacer/
+
+ .fields-group
+ = f.input :highlighted, wrapper: :with_label
+
+ %hr.spacer/
+
+ .field-group
+ .input.with_block_label
+ %label= t('simple_form.labels.user_role.permissions_as_keys')
+ %span.hint= t('simple_form.hints.user_role.permissions_as_keys')
+
+ - (@role.everyone? ? UserRole::Flags::CATEGORIES.slice(:invites) : UserRole::Flags::CATEGORIES).each do |category, permissions|
+ %h4= t(category, scope: 'admin.roles.categories')
+
+ = f.input :permissions_as_keys, collection: permissions, wrapper: :with_block_label, include_blank: false, label_method: lambda { |privilege| safe_join([t("admin.roles.privileges.#{privilege}"), content_tag(:span, t("admin.roles.privileges.#{privilege}_description"), class: 'hint')]) }, required: false, as: :check_boxes, collection_wrapper_tag: 'ul', item_wrapper_tag: 'li', label: false, hint: false
+
+ %hr.spacer/
+
+ .actions
+ = f.button :button, @role.new_record? ? t('admin.roles.add_new') : t('generic.save_changes'), type: :submit
diff --git a/app/views/admin/roles/_role.html.haml b/app/views/admin/roles/_role.html.haml
new file mode 100644
index 000000000..6804f4f15
--- /dev/null
+++ b/app/views/admin/roles/_role.html.haml
@@ -0,0 +1,18 @@
+.announcements-list__item
+ = link_to edit_admin_role_path(role), class: 'announcements-list__item__title' do
+ %span.user-role{ class: "user-role-#{role.id}" }
+ = fa_icon 'users fw'
+
+ - if role.everyone?
+ = t('admin.roles.everyone')
+ - else
+ = role.name
+
+ .announcements-list__item__action-bar
+ .announcements-list__item__meta
+ - if role.everyone?
+ = t('admin.roles.everyone_full_description_html')
+ - else
+ = link_to t('admin.roles.assigned_users', count: role.users.count), admin_accounts_path(role_id: role.id)
+ •
+ %abbr{ title: role.permissions_as_keys.map { |privilege| I18n.t("admin.roles.privileges.#{privilege}") }.join(', ') }= t('admin.roles.permissions_count', count: role.permissions_as_keys.size)
diff --git a/app/views/admin/roles/edit.html.haml b/app/views/admin/roles/edit.html.haml
new file mode 100644
index 000000000..659ccb8dc
--- /dev/null
+++ b/app/views/admin/roles/edit.html.haml
@@ -0,0 +1,8 @@
+- content_for :page_title do
+ = t('admin.roles.edit', name: @role.everyone? ? t('admin.roles.everyone') : @role.name)
+
+- content_for :heading_actions do
+ = link_to t('admin.roles.delete'), admin_role_path(@role), method: :delete, data: { confirm: t('admin.accounts.are_you_sure') }, class: 'button button--destructive' if can?(:destroy, @role)
+
+= render partial: 'form'
+
diff --git a/app/views/admin/roles/index.html.haml b/app/views/admin/roles/index.html.haml
new file mode 100644
index 000000000..4f6c511b4
--- /dev/null
+++ b/app/views/admin/roles/index.html.haml
@@ -0,0 +1,17 @@
+- content_for :page_title do
+ = t('admin.roles.title')
+
+- content_for :heading_actions do
+ = link_to t('admin.roles.add_new'), new_admin_role_path, class: 'button' if can?(:create, :user_role)
+
+%p= t('admin.roles.description_html')
+
+%hr.spacer/
+
+.applications-list
+ = render partial: 'role', collection: @roles.select(&:everyone?)
+
+%hr.spacer/
+
+.applications-list
+ = render partial: 'role', collection: @roles.reject(&:everyone?)
diff --git a/app/views/admin/roles/new.html.haml b/app/views/admin/roles/new.html.haml
new file mode 100644
index 000000000..821079271
--- /dev/null
+++ b/app/views/admin/roles/new.html.haml
@@ -0,0 +1,4 @@
+- content_for :page_title do
+ = t('admin.roles.add_new')
+
+= render partial: 'form'
diff --git a/app/views/admin/settings/edit.html.haml b/app/views/admin/settings/edit.html.haml
index 33bfc43d3..d7896bbc0 100644
--- a/app/views/admin/settings/edit.html.haml
+++ b/app/views/admin/settings/edit.html.haml
@@ -62,9 +62,6 @@
= f.input :show_known_fediverse_at_about_page, as: :boolean, wrapper: :with_label, label: t('admin.settings.show_known_fediverse_at_about_page.title'), hint: t('admin.settings.show_known_fediverse_at_about_page.desc_html')
.fields-group
- = f.input :show_staff_badge, as: :boolean, wrapper: :with_label, label: t('admin.settings.show_staff_badge.title'), hint: t('admin.settings.show_staff_badge.desc_html')
-
- .fields-group
= f.input :open_deletion, as: :boolean, wrapper: :with_label, label: t('admin.settings.registrations.deletion.title'), hint: t('admin.settings.registrations.deletion.desc_html')
- unless whitelist_mode?
@@ -91,9 +88,6 @@
%hr.spacer/
- .fields-group
- = f.input :min_invite_role, wrapper: :with_label, collection: %i(disabled user moderator admin), label: t('admin.settings.registrations.min_invite_role.title'), label_method: lambda { |role| role == :disabled ? t('admin.settings.registrations.min_invite_role.disabled') : t("admin.accounts.roles.#{role}") }, include_blank: false, collection_wrapper_tag: 'ul', item_wrapper_tag: 'li'
-
.fields-row
.fields-row__column.fields-row__column-6.fields-group
= f.input :show_domain_blocks, wrapper: :with_label, collection: %i(disabled users all), label: t('admin.settings.domain_blocks.title'), label_method: lambda { |value| t("admin.settings.domain_blocks.#{value}") }, include_blank: false, collection_wrapper_tag: 'ul', item_wrapper_tag: 'li'
diff --git a/app/views/admin/tags/show.html.haml b/app/views/admin/tags/show.html.haml
index df72bd5f5..fd9acce4a 100644
--- a/app/views/admin/tags/show.html.haml
+++ b/app/views/admin/tags/show.html.haml
@@ -4,49 +4,50 @@
- content_for :page_title do
= "##{@tag.name}"
-- content_for :heading_actions do
- = l(@time_period.first)
- = ' - '
- = l(@time_period.last)
+- if current_user.can?(:view_dashboard)
+ - content_for :heading_actions do
+ = l(@time_period.first)
+ = ' - '
+ = l(@time_period.last)
-.dashboard
- .dashboard__item
- = react_admin_component :counter, measure: 'tag_accounts', start_at: @time_period.first, end_at: @time_period.last, params: { id: @tag.id }, label: t('admin.trends.tags.dashboard.tag_accounts_measure'), href: tag_url(@tag), target: '_blank'
- .dashboard__item
- = react_admin_component :counter, measure: 'tag_uses', start_at: @time_period.first, end_at: @time_period.last, params: { id: @tag.id }, label: t('admin.trends.tags.dashboard.tag_uses_measure')
- .dashboard__item
- = react_admin_component :counter, measure: 'tag_servers', start_at: @time_period.first, end_at: @time_period.last, params: { id: @tag.id }, label: t('admin.trends.tags.dashboard.tag_servers_measure')
- .dashboard__item
- = react_admin_component :dimension, dimension: 'tag_servers', start_at: @time_period.first, end_at: @time_period.last, params: { id: @tag.id }, limit: 8, label: t('admin.trends.tags.dashboard.tag_servers_dimension')
- .dashboard__item
- = react_admin_component :dimension, dimension: 'tag_languages', start_at: @time_period.first, end_at: @time_period.last, params: { id: @tag.id }, limit: 8, label: t('admin.trends.tags.dashboard.tag_languages_dimension')
- .dashboard__item
- = link_to admin_tag_path(@tag.id), class: ['dashboard__quick-access', @tag.usable? ? 'positive' : 'negative'] do
- - if @tag.usable?
- %span= t('admin.trends.tags.usable')
- = fa_icon 'check fw'
- - else
- %span= t('admin.trends.tags.not_usable')
- = fa_icon 'lock fw'
+ .dashboard
+ .dashboard__item
+ = react_admin_component :counter, measure: 'tag_accounts', start_at: @time_period.first, end_at: @time_period.last, params: { id: @tag.id }, label: t('admin.trends.tags.dashboard.tag_accounts_measure'), href: tag_url(@tag), target: '_blank'
+ .dashboard__item
+ = react_admin_component :counter, measure: 'tag_uses', start_at: @time_period.first, end_at: @time_period.last, params: { id: @tag.id }, label: t('admin.trends.tags.dashboard.tag_uses_measure')
+ .dashboard__item
+ = react_admin_component :counter, measure: 'tag_servers', start_at: @time_period.first, end_at: @time_period.last, params: { id: @tag.id }, label: t('admin.trends.tags.dashboard.tag_servers_measure')
+ .dashboard__item
+ = react_admin_component :dimension, dimension: 'tag_servers', start_at: @time_period.first, end_at: @time_period.last, params: { id: @tag.id }, limit: 8, label: t('admin.trends.tags.dashboard.tag_servers_dimension')
+ .dashboard__item
+ = react_admin_component :dimension, dimension: 'tag_languages', start_at: @time_period.first, end_at: @time_period.last, params: { id: @tag.id }, limit: 8, label: t('admin.trends.tags.dashboard.tag_languages_dimension')
+ .dashboard__item
+ = link_to admin_tag_path(@tag.id), class: ['dashboard__quick-access', @tag.usable? ? 'positive' : 'negative'] do
+ - if @tag.usable?
+ %span= t('admin.trends.tags.usable')
+ = fa_icon 'check fw'
+ - else
+ %span= t('admin.trends.tags.not_usable')
+ = fa_icon 'lock fw'
- = link_to admin_tag_path(@tag.id), class: ['dashboard__quick-access', @tag.trendable? ? 'positive' : 'negative'] do
- - if @tag.trendable?
- %span= t('admin.trends.tags.trendable')
- = fa_icon 'check fw'
- - else
- %span= t('admin.trends.tags.not_trendable')
- = fa_icon 'lock fw'
+ = link_to admin_tag_path(@tag.id), class: ['dashboard__quick-access', @tag.trendable? ? 'positive' : 'negative'] do
+ - if @tag.trendable?
+ %span= t('admin.trends.tags.trendable')
+ = fa_icon 'check fw'
+ - else
+ %span= t('admin.trends.tags.not_trendable')
+ = fa_icon 'lock fw'
- = link_to admin_tag_path(@tag.id), class: ['dashboard__quick-access', @tag.listable? ? 'positive' : 'negative'] do
- - if @tag.listable?
- %span= t('admin.trends.tags.listable')
- = fa_icon 'check fw'
- - else
- %span= t('admin.trends.tags.not_listable')
- = fa_icon 'lock fw'
+ = link_to admin_tag_path(@tag.id), class: ['dashboard__quick-access', @tag.listable? ? 'positive' : 'negative'] do
+ - if @tag.listable?
+ %span= t('admin.trends.tags.listable')
+ = fa_icon 'check fw'
+ - else
+ %span= t('admin.trends.tags.not_listable')
+ = fa_icon 'lock fw'
-%hr.spacer/
+ %hr.spacer/
= simple_form_for @tag, url: admin_tag_path(@tag.id) do |f|
= render 'shared/error_messages', object: @tag
diff --git a/app/views/admin/users/roles/show.html.haml b/app/views/admin/users/roles/show.html.haml
new file mode 100644
index 000000000..821618060
--- /dev/null
+++ b/app/views/admin/users/roles/show.html.haml
@@ -0,0 +1,9 @@
+- content_for :page_title do
+ = t('admin.accounts.change_role.title', username: @user.account.username)
+
+= simple_form_for @user, url: admin_user_role_path(@user) do |f|
+ .fields-group
+ = f.association :role, wrapper: :with_block_label, collection: UserRole.assignable, label_method: :name, include_blank: I18n.t('admin.accounts.change_role.no_role')
+
+ .actions
+ = f.button :button, t('generic.save_changes'), type: :submit
diff --git a/app/views/custom_css/show.css.erb b/app/views/custom_css/show.css.erb
new file mode 100644
index 000000000..521834832
--- /dev/null
+++ b/app/views/custom_css/show.css.erb
@@ -0,0 +1,10 @@
+<%- if Setting.custom_css.present? %>
+<%= Setting.custom_css %>
+
+<%- end %>
+<%- UserRole.where(highlighted: true).select { |role| role.color.present? }.each do |role| %>
+.user-role-<%= role.id %> {
+ --user-role-accent: <%= role.color %>;
+}
+
+<%- end %>
diff --git a/app/views/layouts/application.html.haml b/app/views/layouts/application.html.haml
index 25fd5bc34..bf164223c 100755
--- a/app/views/layouts/application.html.haml
+++ b/app/views/layouts/application.html.haml
@@ -34,9 +34,7 @@
%meta{ name: 'style-nonce', content: request.content_security_policy_nonce }
= stylesheet_link_tag '/inert.css', skip_pipeline: true, media: 'all', id: 'inert-style'
-
- - if Setting.custom_css.present?
- = stylesheet_link_tag custom_css_path, host: request.host, media: 'all'
+ = stylesheet_link_tag custom_css_path, host: default_url_options[:host], media: 'all'
= yield :header_tags
diff --git a/app/views/settings/preferences/notifications/show.html.haml b/app/views/settings/preferences/notifications/show.html.haml
index 42754a852..bc7afb993 100644
--- a/app/views/settings/preferences/notifications/show.html.haml
+++ b/app/views/settings/preferences/notifications/show.html.haml
@@ -18,12 +18,10 @@
= ff.input :reblog, as: :boolean, wrapper: :with_label
= ff.input :favourite, as: :boolean, wrapper: :with_label
= ff.input :mention, as: :boolean, wrapper: :with_label
-
- - if current_user.staff?
- = ff.input :report, as: :boolean, wrapper: :with_label
- = ff.input :appeal, as: :boolean, wrapper: :with_label
- = ff.input :pending_account, as: :boolean, wrapper: :with_label
- = ff.input :trending_tag, as: :boolean, wrapper: :with_label
+ = ff.input :report, as: :boolean, wrapper: :with_label if current_user.can?(:manage_reports)
+ = ff.input :appeal, as: :boolean, wrapper: :with_label if current_user.can?(:manage_appeals)
+ = ff.input :pending_account, as: :boolean, wrapper: :with_label if current_user.can?(:manage_users)
+ = ff.input :trending_tag, as: :boolean, wrapper: :with_label if current_user.can?(:manage_taxonomies)
.fields-group
= f.input :setting_always_send_emails, as: :boolean, wrapper: :with_label
diff --git a/config/application.rb b/config/application.rb
index 24fa2a978..06360832c 100644
--- a/config/application.rb
+++ b/config/application.rb
@@ -44,6 +44,7 @@ require_relative '../lib/webpacker/helper_extensions'
require_relative '../lib/rails/engine_extensions'
require_relative '../lib/active_record/database_tasks_extensions'
require_relative '../lib/active_record/batches'
+require_relative '../lib/simple_navigation/item_extensions'
Dotenv::Railtie.load
diff --git a/config/locales/activerecord.en.yml b/config/locales/activerecord.en.yml
index 720b0f5e3..daeed58b8 100644
--- a/config/locales/activerecord.en.yml
+++ b/config/locales/activerecord.en.yml
@@ -38,3 +38,12 @@ en:
email:
blocked: uses a disallowed e-mail provider
unreachable: does not seem to exist
+ role_id:
+ elevated: cannot be higher than your current role
+ user_role:
+ attributes:
+ permissions_as_keys:
+ dangerous: include permissions that are not safe for the base role
+ elevated: cannot include permissions your current role does not possess
+ position:
+ elevated: cannot be higher than your current role
diff --git a/config/locales/en.yml b/config/locales/en.yml
index 91ae3a3bc..2cd4f45ac 100644
--- a/config/locales/en.yml
+++ b/config/locales/en.yml
@@ -83,10 +83,8 @@ en:
posts_tab_heading: Posts
posts_with_replies: Posts and replies
roles:
- admin: Admin
bot: Bot
group: Group
- moderator: Mod
unavailable: Profile unavailable
unfollow: Unfollow
admin:
@@ -105,12 +103,17 @@ en:
avatar: Avatar
by_domain: Domain
change_email:
- changed_msg: Account email successfully changed!
+ changed_msg: Email successfully changed!
current_email: Current email
label: Change email
new_email: New email
submit: Change email
title: Change email for %{username}
+ change_role:
+ changed_msg: Role successfully changed!
+ label: Change role
+ no_role: No role
+ title: Change role for %{username}
confirm: Confirm
confirmed: Confirmed
confirming: Confirming
@@ -154,6 +157,7 @@ en:
active: Active
all: All
pending: Pending
+ silenced: Limited
suspended: Suspended
title: Moderation
moderation_notes: Moderation notes
@@ -161,6 +165,7 @@ en:
most_recent_ip: Most recent IP
no_account_selected: No accounts were changed as none were selected
no_limits_imposed: No limits imposed
+ no_role_assigned: No role assigned
not_subscribed: Not subscribed
pending: Pending review
perform_full_suspension: Suspend
@@ -187,12 +192,7 @@ en:
reset: Reset
reset_password: Reset password
resubscribe: Resubscribe
- role: Permissions
- roles:
- admin: Administrator
- moderator: Moderator
- staff: Staff
- user: User
+ role: Role
search: Search
search_same_email_domain: Other users with the same e-mail domain
search_same_ip: Other users with the same IP
@@ -649,6 +649,67 @@ en:
unresolved: Unresolved
updated_at: Updated
view_profile: View profile
+ roles:
+ add_new: Add role
+ assigned_users:
+ one: "%{count} user"
+ other: "%{count} users"
+ categories:
+ administration: Administration
+ devops: Devops
+ invites: Invites
+ moderation: Moderation
+ special: Special
+ delete: Delete
+ description_html: With <strong>user roles</strong>, you can customize which functions and areas of Mastodon your users can access.
+ edit: Edit '%{name}' role
+ everyone: Default permissions
+ everyone_full_description_html: This is the <strong>base role</strong> affecting <strong>all users</strong>, even those without an assigned role. All other roles inherit permissions from it.
+ permissions_count:
+ one: "%{count} permission"
+ other: "%{count} permissions"
+ privileges:
+ administrator: Administrator
+ administrator_description: Users with this permission will bypass every permission
+ delete_user_data: Delete User Data
+ delete_user_data_description: Allows users to delete other users' data without delay
+ invite_users: Invite Users
+ invite_users_description: Allows users to invite new people to the server
+ manage_announcements: Manage Announcements
+ manage_announcements_description: Allows users to manage announcements on the server
+ manage_appeals: Manage Appeals
+ manage_appeals_description: Allows users to review appeals against moderation actions
+ manage_blocks: Manage Blocks
+ manage_blocks_description: Allows users to block e-mail providers and IP addresses
+ manage_custom_emojis: Manage Custom Emojis
+ manage_custom_emojis_description: Allows users to manage custom emojis on the server
+ manage_federation: Manage Federation
+ manage_federation_description: Allows users to block or allow federation with other domains, and control deliverability
+ manage_invites: Manage Invites
+ manage_invites_description: Allows users to browse and deactivate invite links
+ manage_reports: Manage Reports
+ manage_reports_description: Allows users to review reports and perform moderation actions against them
+ manage_roles: Manage Roles
+ manage_roles_description: Allows users to manage and assign roles below theirs
+ manage_rules: Manage Rules
+ manage_rules_description: Allows users to change server rules
+ manage_settings: Manage Settings
+ manage_settings_description: Allows users to change site settings
+ manage_taxonomies: Manage Taxonomies
+ manage_taxonomies_description: Allows users to review trending content and update hashtag settings
+ manage_user_access: Manage User Access
+ manage_user_access_description: Allows users to disable other users' two-factor authentication, change their e-mail address, and reset their password
+ manage_users: Manage Users
+ manage_users_description: Allows users to view other users' details and perform moderation actions against them
+ manage_webhooks: Manage Webhooks
+ manage_webhooks_description: Allows users to set up webhooks for administrative events
+ view_audit_log: View Audit Log
+ view_audit_log_description: Allows users to see a history of administrative actions on the server
+ view_dashboard: View Dashboard
+ view_dashboard_description: Allows users to access the dashboard and various metrics
+ view_devops: Devops
+ view_devops_description: Allows users to access Sidekiq and pgHero dashboards
+ title: Roles
rules:
add_new: Add rule
delete: Delete
@@ -701,9 +762,6 @@ en:
deletion:
desc_html: Allow anyone to delete their account
title: Open account deletion
- min_invite_role:
- disabled: No one
- title: Allow invitations by
require_invite_text:
desc_html: When registrations require manual approval, make the “Why do you want to join?” text input mandatory rather than optional
title: Require new users to enter a reason to join
@@ -716,9 +774,6 @@ en:
show_known_fediverse_at_about_page:
desc_html: When disabled, restricts the public timeline linked from the landing page to showing only local content
title: Include federated content on unauthenticated public timeline page
- show_staff_badge:
- desc_html: Show a staff badge on a user page
- title: Show staff badge
site_description:
desc_html: Introductory paragraph on the API. Describe what makes this Mastodon server special and anything else important. You can use HTML tags, in particular <code>&lt;a&gt;</code> and <code>&lt;em&gt;</code>.
title: Server description
diff --git a/config/locales/simple_form.en.yml b/config/locales/simple_form.en.yml
index ea4f68562..932f34d82 100644
--- a/config/locales/simple_form.en.yml
+++ b/config/locales/simple_form.en.yml
@@ -96,6 +96,13 @@ en:
name: You can only change the casing of the letters, for example, to make it more readable
user:
chosen_languages: When checked, only posts in selected languages will be displayed in public timelines
+ role: The role controls which permissions the user has
+ user_role:
+ color: Color to be used for the role throughout the UI, as RGB in hex format
+ highlighted: This makes the role publicly visible
+ name: Public name of the role, if role is set to be displayed as a badge
+ permissions_as_keys: Users with this role will have access to...
+ position: Higher role decides conflict resolution in certain situations
webhook:
events: Select events to send
url: Where events will be sent to
@@ -232,6 +239,14 @@ en:
name: Hashtag
trendable: Allow this hashtag to appear under trends
usable: Allow posts to use this hashtag
+ user:
+ role: Role
+ user_role:
+ color: Badge color
+ highlighted: Display role as badge on user profiles
+ name: Name
+ permissions_as_keys: Permissions
+ position: Priority
webhook:
events: Enabled events
url: Endpoint URL
diff --git a/config/navigation.rb b/config/navigation.rb
index ec5719e3e..706de0471 100644
--- a/config/navigation.rb
+++ b/config/navigation.rb
@@ -2,66 +2,67 @@
SimpleNavigation::Configuration.run do |navigation|
navigation.items do |n|
- n.item :web, safe_join([fa_icon('chevron-left fw'), t('settings.back')]), root_url
+ n.item :web, safe_join([fa_icon('chevron-left fw'), t('settings.back')]), root_path
- n.item :profile, safe_join([fa_icon('user fw'), t('settings.profile')]), settings_profile_url, if: -> { current_user.functional? } do |s|
- s.item :profile, safe_join([fa_icon('pencil fw'), t('settings.appearance')]), settings_profile_url
- s.item :featured_tags, safe_join([fa_icon('hashtag fw'), t('settings.featured_tags')]), settings_featured_tags_url
+ n.item :profile, safe_join([fa_icon('user fw'), t('settings.profile')]), settings_profile_path, if: -> { current_user.functional? } do |s|
+ s.item :profile, safe_join([fa_icon('pencil fw'), t('settings.appearance')]), settings_profile_path
+ s.item :featured_tags, safe_join([fa_icon('hashtag fw'), t('settings.featured_tags')]), settings_featured_tags_path
end
- n.item :preferences, safe_join([fa_icon('cog fw'), t('settings.preferences')]), settings_preferences_url, if: -> { current_user.functional? } do |s|
- s.item :appearance, safe_join([fa_icon('desktop fw'), t('settings.appearance')]), settings_preferences_appearance_url
- s.item :notifications, safe_join([fa_icon('bell fw'), t('settings.notifications')]), settings_preferences_notifications_url
- s.item :other, safe_join([fa_icon('cog fw'), t('preferences.other')]), settings_preferences_other_url
+ n.item :preferences, safe_join([fa_icon('cog fw'), t('settings.preferences')]), settings_preferences_path, if: -> { current_user.functional? } do |s|
+ s.item :appearance, safe_join([fa_icon('desktop fw'), t('settings.appearance')]), settings_preferences_appearance_path
+ s.item :notifications, safe_join([fa_icon('bell fw'), t('settings.notifications')]), settings_preferences_notifications_path
+ s.item :other, safe_join([fa_icon('cog fw'), t('preferences.other')]), settings_preferences_other_path
end
- n.item :relationships, safe_join([fa_icon('users fw'), t('settings.relationships')]), relationships_url, if: -> { current_user.functional? }
+ n.item :relationships, safe_join([fa_icon('users fw'), t('settings.relationships')]), relationships_path, if: -> { current_user.functional? }
n.item :filters, safe_join([fa_icon('filter fw'), t('filters.index.title')]), filters_path, highlights_on: %r{/filters}, if: -> { current_user.functional? }
- n.item :statuses_cleanup, safe_join([fa_icon('history fw'), t('settings.statuses_cleanup')]), statuses_cleanup_url, if: -> { current_user.functional? }
+ n.item :statuses_cleanup, safe_join([fa_icon('history fw'), t('settings.statuses_cleanup')]), statuses_cleanup_path, if: -> { current_user.functional? }
- n.item :security, safe_join([fa_icon('lock fw'), t('settings.account')]), edit_user_registration_url do |s|
- s.item :password, safe_join([fa_icon('lock fw'), t('settings.account_settings')]), edit_user_registration_url, highlights_on: %r{/auth/edit|/settings/delete|/settings/migration|/settings/aliases|/settings/login_activities|^/disputes}
- s.item :two_factor_authentication, safe_join([fa_icon('mobile fw'), t('settings.two_factor_authentication')]), settings_two_factor_authentication_methods_url, highlights_on: %r{/settings/two_factor_authentication|/settings/otp_authentication|/settings/security_keys}
- s.item :authorized_apps, safe_join([fa_icon('list fw'), t('settings.authorized_apps')]), oauth_authorized_applications_url
+ n.item :security, safe_join([fa_icon('lock fw'), t('settings.account')]), edit_user_registration_path do |s|
+ s.item :password, safe_join([fa_icon('lock fw'), t('settings.account_settings')]), edit_user_registration_path, highlights_on: %r{/auth/edit|/settings/delete|/settings/migration|/settings/aliases|/settings/login_activities|^/disputes}
+ s.item :two_factor_authentication, safe_join([fa_icon('mobile fw'), t('settings.two_factor_authentication')]), settings_two_factor_authentication_methods_path, highlights_on: %r{/settings/two_factor_authentication|/settings/otp_authentication|/settings/security_keys}
+ s.item :authorized_apps, safe_join([fa_icon('list fw'), t('settings.authorized_apps')]), oauth_authorized_applications_path
end
- n.item :data, safe_join([fa_icon('cloud-download fw'), t('settings.import_and_export')]), settings_export_url do |s|
- s.item :import, safe_join([fa_icon('cloud-upload fw'), t('settings.import')]), settings_import_url, if: -> { current_user.functional? }
- s.item :export, safe_join([fa_icon('cloud-download fw'), t('settings.export')]), settings_export_url
+ n.item :data, safe_join([fa_icon('cloud-download fw'), t('settings.import_and_export')]), settings_export_path do |s|
+ s.item :import, safe_join([fa_icon('cloud-upload fw'), t('settings.import')]), settings_import_path, if: -> { current_user.functional? }
+ s.item :export, safe_join([fa_icon('cloud-download fw'), t('settings.export')]), settings_export_path
end
- n.item :invites, safe_join([fa_icon('user-plus fw'), t('invites.title')]), invites_path, if: proc { Setting.min_invite_role == 'user' && current_user.functional? }
- n.item :development, safe_join([fa_icon('code fw'), t('settings.development')]), settings_applications_url, if: -> { current_user.functional? }
+ n.item :invites, safe_join([fa_icon('user-plus fw'), t('invites.title')]), invites_path, if: -> { current_user.can?(:invite_users) && current_user.functional? }
+ n.item :development, safe_join([fa_icon('code fw'), t('settings.development')]), settings_applications_path, if: -> { current_user.functional? }
- n.item :trends, safe_join([fa_icon('fire fw'), t('admin.trends.title')]), admin_trends_tags_path, if: proc { current_user.staff? } do |s|
+ n.item :trends, safe_join([fa_icon('fire fw'), t('admin.trends.title')]), admin_trends_statuses_path, if: -> { current_user.can?(:manage_taxonomies) } do |s|
s.item :statuses, safe_join([fa_icon('comments-o fw'), t('admin.trends.statuses.title')]), admin_trends_statuses_path, highlights_on: %r{/admin/trends/statuses}
s.item :tags, safe_join([fa_icon('hashtag fw'), t('admin.trends.tags.title')]), admin_trends_tags_path, highlights_on: %r{/admin/tags|/admin/trends/tags}
s.item :links, safe_join([fa_icon('newspaper-o fw'), t('admin.trends.links.title')]), admin_trends_links_path, highlights_on: %r{/admin/trends/links}
end
- n.item :moderation, safe_join([fa_icon('gavel fw'), t('moderation.title')]), admin_reports_url, if: proc { current_user.staff? } do |s|
- s.item :action_logs, safe_join([fa_icon('bars fw'), t('admin.action_logs.title')]), admin_action_logs_url
- s.item :reports, safe_join([fa_icon('flag fw'), t('admin.reports.title')]), admin_reports_url, highlights_on: %r{/admin/reports}
- s.item :accounts, safe_join([fa_icon('users fw'), t('admin.accounts.title')]), admin_accounts_url(origin: 'local'), highlights_on: %r{/admin/accounts|/admin/pending_accounts|/admin/disputes}
- s.item :invites, safe_join([fa_icon('user-plus fw'), t('admin.invites.title')]), admin_invites_path
- s.item :follow_recommendations, safe_join([fa_icon('user-plus fw'), t('admin.follow_recommendations.title')]), admin_follow_recommendations_path, highlights_on: %r{/admin/follow_recommendations}
- s.item :instances, safe_join([fa_icon('cloud fw'), t('admin.instances.title')]), admin_instances_url(limited: whitelist_mode? ? nil : '1'), highlights_on: %r{/admin/instances|/admin/domain_blocks|/admin/domain_allows}, if: -> { current_user.admin? }
- s.item :email_domain_blocks, safe_join([fa_icon('envelope fw'), t('admin.email_domain_blocks.title')]), admin_email_domain_blocks_url, highlights_on: %r{/admin/email_domain_blocks}, if: -> { current_user.admin? }
- s.item :ip_blocks, safe_join([fa_icon('ban fw'), t('admin.ip_blocks.title')]), admin_ip_blocks_url, highlights_on: %r{/admin/ip_blocks}, if: -> { current_user.admin? }
+ n.item :moderation, safe_join([fa_icon('gavel fw'), t('moderation.title')]), nil, if: -> { current_user.can?(:manage_reports, :view_audit_log, :manage_users, :manage_invites, :manage_taxonomies, :manage_federation, :manage_blocks) } do |s|
+ s.item :reports, safe_join([fa_icon('flag fw'), t('admin.reports.title')]), admin_reports_path, highlights_on: %r{/admin/reports}, if: -> { current_user.can?(:manage_reports) }
+ s.item :accounts, safe_join([fa_icon('users fw'), t('admin.accounts.title')]), admin_accounts_path(origin: 'local'), highlights_on: %r{/admin/accounts|/admin/pending_accounts|/admin/disputes|/admin/users}, if: -> { current_user.can?(:manage_users) }
+ s.item :invites, safe_join([fa_icon('user-plus fw'), t('admin.invites.title')]), admin_invites_path, if: -> { current_user.can?(:manage_invites) }
+ s.item :follow_recommendations, safe_join([fa_icon('user-plus fw'), t('admin.follow_recommendations.title')]), admin_follow_recommendations_path, highlights_on: %r{/admin/follow_recommendations}, if: -> { current_user.can?(:manage_taxonomies) }
+ s.item :instances, safe_join([fa_icon('cloud fw'), t('admin.instances.title')]), admin_instances_path(limited: whitelist_mode? ? nil : '1'), highlights_on: %r{/admin/instances|/admin/domain_blocks|/admin/domain_allows}, if: -> { current_user.can?(:manage_federation) }
+ s.item :email_domain_blocks, safe_join([fa_icon('envelope fw'), t('admin.email_domain_blocks.title')]), admin_email_domain_blocks_path, highlights_on: %r{/admin/email_domain_blocks}, if: -> { current_user.can?(:manage_blocks) }
+ s.item :ip_blocks, safe_join([fa_icon('ban fw'), t('admin.ip_blocks.title')]), admin_ip_blocks_path, highlights_on: %r{/admin/ip_blocks}, if: -> { current_user.can?(:manage_blocks) }
+ s.item :action_logs, safe_join([fa_icon('bars fw'), t('admin.action_logs.title')]), admin_action_logs_path, if: -> { current_user.can?(:view_audit_log) }
end
- n.item :admin, safe_join([fa_icon('cogs fw'), t('admin.title')]), admin_dashboard_url, if: proc { current_user.staff? } do |s|
- s.item :dashboard, safe_join([fa_icon('tachometer fw'), t('admin.dashboard.title')]), admin_dashboard_url
- s.item :settings, safe_join([fa_icon('cogs fw'), t('admin.settings.title')]), edit_admin_settings_url, if: -> { current_user.admin? }, highlights_on: %r{/admin/settings}
- s.item :rules, safe_join([fa_icon('gavel fw'), t('admin.rules.title')]), admin_rules_path, highlights_on: %r{/admin/rules}
- s.item :announcements, safe_join([fa_icon('bullhorn fw'), t('admin.announcements.title')]), admin_announcements_path, highlights_on: %r{/admin/announcements}
- s.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}
- s.item :webhooks, safe_join([fa_icon('inbox fw'), t('admin.webhooks.title')]), admin_webhooks_path, highlights_on: %r{/admin/webhooks}
- s.item :relays, safe_join([fa_icon('exchange fw'), t('admin.relays.title')]), admin_relays_url, if: -> { current_user.admin? && !whitelist_mode? }, highlights_on: %r{/admin/relays}
- s.item :sidekiq, safe_join([fa_icon('diamond fw'), 'Sidekiq']), sidekiq_url, link_html: { target: 'sidekiq' }, if: -> { current_user.admin? }
- s.item :pghero, safe_join([fa_icon('database fw'), 'PgHero']), pghero_url, link_html: { target: 'pghero' }, if: -> { current_user.admin? }
+ n.item :admin, safe_join([fa_icon('cogs fw'), t('admin.title')]), nil, if: -> { current_user.can?(:view_dashboard, :manage_settings, :manage_rules, :manage_announcements, :manage_custom_emojis, :manage_webhooks, :manage_federation) } do |s|
+ s.item :dashboard, safe_join([fa_icon('tachometer fw'), t('admin.dashboard.title')]), admin_dashboard_path, if: -> { current_user.can?(:view_dashboard) }
+ s.item :settings, safe_join([fa_icon('cogs fw'), t('admin.settings.title')]), edit_admin_settings_path, if: -> { current_user.can?(:manage_settings) }, highlights_on: %r{/admin/settings}
+ s.item :rules, safe_join([fa_icon('gavel fw'), t('admin.rules.title')]), admin_rules_path, highlights_on: %r{/admin/rules}, if: -> { current_user.can?(:manage_rules) }
+ s.item :roles, safe_join([fa_icon('vcard fw'), t('admin.roles.title')]), admin_roles_path, highlights_on: %r{/admin/roles}, if: -> { current_user.can?(:manage_roles) }
+ s.item :announcements, safe_join([fa_icon('bullhorn fw'), t('admin.announcements.title')]), admin_announcements_path, highlights_on: %r{/admin/announcements}, if: -> { current_user.can?(:manage_announcements) }
+ s.item :custom_emojis, safe_join([fa_icon('smile-o fw'), t('admin.custom_emojis.title')]), admin_custom_emojis_path, highlights_on: %r{/admin/custom_emojis}, if: -> { current_user.can?(:manage_custom_emojis) }
+ s.item :webhooks, safe_join([fa_icon('inbox fw'), t('admin.webhooks.title')]), admin_webhooks_path, highlights_on: %r{/admin/webhooks}, if: -> { current_user.can?(:manage_webhooks) }
+ s.item :relays, safe_join([fa_icon('exchange fw'), t('admin.relays.title')]), admin_relays_path, highlights_on: %r{/admin/relays}, if: -> { !whitelist_mode? && current_user.can?(:manage_federation) }
end
- n.item :logout, safe_join([fa_icon('sign-out fw'), t('auth.logout')]), destroy_user_session_url, link_html: { 'data-method' => 'delete' }
+ n.item :sidekiq, safe_join([fa_icon('diamond fw'), 'Sidekiq']), sidekiq_path, link_html: { target: 'sidekiq' }, if: -> { current_user.can?(:view_devops) }
+ n.item :pghero, safe_join([fa_icon('database fw'), 'PgHero']), pghero_path, link_html: { target: 'pghero' }, if: -> { current_user.can?(:view_devops) }
+ n.item :logout, safe_join([fa_icon('sign-out fw'), t('auth.logout')]), destroy_user_session_path, link_html: { 'data-method' => 'delete' }
end
end
diff --git a/config/roles.yml b/config/roles.yml
new file mode 100644
index 000000000..f443250d1
--- /dev/null
+++ b/config/roles.yml
@@ -0,0 +1,35 @@
+moderator:
+ name: Moderator
+ position: 10
+ permissions:
+ - view_dashboard
+ - view_audit_log
+ - manage_users
+ - manage_reports
+ - manage_taxonomies
+admin:
+ name: Admin
+ position: 100
+ permissions:
+ - view_dashboard
+ - view_audit_log
+ - manage_users
+ - manage_user_access
+ - delete_user_data
+ - manage_reports
+ - manage_taxonomies
+ - manage_federation
+ - manage_settings
+ - manage_blocks
+ - manage_appeals
+ - manage_rules
+ - manage_invites
+ - manage_announcements
+ - manage_custom_emojis
+ - manage_webhooks
+ - manage_roles
+owner:
+ name: Owner
+ position: 1000
+ permissions:
+ - administrator
diff --git a/config/routes.rb b/config/routes.rb
index 4abf55655..177c1cff4 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -10,7 +10,7 @@ Rails.application.routes.draw do
get 'health', to: 'health#show'
- authenticate :user, lambda { |u| u.admin? } do
+ authenticate :user, lambda { |u| u.role&.can?(:view_devops) } do
mount Sidekiq::Web, at: 'sidekiq', as: :sidekiq
mount PgHero::Engine, at: 'pghero', as: :pghero
end
@@ -295,17 +295,11 @@ Rails.application.routes.draw do
post :resend
end
end
-
- resource :role, only: [] do
- member do
- post :promote
- post :demote
- end
- end
end
resources :users, only: [] do
- resource :two_factor_authentication, only: [:destroy]
+ resource :two_factor_authentication, only: [:destroy], controller: 'users/two_factor_authentications'
+ resource :role, only: [:show, :update], controller: 'users/roles'
end
resources :custom_emojis, only: [:index, :new, :create] do
@@ -320,6 +314,7 @@ Rails.application.routes.draw do
end
end
+ resources :roles, except: [:show]
resources :account_moderation_notes, only: [:create, :destroy]
resource :follow_recommendations, only: [:show, :update]
resources :tags, only: [:show, :update]
diff --git a/db/migrate/20220611210335_create_user_roles.rb b/db/migrate/20220611210335_create_user_roles.rb
new file mode 100644
index 000000000..6b7f2b637
--- /dev/null
+++ b/db/migrate/20220611210335_create_user_roles.rb
@@ -0,0 +1,13 @@
+class CreateUserRoles < ActiveRecord::Migration[6.1]
+ def change
+ create_table :user_roles do |t|
+ t.string :name, null: false, default: ''
+ t.string :color, null: false, default: ''
+ t.integer :position, null: false, default: 0
+ t.bigint :permissions, null: false, default: 0
+ t.boolean :highlighted, null: false, default: false
+
+ t.timestamps
+ end
+ end
+end
diff --git a/db/migrate/20220611212541_add_role_id_to_users.rb b/db/migrate/20220611212541_add_role_id_to_users.rb
new file mode 100644
index 000000000..2fda647d4
--- /dev/null
+++ b/db/migrate/20220611212541_add_role_id_to_users.rb
@@ -0,0 +1,8 @@
+class AddRoleIdToUsers < ActiveRecord::Migration[6.1]
+ disable_ddl_transaction!
+
+ def change
+ safety_assured { add_reference :users, :role, foreign_key: { to_table: 'user_roles', on_delete: :nullify }, index: false }
+ add_index :users, :role_id, algorithm: :concurrently, where: 'role_id IS NOT NULL'
+ end
+end
diff --git a/db/post_migrate/20220617202502_migrate_roles.rb b/db/post_migrate/20220617202502_migrate_roles.rb
new file mode 100644
index 000000000..b7a7b2201
--- /dev/null
+++ b/db/post_migrate/20220617202502_migrate_roles.rb
@@ -0,0 +1,26 @@
+# frozen_string_literal: true
+
+class MigrateRoles < ActiveRecord::Migration[5.2]
+ disable_ddl_transaction!
+
+ class UserRole < ApplicationRecord; end
+ class User < ApplicationRecord; end
+
+ def up
+ load Rails.root.join('db', 'seeds', '03_roles.rb')
+
+ admin_role = UserRole.find_by(name: 'Admin')
+ moderator_role = UserRole.find_by(name: 'Moderator')
+
+ User.where(admin: true).in_batches.update_all(role_id: admin_role.id)
+ User.where(moderator: true).in_batches.update_all(role_id: moderator_role.id)
+ end
+
+ def down
+ admin_role = UserRole.find_by(name: 'Admin')
+ moderator_role = UserRole.find_by(name: 'Moderator')
+
+ User.where(role_id: admin_role.id).in_batches.update_all(admin: true) if admin_role
+ User.where(role_id: moderator_role.id).in_batches.update_all(moderator: true) if moderator_role
+ end
+end
diff --git a/db/post_migrate/20220704024901_migrate_settings_to_user_roles.rb b/db/post_migrate/20220704024901_migrate_settings_to_user_roles.rb
new file mode 100644
index 000000000..254690cc3
--- /dev/null
+++ b/db/post_migrate/20220704024901_migrate_settings_to_user_roles.rb
@@ -0,0 +1,41 @@
+# frozen_string_literal: true
+
+class MigrateSettingsToUserRoles < ActiveRecord::Migration[6.1]
+ disable_ddl_transaction!
+
+ class UserRole < ApplicationRecord; end
+
+ def up
+ owner_role = UserRole.find_by(name: 'Owner')
+ admin_role = UserRole.find_by(name: 'Admin')
+ moderator_role = UserRole.find_by(name: 'Moderator')
+ everyone_role = UserRole.find_by(id: -99)
+
+ min_invite_role = Setting.min_invite_role
+ show_staff_badge = Setting.show_staff_badge
+
+ if everyone_role
+ everyone_role.permissions &= ~::UserRole::FLAGS[:invite_users] unless min_invite_role == 'user'
+ everyone_role.save
+ end
+
+ if owner_role
+ owner_role.highlighted = show_staff_badge
+ owner_role.save
+ end
+
+ if admin_role
+ admin_role.permissions |= ::UserRole::FLAGS[:invite_users] if %w(admin moderator).include?(min_invite_role)
+ admin_role.highlighted = show_staff_badge
+ admin_role.save
+ end
+
+ if moderator_role
+ moderator_role.permissions |= ::UserRole::FLAGS[:invite_users] if %w(moderator).include?(min_invite_role)
+ moderator_role.highlighted = show_staff_badge
+ moderator_role.save
+ end
+ end
+
+ def down; end
+end
diff --git a/db/schema.rb b/db/schema.rb
index 759dc712b..54966ef64 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -10,7 +10,7 @@
#
# It's strongly recommended that you check this file into your version control system.
-ActiveRecord::Schema.define(version: 2022_06_13_110903) do
+ActiveRecord::Schema.define(version: 2022_07_04_024901) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
@@ -968,6 +968,16 @@ ActiveRecord::Schema.define(version: 2022_06_13_110903) do
t.index ["user_id"], name: "index_user_invite_requests_on_user_id"
end
+ create_table "user_roles", force: :cascade do |t|
+ t.string "name", default: "", null: false
+ t.string "color", default: "", null: false
+ t.integer "position", default: 0, null: false
+ t.bigint "permissions", default: 0, null: false
+ t.boolean "highlighted", default: false, null: false
+ t.datetime "created_at", precision: 6, null: false
+ t.datetime "updated_at", precision: 6, null: false
+ end
+
create_table "users", force: :cascade do |t|
t.string "email", default: "", null: false
t.datetime "created_at", null: false
@@ -1003,11 +1013,13 @@ ActiveRecord::Schema.define(version: 2022_06_13_110903) do
t.string "webauthn_id"
t.inet "sign_up_ip"
t.boolean "skip_sign_in_token"
+ t.bigint "role_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 ["created_by_application_id"], name: "index_users_on_created_by_application_id", where: "(created_by_application_id IS NOT NULL)"
t.index ["email"], name: "index_users_on_email", unique: true
t.index ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true, opclass: :text_pattern_ops, where: "(reset_password_token IS NOT NULL)"
+ t.index ["role_id"], name: "index_users_on_role_id", where: "(role_id IS NOT NULL)"
end
create_table "web_push_subscriptions", force: :cascade do |t|
@@ -1159,6 +1171,7 @@ ActiveRecord::Schema.define(version: 2022_06_13_110903) do
add_foreign_key "users", "accounts", name: "fk_50500f500d", on_delete: :cascade
add_foreign_key "users", "invites", on_delete: :nullify
add_foreign_key "users", "oauth_applications", column: "created_by_application_id", on_delete: :nullify
+ add_foreign_key "users", "user_roles", column: "role_id", on_delete: :nullify
add_foreign_key "web_push_subscriptions", "oauth_access_tokens", column: "access_token_id", on_delete: :cascade
add_foreign_key "web_push_subscriptions", "users", on_delete: :cascade
add_foreign_key "web_settings", "users", name: "fk_11910667b2", on_delete: :cascade
diff --git a/db/seeds.rb b/db/seeds.rb
index 0bfb5d0db..1ca300de7 100644
--- a/db/seeds.rb
+++ b/db/seeds.rb
@@ -1,11 +1,5 @@
-Doorkeeper::Application.create!(name: 'Web', superapp: true, redirect_uri: Doorkeeper.configuration.native_redirect_uri, scopes: 'read write follow push')
+# frozen_string_literal: true
-domain = ENV['LOCAL_DOMAIN'] || Rails.configuration.x.local_domain
-account = Account.find_or_initialize_by(id: -99, actor_type: 'Application', locked: true, username: domain)
-account.save!
-
-if Rails.env.development?
- admin = Account.where(username: 'admin').first_or_initialize(username: 'admin')
- admin.save(validate: false)
- User.where(email: "admin@#{domain}").first_or_initialize(email: "admin@#{domain}", password: 'mastodonadmin', password_confirmation: 'mastodonadmin', confirmed_at: Time.now.utc, admin: true, account: admin, agreement: true, approved: true).save!
+Dir[Rails.root.join('db', 'seeds', '*.rb')].sort.each do |seed|
+ load seed
end
diff --git a/db/seeds/01_web_app.rb b/db/seeds/01_web_app.rb
new file mode 100644
index 000000000..a457a883b
--- /dev/null
+++ b/db/seeds/01_web_app.rb
@@ -0,0 +1 @@
+Doorkeeper::Application.create_with(name: 'Web', redirect_uri: Doorkeeper.configuration.native_redirect_uri, scopes: 'read write follow push').find_or_create_by(superapp: true)
diff --git a/db/seeds/02_instance_actor.rb b/db/seeds/02_instance_actor.rb
new file mode 100644
index 000000000..39186b273
--- /dev/null
+++ b/db/seeds/02_instance_actor.rb
@@ -0,0 +1 @@
+Account.create_with(actor_type: 'Application', locked: true, username: ENV['LOCAL_DOMAIN'] || Rails.configuration.x.local_domain).find_or_create_by(id: -99)
diff --git a/db/seeds/03_roles.rb b/db/seeds/03_roles.rb
new file mode 100644
index 000000000..7fedf0f71
--- /dev/null
+++ b/db/seeds/03_roles.rb
@@ -0,0 +1,9 @@
+# Pre-create base role
+UserRole.everyone
+
+# Create default roles defined in config file
+default_roles = YAML.load_file(Rails.root.join('config', 'roles.yml'))
+
+default_roles.each do |_, config|
+ UserRole.create_with(position: config['position'], permissions_as_keys: config['permissions'], highlighted: true).find_or_create_by(name: config['name'])
+end
diff --git a/db/seeds/04_admin.rb b/db/seeds/04_admin.rb
new file mode 100644
index 000000000..a67040e4e
--- /dev/null
+++ b/db/seeds/04_admin.rb
@@ -0,0 +1,8 @@
+if Rails.env.development?
+ domain = ENV['LOCAL_DOMAIN'] || Rails.configuration.x.local_domain
+
+ admin = Account.where(username: 'admin').first_or_initialize(username: 'admin')
+ admin.save(validate: false)
+
+ User.where(email: "admin@#{domain}").first_or_initialize(email: "admin@#{domain}", password: 'mastodonadmin', password_confirmation: 'mastodonadmin', confirmed_at: Time.now.utc, role: UserRole.find_by(name: 'Owner'), account: admin, agreement: true, approved: true).save!
+end
diff --git a/lib/mastodon/accounts_cli.rb b/lib/mastodon/accounts_cli.rb
index 7256d1da9..29c934453 100644
--- a/lib/mastodon/accounts_cli.rb
+++ b/lib/mastodon/accounts_cli.rb
@@ -54,7 +54,7 @@ module Mastodon
option :email, required: true
option :confirmed, type: :boolean
- option :role, default: 'user', enum: %w(user moderator admin)
+ option :role
option :reattach, type: :boolean
option :force, type: :boolean
desc 'create USERNAME', 'Create a new user'
@@ -65,8 +65,7 @@ module Mastodon
With the --confirmed option, the confirmation e-mail will
be skipped and the account will be active straight away.
- With the --role option one of "user", "admin" or "moderator"
- can be supplied. Defaults to "user"
+ With the --role option, the role can be supplied.
With the --reattach option, the new user will be reattached
to a given existing username of an old account. If the old
@@ -75,9 +74,22 @@ module Mastodon
username to the new account anyway.
LONG_DESC
def create(username)
+ role_id = nil
+
+ if options[:role]
+ role = UserRole.find_by(name: options[:role])
+
+ if role.nil?
+ say('Cannot find user role with that name', :red)
+ exit(1)
+ end
+
+ role_id = role.id
+ end
+
account = Account.new(username: username)
password = SecureRandom.hex
- user = User.new(email: options[:email], password: password, agreement: true, approved: true, admin: options[:role] == 'admin', moderator: options[:role] == 'moderator', confirmed_at: options[:confirmed] ? Time.now.utc : nil, bypass_invite_request_check: true)
+ user = User.new(email: options[:email], password: password, agreement: true, approved: true, role_id: role_id, confirmed_at: options[:confirmed] ? Time.now.utc : nil, bypass_invite_request_check: true)
if options[:reattach]
account = Account.find_local(username) || Account.new(username: username)
@@ -106,14 +118,14 @@ module Mastodon
user.errors.to_h.each do |key, error|
say('Failure/Error: ', :red)
say(key)
- say(' ' + error, :red)
+ say(" #{error}", :red)
end
exit(1)
end
end
- option :role, enum: %w(user moderator admin)
+ option :role
option :email
option :confirm, type: :boolean
option :enable, type: :boolean
@@ -125,8 +137,7 @@ module Mastodon
long_desc <<-LONG_DESC
Modify a user account.
- With the --role option, update the user's role to one of "user",
- "moderator" or "admin".
+ With the --role option, update the user's role.
With the --email option, update the user's e-mail address. With
the --confirm option, mark the user's e-mail as confirmed.
@@ -152,8 +163,14 @@ module Mastodon
end
if options[:role]
- user.admin = options[:role] == 'admin'
- user.moderator = options[:role] == 'moderator'
+ role = UserRole.find_by(name: options[:role])
+
+ if role.nil?
+ say('Cannot find user role with that name', :red)
+ exit(1)
+ end
+
+ user.role_id = role.id
end
password = SecureRandom.hex if options[:reset_password]
@@ -172,7 +189,7 @@ module Mastodon
user.errors.to_h.each do |key, error|
say('Failure/Error: ', :red)
say(key)
- say(' ' + error, :red)
+ say(" #{error}", :red)
end
exit(1)
@@ -319,7 +336,7 @@ module Mastodon
unless skip_domains.empty?
say('The following domains were not available during the check:', :yellow)
- skip_domains.each { |domain| say(' ' + domain) }
+ skip_domains.each { |domain| say(" #{domain}") }
end
end
diff --git a/lib/simple_navigation/item_extensions.rb b/lib/simple_navigation/item_extensions.rb
new file mode 100644
index 000000000..28af37a18
--- /dev/null
+++ b/lib/simple_navigation/item_extensions.rb
@@ -0,0 +1,15 @@
+# frozen_string_literal: true
+
+module SimpleNavigation
+ module ItemExtensions
+ def url
+ if @url.nil? && @sub_navigation
+ @sub_navigation.items.first.url
+ else
+ @url
+ end
+ end
+ end
+end
+
+SimpleNavigation::Item.prepend(SimpleNavigation::ItemExtensions)
diff --git a/spec/controllers/admin/account_moderation_notes_controller_spec.rb b/spec/controllers/admin/account_moderation_notes_controller_spec.rb
index 410ce6543..d3f3263f8 100644
--- a/spec/controllers/admin/account_moderation_notes_controller_spec.rb
+++ b/spec/controllers/admin/account_moderation_notes_controller_spec.rb
@@ -3,7 +3,7 @@ require 'rails_helper'
RSpec.describe Admin::AccountModerationNotesController, type: :controller do
render_views
- let(:user) { Fabricate(:user, admin: true) }
+ let(:user) { Fabricate(:user, role: UserRole.find_by(name: 'Admin')) }
let(:target_account) { Fabricate(:account) }
before do
diff --git a/spec/controllers/admin/accounts_controller_spec.rb b/spec/controllers/admin/accounts_controller_spec.rb
index 1779fb7c0..1bd51a0c8 100644
--- a/spec/controllers/admin/accounts_controller_spec.rb
+++ b/spec/controllers/admin/accounts_controller_spec.rb
@@ -6,7 +6,7 @@ RSpec.describe Admin::AccountsController, type: :controller do
before { sign_in current_user, scope: :user }
describe 'GET #index' do
- let(:current_user) { Fabricate(:user, admin: true) }
+ let(:current_user) { Fabricate(:user, role: UserRole.find_by(name: 'Admin')) }
around do |example|
default_per_page = Account.default_per_page
@@ -60,7 +60,7 @@ RSpec.describe Admin::AccountsController, type: :controller do
end
describe 'GET #show' do
- let(:current_user) { Fabricate(:user, admin: true) }
+ let(:current_user) { Fabricate(:user, role: UserRole.find_by(name: 'Admin')) }
let(:account) { Fabricate(:account) }
it 'returns http success' do
@@ -72,15 +72,15 @@ RSpec.describe Admin::AccountsController, type: :controller do
describe 'POST #memorialize' do
subject { post :memorialize, params: { id: account.id } }
- let(:current_user) { Fabricate(:user, admin: current_user_admin) }
+ let(:current_user) { Fabricate(:user, role: current_role) }
let(:account) { user.account }
- let(:user) { Fabricate(:user, admin: target_user_admin) }
+ let(:user) { Fabricate(:user, role: target_role) }
context 'when user is admin' do
- let(:current_user_admin) { true }
+ let(:current_role) { UserRole.find_by(name: 'Admin') }
context 'when target user is admin' do
- let(:target_user_admin) { true }
+ let(:target_role) { UserRole.find_by(name: 'Admin') }
it 'fails to memorialize account' do
is_expected.to have_http_status :forbidden
@@ -89,7 +89,7 @@ RSpec.describe Admin::AccountsController, type: :controller do
end
context 'when target user is not admin' do
- let(:target_user_admin) { false }
+ let(:target_role) { UserRole.find_by(name: 'Moderator') }
it 'succeeds in memorializing account' do
is_expected.to redirect_to admin_account_path(account.id)
@@ -99,10 +99,10 @@ RSpec.describe Admin::AccountsController, type: :controller do
end
context 'when user is not admin' do
- let(:current_user_admin) { false }
+ let(:current_role) { UserRole.find_by(name: 'Moderator') }
context 'when target user is admin' do
- let(:target_user_admin) { true }
+ let(:target_role) { UserRole.find_by(name: 'Admin') }
it 'fails to memorialize account' do
is_expected.to have_http_status :forbidden
@@ -111,7 +111,7 @@ RSpec.describe Admin::AccountsController, type: :controller do
end
context 'when target user is not admin' do
- let(:target_user_admin) { false }
+ let(:target_role) { UserRole.find_by(name: 'Moderator') }
it 'fails to memorialize account' do
is_expected.to have_http_status :forbidden
@@ -124,12 +124,12 @@ RSpec.describe Admin::AccountsController, type: :controller do
describe 'POST #enable' do
subject { post :enable, params: { id: account.id } }
- let(:current_user) { Fabricate(:user, admin: admin) }
+ let(:current_user) { Fabricate(:user, role: role) }
let(:account) { user.account }
let(:user) { Fabricate(:user, disabled: true) }
context 'when user is admin' do
- let(:admin) { true }
+ let(:role) { UserRole.find_by(name: 'Admin') }
it 'succeeds in enabling account' do
is_expected.to redirect_to admin_account_path(account.id)
@@ -138,7 +138,7 @@ RSpec.describe Admin::AccountsController, type: :controller do
end
context 'when user is not admin' do
- let(:admin) { false }
+ let(:role) { UserRole.everyone }
it 'fails to enable account' do
is_expected.to have_http_status :forbidden
@@ -150,19 +150,23 @@ RSpec.describe Admin::AccountsController, type: :controller do
describe 'POST #redownload' do
subject { post :redownload, params: { id: account.id } }
- let(:current_user) { Fabricate(:user, admin: admin) }
- let(:account) { Fabricate(:account) }
+ let(:current_user) { Fabricate(:user, role: role) }
+ let(:account) { Fabricate(:account, domain: 'example.com') }
+
+ before do
+ allow_any_instance_of(ResolveAccountService).to receive(:call)
+ end
context 'when user is admin' do
- let(:admin) { true }
+ let(:role) { UserRole.find_by(name: 'Admin') }
- it 'succeeds in redownloadin' do
+ it 'succeeds in redownloading' do
is_expected.to redirect_to admin_account_path(account.id)
end
end
context 'when user is not admin' do
- let(:admin) { false }
+ let(:role) { UserRole.everyone }
it 'fails to redownload' do
is_expected.to have_http_status :forbidden
@@ -173,11 +177,11 @@ RSpec.describe Admin::AccountsController, type: :controller do
describe 'POST #remove_avatar' do
subject { post :remove_avatar, params: { id: account.id } }
- let(:current_user) { Fabricate(:user, admin: admin) }
+ let(:current_user) { Fabricate(:user, role: role) }
let(:account) { Fabricate(:account) }
context 'when user is admin' do
- let(:admin) { true }
+ let(:role) { UserRole.find_by(name: 'Admin') }
it 'succeeds in removing avatar' do
is_expected.to redirect_to admin_account_path(account.id)
@@ -185,7 +189,7 @@ RSpec.describe Admin::AccountsController, type: :controller do
end
context 'when user is not admin' do
- let(:admin) { false }
+ let(:role) { UserRole.everyone }
it 'fails to remove avatar' do
is_expected.to have_http_status :forbidden
@@ -196,12 +200,12 @@ RSpec.describe Admin::AccountsController, type: :controller do
describe 'POST #unblock_email' do
subject { post :unblock_email, params: { id: account.id } }
- let(:current_user) { Fabricate(:user, admin: admin) }
+ let(:current_user) { Fabricate(:user, role: role) }
let(:account) { Fabricate(:account, suspended: true) }
let!(:email_block) { Fabricate(:canonical_email_block, reference_account: account) }
context 'when user is admin' do
- let(:admin) { true }
+ let(:role) { UserRole.find_by(name: 'Admin') }
it 'succeeds in removing email blocks' do
expect { subject }.to change { CanonicalEmailBlock.where(reference_account: account).count }.from(1).to(0)
@@ -214,7 +218,7 @@ RSpec.describe Admin::AccountsController, type: :controller do
end
context 'when user is not admin' do
- let(:admin) { false }
+ let(:role) { UserRole.everyone }
it 'fails to remove avatar' do
subject
diff --git a/spec/controllers/admin/action_logs_controller_spec.rb b/spec/controllers/admin/action_logs_controller_spec.rb
index 4720ed2e2..c1957258f 100644
--- a/spec/controllers/admin/action_logs_controller_spec.rb
+++ b/spec/controllers/admin/action_logs_controller_spec.rb
@@ -5,7 +5,7 @@ require 'rails_helper'
describe Admin::ActionLogsController, type: :controller do
describe 'GET #index' do
it 'returns 200' do
- sign_in Fabricate(:user, admin: true)
+ sign_in Fabricate(:user, role: UserRole.find_by(name: 'Admin'))
get :index, params: { page: 1 }
expect(response).to have_http_status(200)
diff --git a/spec/controllers/admin/base_controller_spec.rb b/spec/controllers/admin/base_controller_spec.rb
index 9ac833623..44be91951 100644
--- a/spec/controllers/admin/base_controller_spec.rb
+++ b/spec/controllers/admin/base_controller_spec.rb
@@ -5,13 +5,14 @@ require 'rails_helper'
describe Admin::BaseController, type: :controller do
controller do
def success
+ authorize :dashboard, :index?
render 'admin/reports/show'
end
end
it 'requires administrator or moderator' do
routes.draw { get 'success' => 'admin/base#success' }
- sign_in(Fabricate(:user, admin: false, moderator: false))
+ sign_in(Fabricate(:user))
get :success
expect(response).to have_http_status(:forbidden)
@@ -19,14 +20,14 @@ describe Admin::BaseController, type: :controller do
it 'renders admin layout as a moderator' do
routes.draw { get 'success' => 'admin/base#success' }
- sign_in(Fabricate(:user, moderator: true))
+ sign_in(Fabricate(:user, role: UserRole.find_by(name: 'Moderator')))
get :success
expect(response).to render_template layout: 'admin'
end
it 'renders admin layout as an admin' do
routes.draw { get 'success' => 'admin/base#success' }
- sign_in(Fabricate(:user, admin: true))
+ sign_in(Fabricate(:user, role: UserRole.find_by(name: 'Admin')))
get :success
expect(response).to render_template layout: 'admin'
end
diff --git a/spec/controllers/admin/change_email_controller_spec.rb b/spec/controllers/admin/change_email_controller_spec.rb
index e7f3f7c97..cf8a27d39 100644
--- a/spec/controllers/admin/change_email_controller_spec.rb
+++ b/spec/controllers/admin/change_email_controller_spec.rb
@@ -3,7 +3,7 @@ require 'rails_helper'
RSpec.describe Admin::ChangeEmailsController, type: :controller do
render_views
- let(:admin) { Fabricate(:user, admin: true) }
+ let(:admin) { Fabricate(:user, role: UserRole.find_by(name: 'Admin')) }
before do
sign_in admin
diff --git a/spec/controllers/admin/confirmations_controller_spec.rb b/spec/controllers/admin/confirmations_controller_spec.rb
index 5b4f7e925..6268903c4 100644
--- a/spec/controllers/admin/confirmations_controller_spec.rb
+++ b/spec/controllers/admin/confirmations_controller_spec.rb
@@ -4,7 +4,7 @@ RSpec.describe Admin::ConfirmationsController, type: :controller do
render_views
before do
- sign_in Fabricate(:user, admin: true), scope: :user
+ sign_in Fabricate(:user, role: UserRole.find_by(name: 'Admin')), scope: :user
end
describe 'POST #create' do
diff --git a/spec/controllers/admin/custom_emojis_controller_spec.rb b/spec/controllers/admin/custom_emojis_controller_spec.rb
index a8d96948c..06cd0c22d 100644
--- a/spec/controllers/admin/custom_emojis_controller_spec.rb
+++ b/spec/controllers/admin/custom_emojis_controller_spec.rb
@@ -3,7 +3,7 @@ require 'rails_helper'
describe Admin::CustomEmojisController do
render_views
- let(:user) { Fabricate(:user, admin: true) }
+ let(:user) { Fabricate(:user, role: UserRole.find_by(name: 'Admin')) }
before do
sign_in user, scope: :user
diff --git a/spec/controllers/admin/dashboard_controller_spec.rb b/spec/controllers/admin/dashboard_controller_spec.rb
index 7824854f9..6231a09a2 100644
--- a/spec/controllers/admin/dashboard_controller_spec.rb
+++ b/spec/controllers/admin/dashboard_controller_spec.rb
@@ -12,7 +12,7 @@ describe Admin::DashboardController, type: :controller do
Admin::SystemCheck::Message.new(:rules_check, nil, admin_rules_path),
Admin::SystemCheck::Message.new(:sidekiq_process_check, 'foo, bar'),
])
- sign_in Fabricate(:user, admin: true)
+ sign_in Fabricate(:user, role: UserRole.find_by(name: 'Admin'))
end
it 'returns 200' do
diff --git a/spec/controllers/admin/disputes/appeals_controller_spec.rb b/spec/controllers/admin/disputes/appeals_controller_spec.rb
index 6a06f9406..712657791 100644
--- a/spec/controllers/admin/disputes/appeals_controller_spec.rb
+++ b/spec/controllers/admin/disputes/appeals_controller_spec.rb
@@ -14,7 +14,7 @@ RSpec.describe Admin::Disputes::AppealsController, type: :controller do
end
describe 'POST #approve' do
- let(:current_user) { Fabricate(:user, admin: true) }
+ let(:current_user) { Fabricate(:user, role: UserRole.find_by(name: 'Admin')) }
before do
allow(UserMailer).to receive(:appeal_approved).and_return(double('email', deliver_later: nil))
@@ -35,7 +35,7 @@ RSpec.describe Admin::Disputes::AppealsController, type: :controller do
end
describe 'POST #reject' do
- let(:current_user) { Fabricate(:user, admin: true) }
+ let(:current_user) { Fabricate(:user, role: UserRole.find_by(name: 'Admin')) }
before do
allow(UserMailer).to receive(:appeal_rejected).and_return(double('email', deliver_later: nil))
diff --git a/spec/controllers/admin/domain_blocks_controller_spec.rb b/spec/controllers/admin/domain_blocks_controller_spec.rb
index ecc79292b..5c2dcd268 100644
--- a/spec/controllers/admin/domain_blocks_controller_spec.rb
+++ b/spec/controllers/admin/domain_blocks_controller_spec.rb
@@ -4,7 +4,7 @@ RSpec.describe Admin::DomainBlocksController, type: :controller do
render_views
before do
- sign_in Fabricate(:user, admin: true), scope: :user
+ sign_in Fabricate(:user, role: UserRole.find_by(name: 'Admin')), scope: :user
end
describe 'GET #new' do
diff --git a/spec/controllers/admin/email_domain_blocks_controller_spec.rb b/spec/controllers/admin/email_domain_blocks_controller_spec.rb
index cf194579d..e9cef4a94 100644
--- a/spec/controllers/admin/email_domain_blocks_controller_spec.rb
+++ b/spec/controllers/admin/email_domain_blocks_controller_spec.rb
@@ -6,7 +6,7 @@ RSpec.describe Admin::EmailDomainBlocksController, type: :controller do
render_views
before do
- sign_in Fabricate(:user, admin: true), scope: :user
+ sign_in Fabricate(:user, role: UserRole.find_by(name: 'Admin')), scope: :user
end
describe 'GET #index' do
diff --git a/spec/controllers/admin/instances_controller_spec.rb b/spec/controllers/admin/instances_controller_spec.rb
index 53427b874..337f7a80c 100644
--- a/spec/controllers/admin/instances_controller_spec.rb
+++ b/spec/controllers/admin/instances_controller_spec.rb
@@ -3,7 +3,7 @@ require 'rails_helper'
RSpec.describe Admin::InstancesController, type: :controller do
render_views
- let(:current_user) { Fabricate(:user, admin: true) }
+ let(:current_user) { Fabricate(:user, role: UserRole.find_by(name: 'Admin')) }
let!(:account) { Fabricate(:account, domain: 'popular') }
let!(:account2) { Fabricate(:account, domain: 'popular') }
@@ -35,11 +35,11 @@ RSpec.describe Admin::InstancesController, type: :controller do
describe 'DELETE #destroy' do
subject { delete :destroy, params: { id: Instance.first.id } }
- let(:current_user) { Fabricate(:user, admin: admin) }
+ let(:current_user) { Fabricate(:user, role: role) }
let(:account) { Fabricate(:account) }
context 'when user is admin' do
- let(:admin) { true }
+ let(:role) { UserRole.find_by(name: 'Admin') }
it 'succeeds in purging instance' do
is_expected.to redirect_to admin_instances_path
@@ -47,7 +47,7 @@ RSpec.describe Admin::InstancesController, type: :controller do
end
context 'when user is not admin' do
- let(:admin) { false }
+ let(:role) { nil }
it 'fails to purge instance' do
is_expected.to have_http_status :forbidden
diff --git a/spec/controllers/admin/invites_controller_spec.rb b/spec/controllers/admin/invites_controller_spec.rb
index 449a699e4..1fb488742 100644
--- a/spec/controllers/admin/invites_controller_spec.rb
+++ b/spec/controllers/admin/invites_controller_spec.rb
@@ -5,7 +5,7 @@ require 'rails_helper'
describe Admin::InvitesController do
render_views
- let(:user) { Fabricate(:user, admin: true) }
+ let(:user) { Fabricate(:user, role: UserRole.find_by(name: 'Admin')) }
before do
sign_in user, scope: :user
diff --git a/spec/controllers/admin/report_notes_controller_spec.rb b/spec/controllers/admin/report_notes_controller_spec.rb
index c0013f41a..fa7572d18 100644
--- a/spec/controllers/admin/report_notes_controller_spec.rb
+++ b/spec/controllers/admin/report_notes_controller_spec.rb
@@ -3,7 +3,7 @@ require 'rails_helper'
describe Admin::ReportNotesController do
render_views
- let(:user) { Fabricate(:user, admin: true) }
+ let(:user) { Fabricate(:user, role: UserRole.find_by(name: 'Admin')) }
before do
sign_in user, scope: :user
diff --git a/spec/controllers/admin/reports_controller_spec.rb b/spec/controllers/admin/reports_controller_spec.rb
index d421f0739..4cd1524bf 100644
--- a/spec/controllers/admin/reports_controller_spec.rb
+++ b/spec/controllers/admin/reports_controller_spec.rb
@@ -3,7 +3,7 @@ require 'rails_helper'
describe Admin::ReportsController do
render_views
- let(:user) { Fabricate(:user, admin: true) }
+ let(:user) { Fabricate(:user, role: UserRole.find_by(name: 'Admin')) }
before do
sign_in user, scope: :user
end
diff --git a/spec/controllers/admin/resets_controller_spec.rb b/spec/controllers/admin/resets_controller_spec.rb
index 28510b5af..aeb172318 100644
--- a/spec/controllers/admin/resets_controller_spec.rb
+++ b/spec/controllers/admin/resets_controller_spec.rb
@@ -5,7 +5,7 @@ describe Admin::ResetsController do
let(:account) { Fabricate(:account) }
before do
- sign_in Fabricate(:user, admin: true), scope: :user
+ sign_in Fabricate(:user, role: UserRole.find_by(name: 'Admin')), scope: :user
end
describe 'POST #create' do
diff --git a/spec/controllers/admin/roles_controller_spec.rb b/spec/controllers/admin/roles_controller_spec.rb
index 8e0de73cb..8ff891205 100644
--- a/spec/controllers/admin/roles_controller_spec.rb
+++ b/spec/controllers/admin/roles_controller_spec.rb
@@ -3,31 +3,247 @@ require 'rails_helper'
describe Admin::RolesController do
render_views
- let(:admin) { Fabricate(:user, admin: true) }
+ let(:permissions) { UserRole::Flags::NONE }
+ let(:current_role) { UserRole.create(name: 'Foo', permissions: permissions, position: 10) }
+ let(:current_user) { Fabricate(:user, role: current_role) }
before do
- sign_in admin, scope: :user
+ sign_in current_user, scope: :user
end
- describe 'POST #promote' do
- subject { post :promote, params: { account_id: user.account_id } }
+ describe 'GET #index' do
+ before do
+ get :index
+ end
+
+ context 'when user does not have permission to manage roles' do
+ it 'returns http forbidden' do
+ expect(response).to have_http_status(:forbidden)
+ end
+ end
- let(:user) { Fabricate(:user, moderator: false, admin: false) }
+ context 'when user has permission to manage roles' do
+ let(:permissions) { UserRole::FLAGS[:manage_roles] }
- it 'promotes user' do
- expect(subject).to redirect_to admin_account_path(user.account_id)
- expect(user.reload).to be_moderator
+ it 'returns http success' do
+ expect(response).to have_http_status(:success)
+ end
end
end
- describe 'POST #demote' do
- subject { post :demote, params: { account_id: user.account_id } }
+ describe 'GET #new' do
+ before do
+ get :new
+ end
+
+ context 'when user does not have permission to manage roles' do
+ it 'returns http forbidden' do
+ expect(response).to have_http_status(:forbidden)
+ end
+ end
+
+ context 'when user has permission to manage roles' do
+ let(:permissions) { UserRole::FLAGS[:manage_roles] }
+
+ it 'returns http success' do
+ expect(response).to have_http_status(:success)
+ end
+ end
+ end
+
+ describe 'POST #create' do
+ let(:selected_position) { 1 }
+ let(:selected_permissions_as_keys) { %w(manage_roles) }
+
+ before do
+ post :create, params: { user_role: { name: 'Bar', position: selected_position, permissions_as_keys: selected_permissions_as_keys } }
+ end
+
+ context 'when user has permission to manage roles' do
+ let(:permissions) { UserRole::FLAGS[:manage_roles] }
+
+ context 'when new role\'s does not elevate above the user\'s role' do
+ let(:selected_position) { 1 }
+ let(:selected_permissions_as_keys) { %w(manage_roles) }
+
+ it 'redirects to roles page' do
+ expect(response).to redirect_to(admin_roles_path)
+ end
+
+ it 'creates new role' do
+ expect(UserRole.find_by(name: 'Bar')).to_not be_nil
+ end
+ end
+
+ context 'when new role\'s position is higher than user\'s role' do
+ let(:selected_position) { 100 }
+ let(:selected_permissions_as_keys) { %w(manage_roles) }
+
+ it 'renders new template' do
+ expect(response).to render_template(:new)
+ end
+
+ it 'does not create new role' do
+ expect(UserRole.find_by(name: 'Bar')).to be_nil
+ end
+ end
+
+ context 'when new role has permissions the user does not have' do
+ let(:selected_position) { 1 }
+ let(:selected_permissions_as_keys) { %w(manage_roles manage_users manage_reports) }
+
+ it 'renders new template' do
+ expect(response).to render_template(:new)
+ end
+
+ it 'does not create new role' do
+ expect(UserRole.find_by(name: 'Bar')).to be_nil
+ end
+ end
+
+ context 'when user has administrator permission' do
+ let(:permissions) { UserRole::FLAGS[:administrator] }
+
+ let(:selected_position) { 1 }
+ let(:selected_permissions_as_keys) { %w(manage_roles manage_users manage_reports) }
+
+ it 'redirects to roles page' do
+ expect(response).to redirect_to(admin_roles_path)
+ end
+
+ it 'creates new role' do
+ expect(UserRole.find_by(name: 'Bar')).to_not be_nil
+ end
+ end
+ end
+ end
+
+ describe 'GET #edit' do
+ let(:role_position) { 8 }
+ let(:role) { UserRole.create(name: 'Bar', permissions: UserRole::FLAGS[:manage_users], position: role_position) }
+
+ before do
+ get :edit, params: { id: role.id }
+ end
+
+ context 'when user does not have permission to manage roles' do
+ it 'returns http forbidden' do
+ expect(response).to have_http_status(:forbidden)
+ end
+ end
+
+ context 'when user has permission to manage roles' do
+ let(:permissions) { UserRole::FLAGS[:manage_roles] }
+
+ context 'when user outranks the role' do
+ it 'returns http success' do
+ expect(response).to have_http_status(:success)
+ end
+ end
+
+ context 'when role outranks user' do
+ let(:role_position) { current_role.position + 1 }
+
+ it 'returns http forbidden' do
+ expect(response).to have_http_status(:forbidden)
+ end
+ end
+ end
+ end
+
+ describe 'PUT #update' do
+ let(:role_position) { 8 }
+ let(:role_permissions) { UserRole::FLAGS[:manage_users] }
+ let(:role) { UserRole.create(name: 'Bar', permissions: role_permissions, position: role_position) }
+
+ let(:selected_position) { 8 }
+ let(:selected_permissions_as_keys) { %w(manage_users) }
+
+ before do
+ put :update, params: { id: role.id, user_role: { name: 'Baz', position: selected_position, permissions_as_keys: selected_permissions_as_keys } }
+ end
+
+ context 'when user does not have permission to manage roles' do
+ it 'returns http forbidden' do
+ expect(response).to have_http_status(:forbidden)
+ end
+
+ it 'does not update the role' do
+ expect(role.reload.name).to eq 'Bar'
+ end
+ end
+
+ context 'when user has permission to manage roles' do
+ let(:permissions) { UserRole::FLAGS[:manage_roles] }
+
+ context 'when role has permissions the user doesn\'t' do
+ it 'renders edit template' do
+ expect(response).to render_template(:edit)
+ end
+
+ it 'does not update the role' do
+ expect(role.reload.name).to eq 'Bar'
+ end
+ end
+
+ context 'when user has all permissions of the role' do
+ let(:permissions) { UserRole::FLAGS[:manage_roles] | UserRole::FLAGS[:manage_users] }
+
+ context 'when user outranks the role' do
+ it 'redirects to roles page' do
+ expect(response).to redirect_to(admin_roles_path)
+ end
+
+ it 'updates the role' do
+ expect(role.reload.name).to eq 'Baz'
+ end
+ end
+
+ context 'when role outranks user' do
+ let(:role_position) { current_role.position + 1 }
+
+ it 'returns http forbidden' do
+ expect(response).to have_http_status(:forbidden)
+ end
+
+ it 'does not update the role' do
+ expect(role.reload.name).to eq 'Bar'
+ end
+ end
+ end
+ end
+ end
+
+ describe 'DELETE #destroy' do
+ let(:role_position) { 8 }
+ let(:role) { UserRole.create(name: 'Bar', permissions: UserRole::FLAGS[:manage_users], position: role_position) }
+
+ before do
+ delete :destroy, params: { id: role.id }
+ end
+
+ context 'when user does not have permission to manage roles' do
+ it 'returns http forbidden' do
+ expect(response).to have_http_status(:forbidden)
+ end
+ end
+
+ context 'when user has permission to manage roles' do
+ let(:permissions) { UserRole::FLAGS[:manage_roles] }
+
+ context 'when user outranks the role' do
+ it 'redirects to roles page' do
+ expect(response).to redirect_to(admin_roles_path)
+ end
+ end
- let(:user) { Fabricate(:user, moderator: true, admin: false) }
+ context 'when role outranks user' do
+ let(:role_position) { current_role.position + 1 }
- it 'demotes user' do
- expect(subject).to redirect_to admin_account_path(user.account_id)
- expect(user.reload).not_to be_moderator
+ it 'returns http forbidden' do
+ expect(response).to have_http_status(:forbidden)
+ end
+ end
end
end
end
diff --git a/spec/controllers/admin/settings_controller_spec.rb b/spec/controllers/admin/settings_controller_spec.rb
index 6cf0ee20a..46749f76c 100644
--- a/spec/controllers/admin/settings_controller_spec.rb
+++ b/spec/controllers/admin/settings_controller_spec.rb
@@ -7,7 +7,7 @@ RSpec.describe Admin::SettingsController, type: :controller do
describe 'When signed in as an admin' do
before do
- sign_in Fabricate(:user, admin: true), scope: :user
+ sign_in Fabricate(:user, role: UserRole.find_by(name: 'Admin')), scope: :user
end
describe 'GET #edit' do
diff --git a/spec/controllers/admin/statuses_controller_spec.rb b/spec/controllers/admin/statuses_controller_spec.rb
index de32fd18e..227688e23 100644
--- a/spec/controllers/admin/statuses_controller_spec.rb
+++ b/spec/controllers/admin/statuses_controller_spec.rb
@@ -3,7 +3,7 @@ require 'rails_helper'
describe Admin::StatusesController do
render_views
- let(:user) { Fabricate(:user, admin: true) }
+ let(:user) { Fabricate(:user, role: UserRole.find_by(name: 'Admin')) }
let(:account) { Fabricate(:account) }
let!(:status) { Fabricate(:status, account: account) }
let(:media_attached_status) { Fabricate(:status, account: account, sensitive: !sensitive) }
diff --git a/spec/controllers/admin/tags_controller_spec.rb b/spec/controllers/admin/tags_controller_spec.rb
index 85c801a9c..52fd09eb1 100644
--- a/spec/controllers/admin/tags_controller_spec.rb
+++ b/spec/controllers/admin/tags_controller_spec.rb
@@ -6,7 +6,7 @@ RSpec.describe Admin::TagsController, type: :controller do
render_views
before do
- sign_in Fabricate(:user, admin: true)
+ sign_in Fabricate(:user, role: UserRole.find_by(name: 'Admin'))
end
describe 'GET #show' do
diff --git a/spec/controllers/admin/users/roles_controller.rb b/spec/controllers/admin/users/roles_controller.rb
new file mode 100644
index 000000000..bd6a3fa67
--- /dev/null
+++ b/spec/controllers/admin/users/roles_controller.rb
@@ -0,0 +1,81 @@
+require 'rails_helper'
+
+describe Admin::Users::RolesController do
+ render_views
+
+ let(:current_role) { UserRole.create(name: 'Foo', permissions: UserRole::FLAGS[:manage_roles], position: 10) }
+ let(:current_user) { Fabricate(:user, role: current_role) }
+
+ let(:previous_role) { nil }
+ let(:user) { Fabricate(:user, role: previous_role) }
+
+ before do
+ sign_in current_user, scope: :user
+ end
+
+ describe 'GET #show' do
+ before do
+ get :show, params: { user_id: user.id }
+ end
+
+ it 'returns http success' do
+ expect(response).to have_http_status(:success)
+ end
+
+ context 'when target user is higher ranked than current user' do
+ let(:previous_role) { UserRole.create(name: 'Baz', permissions: UserRole::FLAGS[:administrator], position: 100) }
+
+ it 'returns http forbidden' do
+ expect(response).to have_http_status(:forbidden)
+ end
+ end
+ end
+
+ describe 'PUT #update' do
+ let(:selected_role) { UserRole.create(name: 'Bar', permissions: permissions, position: position) }
+
+ before do
+ put :update, params: { user_id: user.id, user: { role_id: selected_role.id } }
+ end
+
+ context do
+ let(:permissions) { UserRole::FLAGS[:manage_roles] }
+ let(:position) { 1 }
+
+ it 'updates user role' do
+ expect(user.reload.role_id).to eq selected_role&.id
+ end
+
+ it 'redirects back to account page' do
+ expect(response).to redirect_to(admin_account_path(user.account_id))
+ end
+ end
+
+ context 'when selected role has higher position than current user\'s role' do
+ let(:permissions) { UserRole::FLAGS[:administrator] }
+ let(:position) { 100 }
+
+ it 'does not update user role' do
+ expect(user.reload.role_id).to eq previous_role&.id
+ end
+
+ it 'renders edit form' do
+ expect(response).to render_template(:show)
+ end
+ end
+
+ context 'when target user is higher ranked than current user' do
+ let(:previous_role) { UserRole.create(name: 'Baz', permissions: UserRole::FLAGS[:administrator], position: 100) }
+ let(:permissions) { UserRole::FLAGS[:manage_roles] }
+ let(:position) { 1 }
+
+ it 'does not update user role' do
+ expect(user.reload.role_id).to eq previous_role&.id
+ end
+
+ it 'returns http forbidden' do
+ expect(response).to have_http_status(:forbidden)
+ end
+ end
+ end
+end
diff --git a/spec/controllers/admin/two_factor_authentications_controller_spec.rb b/spec/controllers/admin/users/two_factor_authentications_controller_spec.rb
index c65095729..e56264ef6 100644
--- a/spec/controllers/admin/two_factor_authentications_controller_spec.rb
+++ b/spec/controllers/admin/users/two_factor_authentications_controller_spec.rb
@@ -1,12 +1,13 @@
require 'rails_helper'
require 'webauthn/fake_client'
-describe Admin::TwoFactorAuthenticationsController do
+describe Admin::Users::TwoFactorAuthenticationsController do
render_views
let(:user) { Fabricate(:user) }
+
before do
- sign_in Fabricate(:user, admin: true), scope: :user
+ sign_in Fabricate(:user, role: UserRole.find_by(name: 'Admin')), scope: :user
end
describe 'DELETE #destroy' do
diff --git a/spec/controllers/api/v1/admin/account_actions_controller_spec.rb b/spec/controllers/api/v1/admin/account_actions_controller_spec.rb
index 601290b82..199395f55 100644
--- a/spec/controllers/api/v1/admin/account_actions_controller_spec.rb
+++ b/spec/controllers/api/v1/admin/account_actions_controller_spec.rb
@@ -3,7 +3,7 @@ require 'rails_helper'
RSpec.describe Api::V1::Admin::AccountActionsController, type: :controller do
render_views
- let(:role) { 'moderator' }
+ let(:role) { UserRole.find_by(name: 'Moderator') }
let(:user) { Fabricate(:user, role: role) }
let(:scopes) { 'admin:read admin:write' }
let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: scopes) }
@@ -22,7 +22,7 @@ RSpec.describe Api::V1::Admin::AccountActionsController, type: :controller do
end
shared_examples 'forbidden for wrong role' do |wrong_role|
- let(:role) { wrong_role }
+ let(:role) { UserRole.find_by(name: wrong_role) }
it 'returns http forbidden' do
expect(response).to have_http_status(403)
@@ -35,7 +35,7 @@ RSpec.describe Api::V1::Admin::AccountActionsController, type: :controller do
end
it_behaves_like 'forbidden for wrong scope', 'write:statuses'
- it_behaves_like 'forbidden for wrong role', 'user'
+ it_behaves_like 'forbidden for wrong role', ''
it 'returns http success' do
expect(response).to have_http_status(200)
diff --git a/spec/controllers/api/v1/admin/accounts_controller_spec.rb b/spec/controllers/api/v1/admin/accounts_controller_spec.rb
index b69595f7e..cd38030e0 100644
--- a/spec/controllers/api/v1/admin/accounts_controller_spec.rb
+++ b/spec/controllers/api/v1/admin/accounts_controller_spec.rb
@@ -3,7 +3,7 @@ require 'rails_helper'
RSpec.describe Api::V1::Admin::AccountsController, type: :controller do
render_views
- let(:role) { 'moderator' }
+ let(:role) { UserRole.find_by(name: 'Moderator') }
let(:user) { Fabricate(:user, role: role) }
let(:scopes) { 'admin:read admin:write' }
let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: scopes) }
@@ -22,7 +22,7 @@ RSpec.describe Api::V1::Admin::AccountsController, type: :controller do
end
shared_examples 'forbidden for wrong role' do |wrong_role|
- let(:role) { wrong_role }
+ let(:role) { UserRole.find_by(name: wrong_role) }
it 'returns http forbidden' do
expect(response).to have_http_status(403)
@@ -46,7 +46,7 @@ RSpec.describe Api::V1::Admin::AccountsController, type: :controller do
end
it_behaves_like 'forbidden for wrong scope', 'write:statuses'
- it_behaves_like 'forbidden for wrong role', 'user'
+ it_behaves_like 'forbidden for wrong role', ''
[
[{ active: 'true', local: 'true', staff: 'true' }, [:admin_account]],
@@ -77,7 +77,7 @@ RSpec.describe Api::V1::Admin::AccountsController, type: :controller do
end
it_behaves_like 'forbidden for wrong scope', 'write:statuses'
- it_behaves_like 'forbidden for wrong role', 'user'
+ it_behaves_like 'forbidden for wrong role', ''
it 'returns http success' do
expect(response).to have_http_status(200)
@@ -91,7 +91,7 @@ RSpec.describe Api::V1::Admin::AccountsController, type: :controller do
end
it_behaves_like 'forbidden for wrong scope', 'write:statuses'
- it_behaves_like 'forbidden for wrong role', 'user'
+ it_behaves_like 'forbidden for wrong role', ''
it 'returns http success' do
expect(response).to have_http_status(200)
@@ -109,7 +109,7 @@ RSpec.describe Api::V1::Admin::AccountsController, type: :controller do
end
it_behaves_like 'forbidden for wrong scope', 'write:statuses'
- it_behaves_like 'forbidden for wrong role', 'user'
+ it_behaves_like 'forbidden for wrong role', ''
it 'returns http success' do
expect(response).to have_http_status(200)
@@ -127,7 +127,7 @@ RSpec.describe Api::V1::Admin::AccountsController, type: :controller do
end
it_behaves_like 'forbidden for wrong scope', 'write:statuses'
- it_behaves_like 'forbidden for wrong role', 'user'
+ it_behaves_like 'forbidden for wrong role', ''
it 'returns http success' do
expect(response).to have_http_status(200)
@@ -145,7 +145,7 @@ RSpec.describe Api::V1::Admin::AccountsController, type: :controller do
end
it_behaves_like 'forbidden for wrong scope', 'write:statuses'
- it_behaves_like 'forbidden for wrong role', 'user'
+ it_behaves_like 'forbidden for wrong role', ''
it 'returns http success' do
expect(response).to have_http_status(200)
@@ -163,7 +163,7 @@ RSpec.describe Api::V1::Admin::AccountsController, type: :controller do
end
it_behaves_like 'forbidden for wrong scope', 'write:statuses'
- it_behaves_like 'forbidden for wrong role', 'user'
+ it_behaves_like 'forbidden for wrong role', ''
it 'returns http success' do
expect(response).to have_http_status(200)
@@ -181,7 +181,7 @@ RSpec.describe Api::V1::Admin::AccountsController, type: :controller do
end
it_behaves_like 'forbidden for wrong scope', 'write:statuses'
- it_behaves_like 'forbidden for wrong role', 'user'
+ it_behaves_like 'forbidden for wrong role', ''
it 'returns http success' do
expect(response).to have_http_status(200)
diff --git a/spec/controllers/api/v1/admin/domain_allows_controller_spec.rb b/spec/controllers/api/v1/admin/domain_allows_controller_spec.rb
index edee3ab6c..26a391a60 100644
--- a/spec/controllers/api/v1/admin/domain_allows_controller_spec.rb
+++ b/spec/controllers/api/v1/admin/domain_allows_controller_spec.rb
@@ -3,7 +3,7 @@ require 'rails_helper'
RSpec.describe Api::V1::Admin::DomainAllowsController, type: :controller do
render_views
- let(:role) { 'admin' }
+ let(:role) { UserRole.find_by(name: 'Admin') }
let(:user) { Fabricate(:user, role: role) }
let(:scopes) { 'admin:read admin:write' }
let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: scopes) }
@@ -21,7 +21,7 @@ RSpec.describe Api::V1::Admin::DomainAllowsController, type: :controller do
end
shared_examples 'forbidden for wrong role' do |wrong_role|
- let(:role) { wrong_role }
+ let(:role) { UserRole.find_by(name: wrong_role) }
it 'returns http forbidden' do
expect(response).to have_http_status(403)
@@ -36,8 +36,8 @@ RSpec.describe Api::V1::Admin::DomainAllowsController, type: :controller do
end
it_behaves_like 'forbidden for wrong scope', 'write:statuses'
- it_behaves_like 'forbidden for wrong role', 'user'
- it_behaves_like 'forbidden for wrong role', 'moderator'
+ it_behaves_like 'forbidden for wrong role', ''
+ it_behaves_like 'forbidden for wrong role', 'Moderator'
it 'returns http success' do
expect(response).to have_http_status(200)
@@ -58,8 +58,8 @@ RSpec.describe Api::V1::Admin::DomainAllowsController, type: :controller do
end
it_behaves_like 'forbidden for wrong scope', 'write:statuses'
- it_behaves_like 'forbidden for wrong role', 'user'
- it_behaves_like 'forbidden for wrong role', 'moderator'
+ it_behaves_like 'forbidden for wrong role', ''
+ it_behaves_like 'forbidden for wrong role', 'Moderator'
it 'returns http success' do
expect(response).to have_http_status(200)
@@ -79,8 +79,8 @@ RSpec.describe Api::V1::Admin::DomainAllowsController, type: :controller do
end
it_behaves_like 'forbidden for wrong scope', 'write:statuses'
- it_behaves_like 'forbidden for wrong role', 'user'
- it_behaves_like 'forbidden for wrong role', 'moderator'
+ it_behaves_like 'forbidden for wrong role', ''
+ it_behaves_like 'forbidden for wrong role', 'Moderator'
it 'returns http success' do
expect(response).to have_http_status(200)
@@ -99,8 +99,8 @@ RSpec.describe Api::V1::Admin::DomainAllowsController, type: :controller do
end
it_behaves_like 'forbidden for wrong scope', 'write:statuses'
- it_behaves_like 'forbidden for wrong role', 'user'
- it_behaves_like 'forbidden for wrong role', 'moderator'
+ it_behaves_like 'forbidden for wrong role', ''
+ it_behaves_like 'forbidden for wrong role', 'Moderator'
it 'returns http success' do
expect(response).to have_http_status(200)
diff --git a/spec/controllers/api/v1/admin/domain_blocks_controller_spec.rb b/spec/controllers/api/v1/admin/domain_blocks_controller_spec.rb
index 196f6dc28..f12285b2a 100644
--- a/spec/controllers/api/v1/admin/domain_blocks_controller_spec.rb
+++ b/spec/controllers/api/v1/admin/domain_blocks_controller_spec.rb
@@ -3,7 +3,7 @@ require 'rails_helper'
RSpec.describe Api::V1::Admin::DomainBlocksController, type: :controller do
render_views
- let(:role) { 'admin' }
+ let(:role) { UserRole.find_by(name: 'Admin') }
let(:user) { Fabricate(:user, role: role) }
let(:scopes) { 'admin:read admin:write' }
let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: scopes) }
@@ -21,7 +21,7 @@ RSpec.describe Api::V1::Admin::DomainBlocksController, type: :controller do
end
shared_examples 'forbidden for wrong role' do |wrong_role|
- let(:role) { wrong_role }
+ let(:role) { UserRole.find_by(name: wrong_role) }
it 'returns http forbidden' do
expect(response).to have_http_status(403)
@@ -36,8 +36,8 @@ RSpec.describe Api::V1::Admin::DomainBlocksController, type: :controller do
end
it_behaves_like 'forbidden for wrong scope', 'write:statuses'
- it_behaves_like 'forbidden for wrong role', 'user'
- it_behaves_like 'forbidden for wrong role', 'moderator'
+ it_behaves_like 'forbidden for wrong role', ''
+ it_behaves_like 'forbidden for wrong role', 'Moderator'
it 'returns http success' do
expect(response).to have_http_status(200)
@@ -58,8 +58,8 @@ RSpec.describe Api::V1::Admin::DomainBlocksController, type: :controller do
end
it_behaves_like 'forbidden for wrong scope', 'write:statuses'
- it_behaves_like 'forbidden for wrong role', 'user'
- it_behaves_like 'forbidden for wrong role', 'moderator'
+ it_behaves_like 'forbidden for wrong role', ''
+ it_behaves_like 'forbidden for wrong role', 'Moderator'
it 'returns http success' do
expect(response).to have_http_status(200)
@@ -79,8 +79,8 @@ RSpec.describe Api::V1::Admin::DomainBlocksController, type: :controller do
end
it_behaves_like 'forbidden for wrong scope', 'write:statuses'
- it_behaves_like 'forbidden for wrong role', 'user'
- it_behaves_like 'forbidden for wrong role', 'moderator'
+ it_behaves_like 'forbidden for wrong role', ''
+ it_behaves_like 'forbidden for wrong role', 'Moderator'
it 'returns http success' do
expect(response).to have_http_status(200)
@@ -100,8 +100,8 @@ RSpec.describe Api::V1::Admin::DomainBlocksController, type: :controller do
end
it_behaves_like 'forbidden for wrong scope', 'write:statuses'
- it_behaves_like 'forbidden for wrong role', 'user'
- it_behaves_like 'forbidden for wrong role', 'moderator'
+ it_behaves_like 'forbidden for wrong role', ''
+ it_behaves_like 'forbidden for wrong role', 'Moderator'
it 'returns http success' do
expect(response).to have_http_status(200)
diff --git a/spec/controllers/api/v1/admin/reports_controller_spec.rb b/spec/controllers/api/v1/admin/reports_controller_spec.rb
index b6df53048..880e72030 100644
--- a/spec/controllers/api/v1/admin/reports_controller_spec.rb
+++ b/spec/controllers/api/v1/admin/reports_controller_spec.rb
@@ -3,7 +3,7 @@ require 'rails_helper'
RSpec.describe Api::V1::Admin::ReportsController, type: :controller do
render_views
- let(:role) { 'moderator' }
+ let(:role) { UserRole.find_by(name: 'Moderator') }
let(:user) { Fabricate(:user, role: role) }
let(:scopes) { 'admin:read admin:write' }
let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: scopes) }
@@ -22,7 +22,7 @@ RSpec.describe Api::V1::Admin::ReportsController, type: :controller do
end
shared_examples 'forbidden for wrong role' do |wrong_role|
- let(:role) { wrong_role }
+ let(:role) { UserRole.find_by(name: wrong_role) }
it 'returns http forbidden' do
expect(response).to have_http_status(403)
@@ -35,7 +35,7 @@ RSpec.describe Api::V1::Admin::ReportsController, type: :controller do
end
it_behaves_like 'forbidden for wrong scope', 'write:statuses'
- it_behaves_like 'forbidden for wrong role', 'user'
+ it_behaves_like 'forbidden for wrong role', ''
it 'returns http success' do
expect(response).to have_http_status(200)
@@ -48,7 +48,7 @@ RSpec.describe Api::V1::Admin::ReportsController, type: :controller do
end
it_behaves_like 'forbidden for wrong scope', 'write:statuses'
- it_behaves_like 'forbidden for wrong role', 'user'
+ it_behaves_like 'forbidden for wrong role', ''
it 'returns http success' do
expect(response).to have_http_status(200)
@@ -61,7 +61,7 @@ RSpec.describe Api::V1::Admin::ReportsController, type: :controller do
end
it_behaves_like 'forbidden for wrong scope', 'write:statuses'
- it_behaves_like 'forbidden for wrong role', 'user'
+ it_behaves_like 'forbidden for wrong role', ''
it 'returns http success' do
expect(response).to have_http_status(200)
@@ -74,7 +74,7 @@ RSpec.describe Api::V1::Admin::ReportsController, type: :controller do
end
it_behaves_like 'forbidden for wrong scope', 'write:statuses'
- it_behaves_like 'forbidden for wrong role', 'user'
+ it_behaves_like 'forbidden for wrong role', ''
it 'returns http success' do
expect(response).to have_http_status(200)
@@ -87,7 +87,7 @@ RSpec.describe Api::V1::Admin::ReportsController, type: :controller do
end
it_behaves_like 'forbidden for wrong scope', 'write:statuses'
- it_behaves_like 'forbidden for wrong role', 'user'
+ it_behaves_like 'forbidden for wrong role', ''
it 'returns http success' do
expect(response).to have_http_status(200)
@@ -100,7 +100,7 @@ RSpec.describe Api::V1::Admin::ReportsController, type: :controller do
end
it_behaves_like 'forbidden for wrong scope', 'write:statuses'
- it_behaves_like 'forbidden for wrong role', 'user'
+ it_behaves_like 'forbidden for wrong role', ''
it 'returns http success' do
expect(response).to have_http_status(200)
diff --git a/spec/controllers/api/v1/reports_controller_spec.rb b/spec/controllers/api/v1/reports_controller_spec.rb
index b5baf60e1..dbc64e704 100644
--- a/spec/controllers/api/v1/reports_controller_spec.rb
+++ b/spec/controllers/api/v1/reports_controller_spec.rb
@@ -13,7 +13,7 @@ RSpec.describe Api::V1::ReportsController, type: :controller do
end
describe 'POST #create' do
- let!(:admin) { Fabricate(:user, admin: true) }
+ let!(:admin) { Fabricate(:user, role: UserRole.find_by(name: 'Admin')) }
let(:scopes) { 'write:reports' }
let(:status) { Fabricate(:status) }
diff --git a/spec/controllers/api/v2/admin/accounts_controller_spec.rb b/spec/controllers/api/v2/admin/accounts_controller_spec.rb
index 3212ddb84..2508a9e05 100644
--- a/spec/controllers/api/v2/admin/accounts_controller_spec.rb
+++ b/spec/controllers/api/v2/admin/accounts_controller_spec.rb
@@ -3,7 +3,7 @@ require 'rails_helper'
RSpec.describe Api::V2::Admin::AccountsController, type: :controller do
render_views
- let(:role) { 'moderator' }
+ let(:role) { UserRole.find_by(name: 'Moderator') }
let(:user) { Fabricate(:user, role: role) }
let(:scopes) { 'admin:read admin:write' }
let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: scopes) }
@@ -22,7 +22,7 @@ RSpec.describe Api::V2::Admin::AccountsController, type: :controller do
end
shared_examples 'forbidden for wrong role' do |wrong_role|
- let(:role) { wrong_role }
+ let(:role) { UserRole.find_by(name: wrong_role) }
it 'returns http forbidden' do
expect(response).to have_http_status(403)
@@ -46,7 +46,7 @@ RSpec.describe Api::V2::Admin::AccountsController, type: :controller do
end
it_behaves_like 'forbidden for wrong scope', 'write:statuses'
- it_behaves_like 'forbidden for wrong role', 'user'
+ it_behaves_like 'forbidden for wrong role', ''
[
[{ status: 'active', origin: 'local', permissions: 'staff' }, [:admin_account]],
diff --git a/spec/controllers/application_controller_spec.rb b/spec/controllers/application_controller_spec.rb
index 53e163d49..1b002e01c 100644
--- a/spec/controllers/application_controller_spec.rb
+++ b/spec/controllers/application_controller_spec.rb
@@ -183,70 +183,6 @@ describe ApplicationController, type: :controller do
end
end
- describe 'require_admin!' do
- controller do
- before_action :require_admin!
-
- def success
- head 200
- end
- end
-
- before do
- routes.draw { get 'success' => 'anonymous#success' }
- end
-
- it 'returns a 403 if current user is not admin' do
- sign_in(Fabricate(:user, admin: false))
- get 'success'
- expect(response).to have_http_status(403)
- end
-
- it 'returns a 403 if current user is only a moderator' do
- sign_in(Fabricate(:user, moderator: true))
- get 'success'
- expect(response).to have_http_status(403)
- end
-
- it 'does nothing if current user is admin' do
- sign_in(Fabricate(:user, admin: true))
- get 'success'
- expect(response).to have_http_status(200)
- end
- end
-
- describe 'require_staff!' do
- controller do
- before_action :require_staff!
-
- def success
- head 200
- end
- end
-
- before do
- routes.draw { get 'success' => 'anonymous#success' }
- end
-
- it 'returns a 403 if current user is not admin or moderator' do
- sign_in(Fabricate(:user, admin: false, moderator: false))
- get 'success'
- expect(response).to have_http_status(403)
- end
-
- it 'does nothing if current user is moderator' do
- sign_in(Fabricate(:user, moderator: true))
- get 'success'
- expect(response).to have_http_status(200)
- end
-
- it 'does nothing if current user is admin' do
- sign_in(Fabricate(:user, admin: true))
- get 'success'
- expect(response).to have_http_status(200)
- end
- end
-
describe 'forbidden' do
controller do
def route_forbidden
diff --git a/spec/controllers/disputes/appeals_controller_spec.rb b/spec/controllers/disputes/appeals_controller_spec.rb
index faa571fc9..90f222f49 100644
--- a/spec/controllers/disputes/appeals_controller_spec.rb
+++ b/spec/controllers/disputes/appeals_controller_spec.rb
@@ -5,7 +5,7 @@ RSpec.describe Disputes::AppealsController, type: :controller do
before { sign_in current_user, scope: :user }
- let!(:admin) { Fabricate(:user, admin: true) }
+ let!(:admin) { Fabricate(:user, role: UserRole.find_by(name: 'Admin')) }
describe '#create' do
let(:current_user) { Fabricate(:user) }
diff --git a/spec/controllers/invites_controller_spec.rb b/spec/controllers/invites_controller_spec.rb
index 76e617e6b..23b98fb12 100644
--- a/spec/controllers/invites_controller_spec.rb
+++ b/spec/controllers/invites_controller_spec.rb
@@ -7,30 +7,30 @@ describe InvitesController do
sign_in user
end
- around do |example|
- min_invite_role = Setting.min_invite_role
- example.run
- Setting.min_invite_role = min_invite_role
- end
-
describe 'GET #index' do
subject { get :index }
- let(:user) { Fabricate(:user, moderator: false, admin: false) }
+ let(:user) { Fabricate(:user) }
let!(:invite) { Fabricate(:invite, user: user) }
- context 'when user is a staff' do
+ context 'when everyone can invite' do
+ before do
+ UserRole.everyone.update(permissions: UserRole.everyone.permissions | UserRole::FLAGS[:invite_users])
+ end
+
it 'renders index page' do
- Setting.min_invite_role = 'user'
expect(subject).to render_template :index
expect(assigns(:invites)).to include invite
expect(assigns(:invites).count).to eq 1
end
end
- context 'when user is not a staff' do
+ context 'when not everyone can invite' do
+ before do
+ UserRole.everyone.update(permissions: UserRole.everyone.permissions & ~UserRole::FLAGS[:invite_users])
+ end
+
it 'returns 403' do
- Setting.min_invite_role = 'modelator'
expect(subject).to have_http_status 403
end
end
@@ -39,8 +39,12 @@ describe InvitesController do
describe 'POST #create' do
subject { post :create, params: { invite: { max_uses: '10', expires_in: 1800 } } }
- context 'when user is an admin' do
- let(:user) { Fabricate(:user, moderator: false, admin: true) }
+ context 'when everyone can invite' do
+ let(:user) { Fabricate(:user) }
+
+ before do
+ UserRole.everyone.update(permissions: UserRole.everyone.permissions | UserRole::FLAGS[:invite_users])
+ end
it 'succeeds to create a invite' do
expect { subject }.to change { Invite.count }.by(1)
@@ -49,8 +53,12 @@ describe InvitesController do
end
end
- context 'when user is not an admin' do
- let(:user) { Fabricate(:user, moderator: true, admin: false) }
+ context 'when not everyone can invite' do
+ let(:user) { Fabricate(:user) }
+
+ before do
+ UserRole.everyone.update(permissions: UserRole.everyone.permissions & ~UserRole::FLAGS[:invite_users])
+ end
it 'returns 403' do
expect(subject).to have_http_status 403
@@ -61,8 +69,8 @@ describe InvitesController do
describe 'DELETE #create' do
subject { delete :destroy, params: { id: invite.id } }
+ let(:user) { Fabricate(:user) }
let!(:invite) { Fabricate(:invite, user: user, expires_at: nil) }
- let(:user) { Fabricate(:user, moderator: false, admin: true) }
it 'expires invite' do
expect(subject).to redirect_to invites_path
diff --git a/spec/fabricators/user_role_fabricator.rb b/spec/fabricators/user_role_fabricator.rb
new file mode 100644
index 000000000..28f76c8c4
--- /dev/null
+++ b/spec/fabricators/user_role_fabricator.rb
@@ -0,0 +1,5 @@
+Fabricator(:user_role) do
+ name "MyString"
+ color "MyString"
+ permissions ""
+end \ No newline at end of file
diff --git a/spec/models/account_spec.rb b/spec/models/account_spec.rb
index dc0ca3da3..467d41836 100644
--- a/spec/models/account_spec.rb
+++ b/spec/models/account_spec.rb
@@ -445,7 +445,7 @@ RSpec.describe Account, type: :model do
it 'accepts arbitrary limits' do
2.times.each { Fabricate(:account, display_name: "Display Name") }
- results = Account.search_for("display", 1)
+ results = Account.search_for("display", limit: 1)
expect(results.size).to eq 1
end
@@ -473,7 +473,7 @@ RSpec.describe Account, type: :model do
)
account.follow!(match)
- results = Account.advanced_search_for('A?l\i:c e', account, 10, true)
+ results = Account.advanced_search_for('A?l\i:c e', account, limit: 10, following: true)
expect(results).to eq [match]
end
@@ -485,7 +485,7 @@ RSpec.describe Account, type: :model do
domain: 'example.com'
)
- results = Account.advanced_search_for('A?l\i:c e', account, 10, true)
+ results = Account.advanced_search_for('A?l\i:c e', account, limit: 10, following: true)
expect(results).to eq []
end
@@ -498,7 +498,7 @@ RSpec.describe Account, type: :model do
suspended: true
)
- results = Account.advanced_search_for('username', account, 10, true)
+ results = Account.advanced_search_for('username', account, limit: 10, following: true)
expect(results).to eq []
end
@@ -511,7 +511,7 @@ RSpec.describe Account, type: :model do
match.user.update(approved: false)
- results = Account.advanced_search_for('username', account, 10, true)
+ results = Account.advanced_search_for('username', account, limit: 10, following: true)
expect(results).to eq []
end
@@ -524,7 +524,7 @@ RSpec.describe Account, type: :model do
match.user.update(confirmed_at: nil)
- results = Account.advanced_search_for('username', account, 10, true)
+ results = Account.advanced_search_for('username', account, limit: 10, following: true)
expect(results).to eq []
end
end
@@ -588,7 +588,7 @@ RSpec.describe Account, type: :model do
it 'accepts arbitrary limits' do
2.times { Fabricate(:account, display_name: "Display Name") }
- results = Account.advanced_search_for("display", account, 1)
+ results = Account.advanced_search_for("display", account, limit: 1)
expect(results.size).to eq 1
end
diff --git a/spec/models/admin/account_action_spec.rb b/spec/models/admin/account_action_spec.rb
index 809c7fc46..b6a052b76 100644
--- a/spec/models/admin/account_action_spec.rb
+++ b/spec/models/admin/account_action_spec.rb
@@ -5,7 +5,7 @@ RSpec.describe Admin::AccountAction, type: :model do
describe '#save!' do
subject { account_action.save! }
- let(:account) { Fabricate(:user, admin: true).account }
+ let(:account) { Fabricate(:user, role: UserRole.find_by(name: 'Admin')).account }
let(:target_account) { Fabricate(:account) }
let(:type) { 'disable' }
diff --git a/spec/models/user_role_spec.rb b/spec/models/user_role_spec.rb
new file mode 100644
index 000000000..28019593e
--- /dev/null
+++ b/spec/models/user_role_spec.rb
@@ -0,0 +1,189 @@
+require 'rails_helper'
+
+RSpec.describe UserRole, type: :model do
+ subject { described_class.create(name: 'Foo', position: 1) }
+
+ describe '#can?' do
+ context 'with a single flag' do
+ it 'returns true if any of them are present' do
+ subject.permissions = UserRole::FLAGS[:manage_reports]
+ expect(subject.can?(:manage_reports)).to be true
+ end
+
+ it 'returns false if it is not set' do
+ expect(subject.can?(:manage_reports)).to be false
+ end
+ end
+
+ context 'with multiple flags' do
+ it 'returns true if any of them are present' do
+ subject.permissions = UserRole::FLAGS[:manage_users]
+ expect(subject.can?(:manage_reports, :manage_users)).to be true
+ end
+
+ it 'returns false if none of them are present' do
+ expect(subject.can?(:manage_reports, :manage_users)).to be false
+ end
+ end
+
+ context 'with an unknown flag' do
+ it 'raises an error' do
+ expect { subject.can?(:foo) }.to raise_error ArgumentError
+ end
+ end
+ end
+
+ describe '#overrides?' do
+ it 'returns true if other role has lower position' do
+ expect(subject.overrides?(described_class.new(position: subject.position - 1))).to be true
+ end
+
+ it 'returns true if other role is nil' do
+ expect(subject.overrides?(nil)).to be true
+ end
+
+ it 'returns false if other role has higher position' do
+ expect(subject.overrides?(described_class.new(position: subject.position + 1))).to be false
+ end
+ end
+
+ describe '#permissions_as_keys' do
+ before do
+ subject.permissions = UserRole::FLAGS[:invite_users] | UserRole::FLAGS[:view_dashboard] | UserRole::FLAGS[:manage_reports]
+ end
+
+ it 'returns an array' do
+ expect(subject.permissions_as_keys).to match_array %w(invite_users view_dashboard manage_reports)
+ end
+ end
+
+ describe '#permissions_as_keys=' do
+ let(:input) { }
+
+ before do
+ subject.permissions_as_keys = input
+ end
+
+ context 'with a single value' do
+ let(:input) { %w(manage_users) }
+
+ it 'sets permission flags' do
+ expect(subject.permissions).to eq UserRole::FLAGS[:manage_users]
+ end
+ end
+
+ context 'with multiple values' do
+ let(:input) { %w(manage_users manage_reports) }
+
+ it 'sets permission flags' do
+ expect(subject.permissions).to eq UserRole::FLAGS[:manage_users] | UserRole::FLAGS[:manage_reports]
+ end
+ end
+
+ context 'with an unknown value' do
+ let(:input) { %w(foo) }
+
+ it 'does not set permission flags' do
+ expect(subject.permissions).to eq UserRole::Flags::NONE
+ end
+ end
+ end
+
+ describe '#computed_permissions' do
+ context 'when the role is nobody' do
+ let(:subject) { described_class.nobody }
+
+ it 'returns none' do
+ expect(subject.computed_permissions).to eq UserRole::Flags::NONE
+ end
+ end
+
+ context 'when the role is everyone' do
+ let(:subject) { described_class.everyone }
+
+ it 'returns permissions' do
+ expect(subject.computed_permissions).to eq subject.permissions
+ end
+ end
+
+ context 'when role has the administrator flag' do
+ before do
+ subject.permissions = UserRole::FLAGS[:administrator]
+ end
+
+ it 'returns all permissions' do
+ expect(subject.computed_permissions).to eq UserRole::Flags::ALL
+ end
+ end
+
+ context do
+ it 'returns permissions combined with the everyone role' do
+ expect(subject.computed_permissions).to eq described_class.everyone.permissions
+ end
+ end
+ end
+
+ describe '.everyone' do
+ subject { described_class.everyone }
+
+ it 'returns a role' do
+ expect(subject).to be_kind_of(described_class)
+ end
+
+ it 'is identified as the everyone role' do
+ expect(subject.everyone?).to be true
+ end
+
+ it 'has default permissions' do
+ expect(subject.permissions).to eq UserRole::FLAGS[:invite_users]
+ end
+
+ it 'has negative position' do
+ expect(subject.position).to eq -1
+ end
+ end
+
+ describe '.nobody' do
+ subject { described_class.nobody }
+
+ it 'returns a role' do
+ expect(subject).to be_kind_of(described_class)
+ end
+
+ it 'is identified as the nobody role' do
+ expect(subject.nobody?).to be true
+ end
+
+ it 'has no permissions' do
+ expect(subject.permissions).to eq UserRole::Flags::NONE
+ end
+
+ it 'has negative position' do
+ expect(subject.position).to eq -1
+ end
+ end
+
+ describe '#everyone?' do
+ it 'returns true when id is -99' do
+ subject.id = -99
+ expect(subject.everyone?).to be true
+ end
+
+ it 'returns false when id is not -99' do
+ subject.id = 123
+ expect(subject.everyone?).to be false
+ end
+ end
+
+ describe '#nobody?' do
+ it 'returns true when id is nil' do
+ subject.id = nil
+ expect(subject.nobody?).to be true
+ end
+
+ it 'returns false when id is not nil' do
+ subject.id = 123
+ expect(subject.nobody?).to be false
+ end
+ end
+end
diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb
index 1645ab59e..a7da31e60 100644
--- a/spec/models/user_spec.rb
+++ b/spec/models/user_spec.rb
@@ -56,14 +56,6 @@ RSpec.describe User, type: :model do
end
end
- describe 'admins' do
- it 'returns an array of users who are admin' do
- user_1 = Fabricate(:user, admin: false)
- user_2 = Fabricate(:user, admin: true)
- expect(User.admins).to match_array([user_2])
- end
- end
-
describe 'confirmed' do
it 'returns an array of users who are confirmed' do
user_1 = Fabricate(:user, confirmed_at: nil)
@@ -289,49 +281,6 @@ RSpec.describe User, type: :model do
end
end
- describe '#role' do
- it 'returns admin for admin' do
- user = User.new(admin: true)
- expect(user.role).to eq 'admin'
- end
-
- it 'returns moderator for moderator' do
- user = User.new(moderator: true)
- expect(user.role).to eq 'moderator'
- end
-
- it 'returns user otherwise' do
- user = User.new
- expect(user.role).to eq 'user'
- end
- end
-
- describe '#role?' do
- it 'returns false when invalid role requested' do
- user = User.new(admin: true)
- expect(user.role?('disabled')).to be false
- end
-
- it 'returns true when exact role match' do
- user = User.new
- mod = User.new(moderator: true)
- admin = User.new(admin: true)
-
- expect(user.role?('user')).to be true
- expect(mod.role?('moderator')).to be true
- expect(admin.role?('admin')).to be true
- end
-
- it 'returns true when role higher than needed' do
- mod = User.new(moderator: true)
- admin = User.new(admin: true)
-
- expect(mod.role?('user')).to be true
- expect(admin.role?('user')).to be true
- expect(admin.role?('moderator')).to be true
- end
- end
-
describe '#disable!' do
subject(:user) { Fabricate(:user, disabled: false, current_sign_in_at: current_sign_in_at, last_sign_in_at: nil) }
let(:current_sign_in_at) { Time.zone.now }
@@ -420,110 +369,6 @@ RSpec.describe User, type: :model do
end
end
- describe '#promote!' do
- subject(:user) { Fabricate(:user, admin: is_admin, moderator: is_moderator) }
-
- before do
- user.promote!
- end
-
- context 'when user is an admin' do
- let(:is_admin) { true }
-
- context 'when user is a moderator' do
- let(:is_moderator) { true }
-
- it 'changes moderator filed false' do
- expect(user).to be_admin
- expect(user).not_to be_moderator
- end
- end
-
- context 'when user is not a moderator' do
- let(:is_moderator) { false }
-
- it 'does not change status' do
- expect(user).to be_admin
- expect(user).not_to be_moderator
- end
- end
- end
-
- context 'when user is not admin' do
- let(:is_admin) { false }
-
- context 'when user is a moderator' do
- let(:is_moderator) { true }
-
- it 'changes user into an admin' do
- expect(user).to be_admin
- expect(user).not_to be_moderator
- end
- end
-
- context 'when user is not a moderator' do
- let(:is_moderator) { false }
-
- it 'changes user into a moderator' do
- expect(user).not_to be_admin
- expect(user).to be_moderator
- end
- end
- end
- end
-
- describe '#demote!' do
- subject(:user) { Fabricate(:user, admin: admin, moderator: moderator) }
-
- before do
- user.demote!
- end
-
- context 'when user is an admin' do
- let(:admin) { true }
-
- context 'when user is a moderator' do
- let(:moderator) { true }
-
- it 'changes user into a moderator' do
- expect(user).not_to be_admin
- expect(user).to be_moderator
- end
- end
-
- context 'when user is not a moderator' do
- let(:moderator) { false }
-
- it 'changes user into a moderator' do
- expect(user).not_to be_admin
- expect(user).to be_moderator
- end
- end
- end
-
- context 'when user is not an admin' do
- let(:admin) { false }
-
- context 'when user is a moderator' do
- let(:moderator) { true }
-
- it 'changes user into a plain user' do
- expect(user).not_to be_admin
- expect(user).not_to be_moderator
- end
- end
-
- context 'when user is not a moderator' do
- let(:moderator) { false }
-
- it 'does not change any fields' do
- expect(user).not_to be_admin
- expect(user).not_to be_moderator
- end
- end
- end
- end
-
describe '#active_for_authentication?' do
subject { user.active_for_authentication? }
let(:user) { Fabricate(:user, disabled: disabled, confirmed_at: confirmed_at) }
@@ -560,4 +405,8 @@ RSpec.describe User, type: :model do
end
end
end
+
+ describe '.those_who_can' do
+ pending
+ end
end
diff --git a/spec/policies/account_moderation_note_policy_spec.rb b/spec/policies/account_moderation_note_policy_spec.rb
index 39ec2008a..846747346 100644
--- a/spec/policies/account_moderation_note_policy_spec.rb
+++ b/spec/policies/account_moderation_note_policy_spec.rb
@@ -5,7 +5,7 @@ require 'pundit/rspec'
RSpec.describe AccountModerationNotePolicy do
let(:subject) { described_class }
- let(:admin) { Fabricate(:user, admin: true).account }
+ let(:admin) { Fabricate(:user, role: UserRole.find_by(name: 'Admin')).account }
let(:john) { Fabricate(:account) }
permissions :create? do
@@ -31,7 +31,7 @@ RSpec.describe AccountModerationNotePolicy do
context 'admin' do
it 'grants to destroy' do
- expect(subject).to permit(admin, AccountModerationNotePolicy)
+ expect(subject).to permit(admin, account_moderation_note)
end
end
diff --git a/spec/policies/account_policy_spec.rb b/spec/policies/account_policy_spec.rb
index b55eb65a7..0f23fd97e 100644
--- a/spec/policies/account_policy_spec.rb
+++ b/spec/policies/account_policy_spec.rb
@@ -5,7 +5,7 @@ require 'pundit/rspec'
RSpec.describe AccountPolicy do
let(:subject) { described_class }
- let(:admin) { Fabricate(:user, admin: true).account }
+ let(:admin) { Fabricate(:user, role: UserRole.find_by(name: 'Admin')).account }
let(:john) { Fabricate(:account) }
let(:alice) { Fabricate(:account) }
@@ -55,7 +55,7 @@ RSpec.describe AccountPolicy do
end
end
- permissions :redownload?, :subscribe?, :unsubscribe? do
+ permissions :redownload? do
context 'admin' do
it 'permits' do
expect(subject).to permit(admin)
@@ -70,7 +70,7 @@ RSpec.describe AccountPolicy do
end
permissions :suspend?, :silence? do
- let(:staff) { Fabricate(:user, admin: true).account }
+ let(:staff) { Fabricate(:user, role: UserRole.find_by(name: 'Admin')).account }
context 'staff' do
context 'record is staff' do
@@ -94,7 +94,7 @@ RSpec.describe AccountPolicy do
end
permissions :memorialize? do
- let(:other_admin) { Fabricate(:user, admin: true).account }
+ let(:other_admin) { Fabricate(:user, role: UserRole.find_by(name: 'Admin')).account }
context 'admin' do
context 'record is admin' do
diff --git a/spec/policies/custom_emoji_policy_spec.rb b/spec/policies/custom_emoji_policy_spec.rb
index e4f1af3c1..6a6ef6694 100644
--- a/spec/policies/custom_emoji_policy_spec.rb
+++ b/spec/policies/custom_emoji_policy_spec.rb
@@ -5,7 +5,7 @@ require 'pundit/rspec'
RSpec.describe CustomEmojiPolicy do
let(:subject) { described_class }
- let(:admin) { Fabricate(:user, admin: true).account }
+ let(:admin) { Fabricate(:user, role: UserRole.find_by(name: 'Admin')).account }
let(:john) { Fabricate(:account) }
permissions :index?, :enable?, :disable? do
diff --git a/spec/policies/domain_block_policy_spec.rb b/spec/policies/domain_block_policy_spec.rb
index b24ed9e3a..01b97e823 100644
--- a/spec/policies/domain_block_policy_spec.rb
+++ b/spec/policies/domain_block_policy_spec.rb
@@ -5,7 +5,7 @@ require 'pundit/rspec'
RSpec.describe DomainBlockPolicy do
let(:subject) { described_class }
- let(:admin) { Fabricate(:user, admin: true).account }
+ let(:admin) { Fabricate(:user, role: UserRole.find_by(name: 'Admin')).account }
let(:john) { Fabricate(:account) }
permissions :index?, :show?, :create?, :destroy? do
diff --git a/spec/policies/email_domain_block_policy_spec.rb b/spec/policies/email_domain_block_policy_spec.rb
index 1ff55af8e..913075c3d 100644
--- a/spec/policies/email_domain_block_policy_spec.rb
+++ b/spec/policies/email_domain_block_policy_spec.rb
@@ -5,7 +5,7 @@ require 'pundit/rspec'
RSpec.describe EmailDomainBlockPolicy do
let(:subject) { described_class }
- let(:admin) { Fabricate(:user, admin: true).account }
+ let(:admin) { Fabricate(:user, role: UserRole.find_by(name: 'Admin')).account }
let(:john) { Fabricate(:account) }
permissions :index?, :create?, :destroy? do
diff --git a/spec/policies/instance_policy_spec.rb b/spec/policies/instance_policy_spec.rb
index 71ef1fe50..f6f51af06 100644
--- a/spec/policies/instance_policy_spec.rb
+++ b/spec/policies/instance_policy_spec.rb
@@ -5,7 +5,7 @@ require 'pundit/rspec'
RSpec.describe InstancePolicy do
let(:subject) { described_class }
- let(:admin) { Fabricate(:user, admin: true).account }
+ let(:admin) { Fabricate(:user, role: UserRole.find_by(name: 'Admin')).account }
let(:john) { Fabricate(:account) }
permissions :index?, :show?, :destroy? do
diff --git a/spec/policies/invite_policy_spec.rb b/spec/policies/invite_policy_spec.rb
index 122137804..01660322f 100644
--- a/spec/policies/invite_policy_spec.rb
+++ b/spec/policies/invite_policy_spec.rb
@@ -5,8 +5,8 @@ require 'pundit/rspec'
RSpec.describe InvitePolicy do
let(:subject) { described_class }
- let(:admin) { Fabricate(:user, admin: true).account }
- let(:john) { Fabricate(:account) }
+ let(:admin) { Fabricate(:user, role: UserRole.find_by(name: 'Admin')).account }
+ let(:john) { Fabricate(:user).account }
permissions :index? do
context 'staff?' do
@@ -17,16 +17,22 @@ RSpec.describe InvitePolicy do
end
permissions :create? do
- context 'min_required_role?' do
+ context 'has privilege' do
+ before do
+ UserRole.everyone.update(permissions: UserRole::FLAGS[:invite_users])
+ end
+
it 'permits' do
- allow_any_instance_of(described_class).to receive(:min_required_role?) { true }
expect(subject).to permit(john, Invite)
end
end
- context 'not min_required_role?' do
+ context 'does not have privilege' do
+ before do
+ UserRole.everyone.update(permissions: UserRole::Flags::NONE)
+ end
+
it 'denies' do
- allow_any_instance_of(described_class).to receive(:min_required_role?) { false }
expect(subject).to_not permit(john, Invite)
end
end
@@ -54,39 +60,15 @@ RSpec.describe InvitePolicy do
end
context 'not owner?' do
- context 'Setting.min_invite_role == "admin"' do
- before do
- Setting.min_invite_role = 'admin'
- end
-
- context 'admin?' do
- it 'permits' do
- expect(subject).to permit(admin, Fabricate(:invite))
- end
- end
-
- context 'not admin?' do
- it 'denies' do
- expect(subject).to_not permit(john, Fabricate(:invite))
- end
+ context 'admin?' do
+ it 'permits' do
+ expect(subject).to permit(admin, Fabricate(:invite))
end
end
- context 'Setting.min_invite_role != "admin"' do
- before do
- Setting.min_invite_role = 'else'
- end
-
- context 'staff?' do
- it 'permits' do
- expect(subject).to permit(admin, Fabricate(:invite))
- end
- end
-
- context 'not staff?' do
- it 'denies' do
- expect(subject).to_not permit(john, Fabricate(:invite))
- end
+ context 'not admin?' do
+ it 'denies' do
+ expect(subject).to_not permit(john, Fabricate(:invite))
end
end
end
diff --git a/spec/policies/relay_policy_spec.rb b/spec/policies/relay_policy_spec.rb
index 139d945dc..2c50ba1e9 100644
--- a/spec/policies/relay_policy_spec.rb
+++ b/spec/policies/relay_policy_spec.rb
@@ -5,7 +5,7 @@ require 'pundit/rspec'
RSpec.describe RelayPolicy do
let(:subject) { described_class }
- let(:admin) { Fabricate(:user, admin: true).account }
+ let(:admin) { Fabricate(:user, role: UserRole.find_by(name: 'Admin')).account }
let(:john) { Fabricate(:account) }
permissions :update? do
diff --git a/spec/policies/report_note_policy_spec.rb b/spec/policies/report_note_policy_spec.rb
index c34f99b71..99f5ffb8e 100644
--- a/spec/policies/report_note_policy_spec.rb
+++ b/spec/policies/report_note_policy_spec.rb
@@ -5,7 +5,7 @@ require 'pundit/rspec'
RSpec.describe ReportNotePolicy do
let(:subject) { described_class }
- let(:admin) { Fabricate(:user, admin: true).account }
+ let(:admin) { Fabricate(:user, role: UserRole.find_by(name: 'Admin')).account }
let(:john) { Fabricate(:account) }
permissions :create? do
@@ -25,7 +25,8 @@ RSpec.describe ReportNotePolicy do
permissions :destroy? do
context 'admin?' do
it 'permit' do
- expect(subject).to permit(admin, ReportNote)
+ report_note = Fabricate(:report_note, account: john)
+ expect(subject).to permit(admin, report_note)
end
end
diff --git a/spec/policies/report_policy_spec.rb b/spec/policies/report_policy_spec.rb
index 84c366d7f..8b005d8dd 100644
--- a/spec/policies/report_policy_spec.rb
+++ b/spec/policies/report_policy_spec.rb
@@ -5,7 +5,7 @@ require 'pundit/rspec'
RSpec.describe ReportPolicy do
let(:subject) { described_class }
- let(:admin) { Fabricate(:user, admin: true).account }
+ let(:admin) { Fabricate(:user, role: UserRole.find_by(name: 'Admin')).account }
let(:john) { Fabricate(:account) }
permissions :update?, :index?, :show? do
diff --git a/spec/policies/settings_policy_spec.rb b/spec/policies/settings_policy_spec.rb
index 3fa183c50..e16ee51a4 100644
--- a/spec/policies/settings_policy_spec.rb
+++ b/spec/policies/settings_policy_spec.rb
@@ -5,7 +5,7 @@ require 'pundit/rspec'
RSpec.describe SettingsPolicy do
let(:subject) { described_class }
- let(:admin) { Fabricate(:user, admin: true).account }
+ let(:admin) { Fabricate(:user, role: UserRole.find_by(name: 'Admin')).account }
let(:john) { Fabricate(:account) }
permissions :update?, :show? do
diff --git a/spec/policies/status_policy_spec.rb b/spec/policies/status_policy_spec.rb
index 28b808ee2..205ecd720 100644
--- a/spec/policies/status_policy_spec.rb
+++ b/spec/policies/status_policy_spec.rb
@@ -6,7 +6,7 @@ require 'pundit/rspec'
RSpec.describe StatusPolicy, type: :model do
subject { described_class }
- let(:admin) { Fabricate(:user, admin: true) }
+ let(:admin) { Fabricate(:user, role: UserRole.find_by(name: 'Admin')) }
let(:alice) { Fabricate(:account, username: 'alice') }
let(:bob) { Fabricate(:account, username: 'bob') }
let(:status) { Fabricate(:status, account: alice) }
diff --git a/spec/policies/tag_policy_spec.rb b/spec/policies/tag_policy_spec.rb
index 256e6786a..9be7140fc 100644
--- a/spec/policies/tag_policy_spec.rb
+++ b/spec/policies/tag_policy_spec.rb
@@ -5,7 +5,7 @@ require 'pundit/rspec'
RSpec.describe TagPolicy do
let(:subject) { described_class }
- let(:admin) { Fabricate(:user, admin: true).account }
+ let(:admin) { Fabricate(:user, role: UserRole.find_by(name: 'Admin')).account }
let(:john) { Fabricate(:account) }
permissions :index?, :show?, :update? do
diff --git a/spec/policies/user_policy_spec.rb b/spec/policies/user_policy_spec.rb
index 731c041d1..ff0916674 100644
--- a/spec/policies/user_policy_spec.rb
+++ b/spec/policies/user_policy_spec.rb
@@ -5,7 +5,7 @@ require 'pundit/rspec'
RSpec.describe UserPolicy do
let(:subject) { described_class }
- let(:admin) { Fabricate(:user, admin: true).account }
+ let(:admin) { Fabricate(:user, role: UserRole.find_by(name: 'Admin')).account }
let(:john) { Fabricate(:account) }
permissions :reset_password?, :change_email? do
@@ -111,57 +111,4 @@ RSpec.describe UserPolicy do
end
end
end
-
- permissions :promote? do
- context 'admin?' do
- context 'promotable?' do
- it 'permits' do
- expect(subject).to permit(admin, john.user)
- end
- end
-
- context '!promotable?' do
- it 'denies' do
- expect(subject).to_not permit(admin, admin.user)
- end
- end
- end
-
- context '!admin?' do
- it 'denies' do
- expect(subject).to_not permit(john, User)
- end
- end
- end
-
- permissions :demote? do
- context 'admin?' do
- context '!record.admin?' do
- context 'demoteable?' do
- it 'permits' do
- john.user.update(moderator: true)
- expect(subject).to permit(admin, john.user)
- end
- end
-
- context '!demoteable?' do
- it 'denies' do
- expect(subject).to_not permit(admin, john.user)
- end
- end
- end
-
- context 'record.admin?' do
- it 'denies' do
- expect(subject).to_not permit(admin, admin.user)
- end
- end
- end
-
- context '!admin?' do
- it 'denies' do
- expect(subject).to_not permit(john, User)
- end
- end
- end
end