mirror of
https://github.com/discourse/discourse.git
synced 2026-08-06 13:08:40 +08:00
Introduces a server-side definition of banned_acl,
similar to mandatory_acl from 5823e4e3b2
This allows AclTarget implementing classes to define
which ACLs cannot be used for certain types. For example:
```
def self.banned_acl
[{ type: :group, id: Group::AUTO_GROUPS[:anonymous_users], permission: "edit" }]
end
```
This prevents anonymous users group from being able to have the Edit
permission on the target, which is practical because the anonymous
user is not logged in and generally cannot create/edit anything.
This restriction is passed to the DAccessControl component via the Site
serializer, same as mandatory_acl, and is used to prevent the user from
selecting the banned ACLs in the UI.
Then, server-side this rule is enforced in `AccessControlListManager`
service as a policy.
**Before**
<img width="971" height="287" alt="image"
src="https://github.com/user-attachments/assets/af4ace0f-7b11-4005-8344-774d554693f0"
/>
**After**
<img width="914" height="194" alt="image"
src="https://github.com/user-attachments/assets/b756b976-f6e6-4331-b27d-c9a2d13997aa"
/>
81 lines
2.1 KiB
Ruby
Vendored
81 lines
2.1 KiB
Ruby
Vendored
# frozen_string_literal: true
|
|
|
|
module AclTarget
|
|
extend ActiveSupport::Concern
|
|
|
|
def self.acl_matches?(acl_a, acl_b)
|
|
acl_a[:type].to_sym == acl_b[:type].to_sym && acl_a[:id] == acl_b[:id] &&
|
|
acl_a[:permission].to_s == acl_b[:permission].to_s
|
|
end
|
|
|
|
def self.target_classes
|
|
loaded_target_classes
|
|
end
|
|
|
|
def self.loaded_target_classes
|
|
@loaded_target_classes ||= []
|
|
end
|
|
|
|
included do
|
|
AclTarget.loaded_target_classes << self if !AclTarget.loaded_target_classes.include?(self)
|
|
|
|
has_many :access_control_lists,
|
|
as: :target,
|
|
class_name: "AccessControlList",
|
|
dependent: :destroy
|
|
|
|
scope :with_acl_permission,
|
|
->(guardian, permission) do
|
|
where(
|
|
id:
|
|
AccessControlList
|
|
.matching_user(guardian.user)
|
|
.where(target_type: polymorphic_name, permission: permission)
|
|
.select(:target_id),
|
|
)
|
|
end
|
|
|
|
scope :with_any_acl_permissions,
|
|
->(guardian, permissions) do
|
|
where(
|
|
id:
|
|
AccessControlList
|
|
.matching_user(guardian.user)
|
|
.where(target_type: polymorphic_name, permission: Array.wrap(permissions))
|
|
.select(:target_id),
|
|
)
|
|
end
|
|
|
|
def reload(options = nil)
|
|
@permission_acl = nil
|
|
super
|
|
end
|
|
end
|
|
|
|
def permission_acl
|
|
@permission_acl ||= AccessControlList.where(target: self).target_acl(self)
|
|
end
|
|
|
|
class_methods do
|
|
def acl_target_key
|
|
name
|
|
end
|
|
|
|
def has_mandatory_acl?
|
|
defined?(mandatory_acl).present? && mandatory_acl.length.positive?
|
|
end
|
|
|
|
def acl_is_mandatory?(acl)
|
|
has_mandatory_acl? &&
|
|
mandatory_acl.any? { |mandatory_acl| AclTarget.acl_matches?(acl, mandatory_acl) }
|
|
end
|
|
|
|
def has_banned_acl?
|
|
defined?(banned_acl).present? && banned_acl.length.positive?
|
|
end
|
|
|
|
def acl_is_banned?(acl)
|
|
has_banned_acl? && banned_acl.any? { |banned_acl| AclTarget.acl_matches?(acl, banned_acl) }
|
|
end
|
|
end
|
|
end
|