diff options
Diffstat (limited to 'app/models/user_role.rb')
-rw-r--r-- | app/models/user_role.rb | 179 |
1 files changed, 179 insertions, 0 deletions
diff --git a/app/models/user_role.rb b/app/models/user_role.rb new file mode 100644 index 000000000..833b96d71 --- /dev/null +++ b/app/models/user_role.rb @@ -0,0 +1,179 @@ +# frozen_string_literal: true + +# == Schema Information +# +# Table name: user_roles +# +# id :bigint(8) not null, primary key +# name :string default(""), not null +# color :string default(""), not null +# position :integer default(0), not null +# permissions :bigint(8) default(0), not null +# highlighted :boolean default(FALSE), not null +# created_at :datetime not null +# updated_at :datetime not null +# + +class UserRole < ApplicationRecord + FLAGS = { + administrator: (1 << 0), + view_devops: (1 << 1), + view_audit_log: (1 << 2), + view_dashboard: (1 << 3), + manage_reports: (1 << 4), + manage_federation: (1 << 5), + manage_settings: (1 << 6), + manage_blocks: (1 << 7), + manage_taxonomies: (1 << 8), + manage_appeals: (1 << 9), + manage_users: (1 << 10), + manage_invites: (1 << 11), + manage_rules: (1 << 12), + manage_announcements: (1 << 13), + manage_custom_emojis: (1 << 14), + manage_webhooks: (1 << 15), + invite_users: (1 << 16), + manage_roles: (1 << 17), + manage_user_access: (1 << 18), + delete_user_data: (1 << 19), + }.freeze + + module Flags + NONE = 0 + ALL = FLAGS.values.reduce(&:|) + + DEFAULT = FLAGS[:invite_users] + + CATEGORIES = { + invites: %i( + invite_users + ).freeze, + + moderation: %w( + view_dashboard + view_audit_log + manage_users + manage_user_access + delete_user_data + manage_reports + manage_appeals + manage_federation + manage_blocks + manage_taxonomies + manage_invites + ).freeze, + + administration: %w( + manage_settings + manage_rules + manage_roles + manage_webhooks + manage_custom_emojis + manage_announcements + ).freeze, + + devops: %w( + view_devops + ).freeze, + + special: %i( + administrator + ).freeze, + }.freeze + end + + attr_writer :current_account + + validates :name, presence: true, unless: :everyone? + validates :color, format: { with: /\A#?(?:[A-F0-9]{3}){1,2}\z/i }, unless: -> { color.blank? } + + validate :validate_permissions_elevation + validate :validate_position_elevation + validate :validate_dangerous_permissions + + before_validation :set_position + + scope :assignable, -> { where.not(id: -99).order(position: :asc) } + + has_many :users, inverse_of: :role, foreign_key: 'role_id', dependent: :nullify + + def self.nobody + @nobody ||= UserRole.new(permissions: Flags::NONE, position: -1) + end + + def self.everyone + UserRole.find(-99) + rescue ActiveRecord::RecordNotFound + UserRole.create!(id: -99, permissions: Flags::DEFAULT) + end + + def self.that_can(*any_of_privileges) + all.select { |role| role.can?(*any_of_privileges) } + end + + def everyone? + id == -99 + end + + def nobody? + id.nil? + end + + def permissions_as_keys + FLAGS.keys.select { |privilege| permissions & FLAGS[privilege] == FLAGS[privilege] }.map(&:to_s) + end + + def permissions_as_keys=(value) + self.permissions = value.map(&:presence).compact.reduce(Flags::NONE) { |bitmask, privilege| FLAGS.key?(privilege.to_sym) ? (bitmask | FLAGS[privilege.to_sym]) : bitmask } + end + + def can?(*any_of_privileges) + any_of_privileges.any? { |privilege| in_permissions?(privilege) } + end + + def overrides?(other_role) + other_role.nil? || position > other_role.position + end + + def computed_permissions + # If called on the everyone role, no further computation needed + return permissions if everyone? + + # If called on the nobody role, no permissions are there to be given + return Flags::NONE if nobody? + + # Otherwise, compute permissions based on special conditions + @computed_permissions ||= begin + permissions = self.class.everyone.permissions | self.permissions + + if permissions & FLAGS[:administrator] == FLAGS[:administrator] + Flags::ALL + else + permissions + end + end + end + + private + + def in_permissions?(privilege) + raise ArgumentError, "Unknown privilege: #{privilege}" unless FLAGS.key?(privilege) + computed_permissions & FLAGS[privilege] == FLAGS[privilege] + end + + def set_position + self.position = -1 if everyone? + end + + def validate_permissions_elevation + errors.add(:permissions_as_keys, :elevated) if defined?(@current_account) && @current_account.user_role.computed_permissions & permissions != permissions + end + + def validate_position_elevation + errors.add(:position, :elevated) if defined?(@current_account) && @current_account.user_role.position < position + end + + def validate_dangerous_permissions + errors.add(:permissions_as_keys, :dangerous) if everyone? && Flags::DEFAULT & permissions != permissions + end +end |