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
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
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
layout 'admin'
- before_action :require_staff!
before_action :set_pack
before_action :set_body_classes
+ after_action :verify_authorized
private
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
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
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
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
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
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
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
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
+++ /dev/null
-# 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
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
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
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
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
--- /dev/null
+# 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
# frozen_string_literal: true
module Admin
- class TwoFactorAuthenticationsController < BaseController
+ class Users::TwoFactorAuthenticationsController < BaseController
before_action :set_target_user
def destroy
# 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
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(
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
# 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
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
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
# 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
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(
# 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
# 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
# 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
# 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
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
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
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
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
private
+ def enabled?
+ Setting.trends
+ end
+
# Retrieve a comma-separated list of tags from the environment variable
# `ALWAYS_TRENDING_TAGS`, which will always be reported as trending by
# {#index}.
# Note that having too many tags always trending will render {#index}
# completely deterministic (as per {#BaseController::limit_param})!
# TODO: is that desirable? should we log a warning in that case?
+
def set_tags
- @tags = if !Setting.trends
+ @tags = if !enabled?
[]
else
guaranteed_tags = always_trending
# TODO: how does the query handle negative limits? is this necessary?
limit_considering_guaranteed = [0, limit_param(DEFAULT_TAGS_LIMIT) - guaranteed_tags.size].max
- guaranteed_tags | Trends.tags.query.allowed.offset(offset_param).limit(limit_considering_guaranteed)
+ guaranteed_tags | tags_from_trends.offset(offset_param).limit(limit_considering_guaranteed)
end
end
+ def tags_from_trends
+ Trends.tags.query.allowed
+ end
+
def insert_pagination_headers
set_pagination_headers(next_path, prev_path)
end
email
ip
invited_by
+ role_ids
).freeze
PAGINATION_PARAMS = (%i(limit) + FILTER_PARAMS).freeze
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
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
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
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
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' },
static contextTypes = {
router: PropTypes.object,
+ identity: PropTypes.object,
};
static propTypes = {
}
}
- 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')}` });
signedIn: !!state.meta.me,
accountId: state.meta.me,
accessToken: state.meta.access_token,
+ permissions: state.role.permissions,
});
export default class Mastodon extends React.PureComponent {
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';
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' },
export default @injectIntl
class Header extends ImmutablePureComponent {
+ static contextTypes = {
+ identity: PropTypes.object,
+ };
+
static propTypes = {
account: ImmutablePropTypes.map,
identity_props: ImmutablePropTypes.list,
}
}
- 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')}` });
}
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,
</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>
</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>
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' },
static contextTypes = {
router: PropTypes.object,
+ identity: PropTypes.object,
};
static propTypes = {
}
}
- 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')}` });
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?' },
@connect(null, mapDispatchToProps)
class LinkFooter extends React.PureComponent {
+ static contextTypes = {
+ identity: PropTypes.object,
+ };
+
static propTypes = {
withHotkeys: PropTypes.bool,
onLogout: PropTypes.func.isRequired,
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>}
export const me = getMeta('me');
export const searchEnabled = getMeta('search_enabled');
export const maxChars = (initialState && initialState.max_toot_chars) || 500;
-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');
--- /dev/null
+export const PERMISSION_INVITE_USERS = 0x0000000000010000;
+export const PERMISSION_MANAGE_USERS = 0x0000000000000400;
+export const PERMISSION_MANAGE_REPORTS = 0x0000000000000010;
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:
margin-top: 15px;
}
+.user-role {
+ color: var(--user-role-accent);
+}
+
.announcements-list,
.filters-list {
border: 1px solid lighten($ui-base-color, 4%);
&__meta {
padding: 0 15px;
color: $dark-text-color;
+
+ a {
+ color: inherit;
+ text-decoration: underline;
+
+ &:hover,
+ &:focus,
+ &:active {
+ text-decoration: none;
+ }
+ }
}
&__action-bar {
}
}
+ .input.with_block_label.user_role_permissions_as_keys ul {
+ columns: unset;
+ }
+
.input.datetime .label_input select {
display: inline-block;
width: auto;
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
# 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
# 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
# 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?
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
class Admin::SystemCheck::RulesCheck < Admin::SystemCheck::BaseCheck
include RoutingHelper
+ def skip?
+ !current_user.can?(:manage_rules)
+ end
+
def pass?
Rule.kept.exists?
end
scheduler
).freeze
+ def skip?
+ !current_user.can?(:view_devops)
+ end
+
def pass?
missing_queues.empty?
end
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))) }
:unconfirmed?,
:unconfirmed_or_pending?,
:role,
- :admin?,
- :moderator?,
- :staff?,
:locale,
:shows_application?,
to: :user,
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
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])
KEYS = %i(
origin
status
- permissions
+ role_ids
username
by_domain
display_name
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
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'
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
end
def valid_ip?(value)
- IPAddr.new(value) && true
+ IPAddr.new(value.to_s) && true
rescue IPAddr::InvalidAddressError
false
end
+++ /dev/null
-# 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
closed_registrations_message
open_deletion
timeline_preview
- show_staff_badge
bootstrap_timeline_accounts
flavour
skin
- min_invite_role
activity_api_enabled
peers_api_enabled
show_known_fediverse_at_about_page
BOOLEAN_KEYS = %i(
open_deletion
timeline_preview
- show_staff_badge
activity_api_enabled
peers_api_enabled
show_known_fediverse_at_about_page
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 }
tags_requiring_review = tags.request_review
statuses_requiring_review = statuses.request_review
- User.staff.includes(:account).find_each do |user|
+ User.those_who_can(:manage_taxonomies).includes(:account).find_each do |user|
links = user.allows_trending_links_review_emails? ? links_requiring_review : []
tags = user.allows_trending_tags_review_emails? ? tags_requiring_review : []
statuses = user.allows_trending_statuses_review_emails? ? statuses_requiring_review : []
# sign_in_token_sent_at :datetime
# webauthn_id :string
# sign_up_ip :inet
+# role_id :bigint(8)
#
class User < ApplicationRecord
)
include Settings::Extend
- include UserRoles
include Redisable
include LanguagesHelper
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
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) }
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
:disable_swiping, :always_send_emails, :default_content_type, :system_emoji_font,
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?
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')
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
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
--- /dev/null
+# 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
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
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
class AccountWarningPolicy < ApplicationPolicy
def show?
- target? || staff?
+ target? || role.can?(:manage_appeals)
end
def appeal?
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
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
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
@record = record
end
- delegate :admin?, :moderator?, :staff?, to: :current_user, allow_nil: true
-
private
def current_user
def user_signed_in?
!current_user.nil?
end
+
+ def role
+ current_user&.role || UserRole.nobody
+ end
end
--- /dev/null
+# frozen_string_literal: true
+
+class AuditLogPolicy < ApplicationPolicy
+ def index?
+ role.can?(:view_audit_log)
+ end
+end
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
--- /dev/null
+# frozen_string_literal: true
+
+class DashboardPolicy < ApplicationPolicy
+ def index?
+ role.can?(:view_dashboard)
+ end
+end
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
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
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
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
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
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
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
def owner?
record.user_id == current_user&.id
end
-
- def min_required_role?
- current_user&.role?(Setting.min_invite_role)
- end
end
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
class PreviewCardPolicy < ApplicationPolicy
def index?
- staff?
+ role.can?(:manage_taxonomies)
end
def review?
- staff?
+ role.can?(:manage_taxonomies)
end
end
class PreviewCardProviderPolicy < ApplicationPolicy
def index?
- staff?
+ role.can?(:manage_taxonomies)
end
def review?
- staff?
+ role.can?(:manage_taxonomies)
end
end
class RelayPolicy < ApplicationPolicy
def update?
- admin?
+ role.can?(:manage_federation)
end
end
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
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
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
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
end
def index?
- staff?
+ role.can?(:manage_reports, :manage_users)
end
def show?
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
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
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
--- /dev/null
+# 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
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
class InitialStatePresenter < ActiveModelSerializers::Model
attributes :settings, :push_subscription, :token,
:current_account, :admin, :text, :visibility
+
+ def role
+ current_account&.user_role
+ end
end
:languages
has_one :push_subscription, serializer: REST::WebPushSubscriptionSerializer
+ has_one :role, serializer: REST::RoleSerializer
def max_toot_chars
StatusLengthValidator::MAX_CHARS
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,
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[:default_content_type] = object.current_account.user.setting_default_content_type
store[:system_emoji_font] = object.current_account.user.setting_system_emoji_font
class REST::CredentialAccountSerializer < REST::AccountSerializer
attributes :source
+ has_one :role, serializer: REST::RoleSerializer
+
def source
user = object.user
follow_requests_count: FollowRequest.where(target_account: object).limit(40).count,
}
end
+
+ def role
+ object.user_role
+ end
end
end
def invites_enabled
- Setting.min_invite_role == 'user'
+ UserRole.everyone.can?(:invite_users)
end
private
--- /dev/null
+# 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
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
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
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
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?
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
- content_for :page_title do
= t('admin.accounts.title')
-.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
%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')
.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')
- content_for :page_title do
= @instance.domain
-- 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/
--- /dev/null
+= 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
--- /dev/null
+.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)
--- /dev/null
+- 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'
+
--- /dev/null
+- 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?)
--- /dev/null
+- content_for :page_title do
+ = t('admin.roles.add_new')
+
+= render partial: 'form'
.fields-group
= 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')
%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'
- content_for :page_title do
= "##{@tag.name}"
-- 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'
-
- = 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'
-
-%hr.spacer/
+- 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'
+
+ = 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'
+
+ %hr.spacer/
= simple_form_for @tag, url: admin_tag_path(@tag.id) do |f|
= render 'shared/error_messages', object: @tag
--- /dev/null
+- 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
--- /dev/null
+<%- 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 %>
= render partial: 'layouts/theme', object: @core
= render partial: 'layouts/theme', object: @theme
- - if Setting.custom_css.present?
- = stylesheet_link_tag custom_css_path, host: request.host, media: 'all'
+ = stylesheet_link_tag custom_css_path, host: request.host, media: 'all'
%body{ class: body_classes }
= content_for?(:content) ? yield(:content) : yield
= 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 :trending_link, as: :boolean, wrapper: :with_label
- = ff.input :trending_status, 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)
+ = ff.input :trending_link, as: :boolean, wrapper: :with_label if current_user.can?(:manage_taxonomies)
+ = ff.input :trending_status, as: :boolean, wrapper: :with_label if current_user.can?(:manage_taxonomies)
.fields-group
= f.input :setting_always_send_emails, as: :boolean, wrapper: :with_label
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
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
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:
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
active: Active
all: All
pending: Pending
+ silenced: Limited
suspended: Suspended
title: Moderation
moderation_notes: Moderation notes
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
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
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
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
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><a></code> and <code><em></code>.
title: Server description
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
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
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 :flavours, safe_join([fa_icon('paint-brush fw'), t('settings.flavours')]), settings_flavours_url do |flavours|
+ n.item :flavours, safe_join([fa_icon('paint-brush fw'), t('settings.flavours')]), settings_flavours_path do |flavours|
Themes.instance.flavours.each do |flavour|
- flavours.item flavour.to_sym, safe_join([fa_icon('star fw'), t("flavours.#{flavour}.name", default: flavour)]), settings_flavour_url(flavour)
+ flavours.item flavour.to_sym, safe_join([fa_icon('star fw'), t("flavours.#{flavour}.name", default: flavour)]), settings_flavour_path(flavour)
end
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
--- /dev/null
+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
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
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
end
end
+ resources :roles, except: [:show]
resources :account_moderation_notes, only: [:create, :destroy]
resource :follow_recommendations, only: [:show, :update]
resources :tags, only: [:show, :update]
--- /dev/null
+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
--- /dev/null
+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
--- /dev/null
+# 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
--- /dev/null
+# 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
#
# 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"
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
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|
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
-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
--- /dev/null
+Doorkeeper::Application.create_with(name: 'Web', redirect_uri: Doorkeeper.configuration.native_redirect_uri, scopes: 'read write follow push').find_or_create_by(superapp: true)
--- /dev/null
+Account.create_with(actor_type: 'Application', locked: true, username: ENV['LOCAL_DOMAIN'] || Rails.configuration.x.local_domain).find_or_create_by(id: -99)
--- /dev/null
+# 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
--- /dev/null
+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
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'
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
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)
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
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.
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]
user.errors.to_h.each do |key, error|
say('Failure/Error: ', :red)
say(key)
- say(' ' + error, :red)
+ say(" #{error}", :red)
end
exit(1)
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
--- /dev/null
+# 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)
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
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
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
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
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)
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
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
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)
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
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
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)
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
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)
end
context 'when user is not admin' do
- let(:admin) { false }
+ let(:role) { UserRole.everyone }
it 'fails to remove avatar' do
subject
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)
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)
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
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
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
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
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
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))
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))
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
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
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') }
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
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
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
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
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
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
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
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
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) }
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
--- /dev/null
+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
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
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) }
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)
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)
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) }
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)
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]],
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)
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)
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)
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)
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)
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)
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)
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) }
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)
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)
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)
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)
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)
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) }
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)
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)
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)
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)
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)
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) }
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)
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)
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)
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)
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)
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)
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)
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) }
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) }
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)
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]],
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
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) }
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
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)
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
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
--- /dev/null
+Fabricator(:user_role) do
+ name "MyString"
+ color "MyString"
+ permissions ""
+end
\ No newline at end of file
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
)
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
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
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
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
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
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
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' }
--- /dev/null
+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
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)
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 }
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) }
end
end
end
+
+ describe '.those_who_can' do
+ pending
+ end
end
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
context 'admin' do
it 'grants to destroy' do
- expect(subject).to permit(admin, AccountModerationNotePolicy)
+ expect(subject).to permit(admin, account_moderation_note)
end
end
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) }
end
end
- permissions :redownload?, :subscribe?, :unsubscribe? do
+ permissions :redownload? do
context 'admin' do
it 'permits' do
expect(subject).to permit(admin)
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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) }
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
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
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