mirror of https://github.com/tootsuite/mastodon
				
				
				
			
		
			
				
	
	
		
			199 lines
		
	
	
		
			5.3 KiB
		
	
	
	
		
			Ruby
		
	
	
			
		
		
	
	
			199 lines
		
	
	
		
			5.3 KiB
		
	
	
	
		
			Ruby
		
	
	
| # 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
 | |
| 
 | |
|   EVERYONE_ROLE_ID = -99
 | |
|   NOBODY_POSITION = -1
 | |
| 
 | |
|   module Flags
 | |
|     NONE = 0
 | |
|     ALL  = FLAGS.values.reduce(&:|)
 | |
| 
 | |
|     DEFAULT = FLAGS[:invite_users]
 | |
| 
 | |
|     CATEGORIES = {
 | |
|       invites: %i(
 | |
|         invite_users
 | |
|       ).freeze,
 | |
| 
 | |
|       moderation: %i(
 | |
|         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: %i(
 | |
|         manage_settings
 | |
|         manage_rules
 | |
|         manage_roles
 | |
|         manage_webhooks
 | |
|         manage_custom_emojis
 | |
|         manage_announcements
 | |
|       ).freeze,
 | |
| 
 | |
|       devops: %i(
 | |
|         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
 | |
|   validate :validate_own_role_edition
 | |
| 
 | |
|   before_validation :set_position
 | |
| 
 | |
|   scope :assignable, -> { where.not(id: EVERYONE_ROLE_ID).order(position: :asc) }
 | |
|   scope :highlighted, -> { where(highlighted: true) }
 | |
|   scope :with_color, -> { where.not(color: [nil, '']) }
 | |
|   scope :providing_styles, -> { highlighted.with_color }
 | |
| 
 | |
|   has_many :users, inverse_of: :role, foreign_key: 'role_id', dependent: :nullify
 | |
| 
 | |
|   def self.nobody
 | |
|     @nobody ||= UserRole.new(permissions: Flags::NONE, position: NOBODY_POSITION)
 | |
|   end
 | |
| 
 | |
|   def self.everyone
 | |
|     UserRole.find(EVERYONE_ROLE_ID)
 | |
|   rescue ActiveRecord::RecordNotFound
 | |
|     UserRole.create!(id: EVERYONE_ROLE_ID, permissions: Flags::DEFAULT)
 | |
|   end
 | |
| 
 | |
|   def self.that_can(*any_of_privileges)
 | |
|     all.select { |role| role.can?(*any_of_privileges) }
 | |
|   end
 | |
| 
 | |
|   def everyone?
 | |
|     id == EVERYONE_ROLE_ID
 | |
|   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.filter_map(&:presence).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
 | |
| 
 | |
|   def to_log_human_identifier
 | |
|     name
 | |
|   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 = NOBODY_POSITION if everyone?
 | |
|   end
 | |
| 
 | |
|   def validate_own_role_edition
 | |
|     return unless defined?(@current_account) && @current_account.user_role.id == id
 | |
| 
 | |
|     errors.add(:permissions_as_keys, :own_role) if permissions_changed?
 | |
|     errors.add(:position, :own_role) if position_changed?
 | |
|   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
 |