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