mirror of
https://github.com/discourse/discourse.git
synced 2026-08-06 13:08:40 +08:00
Introduces a new way of handling permissions for a target entity within Discourse, including integration with `Guardian` and `User` models. For this initial PR, only group-based permissions will work with this new model, a followup PR will introduce user-based permissions. The initial use case for this is securing Kanban Boards in Discourse Kanban, see https://github.com/discourse/discourse-kanban/pull/58 for the sister PR. In future, we will use this in more places in core, like Chat, Category, etc. The new model is called `AccessControlList`, here is the schema: * `target_id`/`target_type` - Polymorphic columns, can point to any other model * `owner` - A string indicating whether the permission is owned by `core` or a plugin e.g. `discourse-kanban` * `permission` - A free text field, which can be whatever the target requires, but defaults are `edit`, `view`, `manage`, and `own`. `own` at this time is a special permission that should be added to whatever user creates the ACL at first, but should not be shown in any UI * `allowed_group_ids` - An array of group IDs which have this permission * `allowed_user_ids` - An array of group IDs which have this permission A component, called `DAccessControl`, is also introduced to display these permissions and the groups (and soon users) who have them. The component allows a custom description label, and the list of permissions can have their text and description modified, and can also have permissions removed or added as needed for the target: <img width="612" height="222" alt="image" src="https://github.com/user-attachments/assets/88f8d70a-f965-4ef2-9e64-5f8b2bf3fa12" /> <img width="598" height="380" alt="image" src="https://github.com/user-attachments/assets/db90b956-86c2-4ca7-9b63-4c3eb0bad5ef" /> Each target entity can define their own `mandatory_acl` array which is similar to how `mandatory_values` for site settings. For example, Kanban Boards have a mandatory ACL of the admins group ID having the `manage` permission for the board, since admins always need to be able to see + manage boards. This allows us to avoid hardcoding admin/staff escape hatches in guardian/permissions code. These will be shown as disabled rows in the `DAccessControl` component: <img width="577" height="248" alt="image" src="https://github.com/user-attachments/assets/d9bf1754-0530-4edb-b89f-28887cd613d6" /> This commit also introduces a `full_name` calculation for automatic groups, so we can display a nicer version of the name for things like `admins`, `staff`, `trust_level_0` in the UI.
67 lines
1.7 KiB
Ruby
Vendored
67 lines
1.7 KiB
Ruby
Vendored
# frozen_string_literal: true
|
|
|
|
RSpec.describe AclTarget do
|
|
let(:target_class) do
|
|
Class.new(ActiveRecord::Base) do
|
|
include AclTarget
|
|
|
|
self.table_name = "posts"
|
|
|
|
def self.name
|
|
"AclTargetSpecTarget"
|
|
end
|
|
end
|
|
end
|
|
|
|
it "adds mandatory acl class methods" do
|
|
expect(target_class).to respond_to(:has_mandatory_acl?, :acl_is_mandatory?)
|
|
end
|
|
|
|
it "registers loaded target classes" do
|
|
expect(described_class.target_classes).to include(target_class)
|
|
end
|
|
|
|
describe ".acl_target_key" do
|
|
it "returns the class name" do
|
|
expect(target_class.acl_target_key).to eq("AclTargetSpecTarget")
|
|
end
|
|
end
|
|
|
|
describe ".has_mandatory_acl?" do
|
|
it "returns false without mandatory acl entries" do
|
|
expect(target_class).not_to have_mandatory_acl
|
|
|
|
target_class.define_singleton_method(:mandatory_acl) { [] }
|
|
|
|
expect(target_class).not_to have_mandatory_acl
|
|
end
|
|
|
|
it "returns true with mandatory acl entries" do
|
|
target_class.define_singleton_method(:mandatory_acl) do
|
|
[{ type: :group, id: 1, permission: "view" }]
|
|
end
|
|
|
|
expect(target_class).to have_mandatory_acl
|
|
end
|
|
end
|
|
|
|
describe ".acl_is_mandatory?" do
|
|
before do
|
|
target_class.define_singleton_method(:mandatory_acl) do
|
|
[{ type: :group, id: 1, permission: "view" }, { type: :user, id: 2, permission: "edit" }]
|
|
end
|
|
end
|
|
|
|
it "returns true for matching mandatory acl entries" do
|
|
expect(target_class.acl_is_mandatory?({ type: :group, id: 1, permission: "view" })).to eq(
|
|
true,
|
|
)
|
|
end
|
|
|
|
it "returns false for different acl entries" do
|
|
expect(target_class.acl_is_mandatory?({ type: :group, id: 1, permission: "edit" })).to eq(
|
|
false,
|
|
)
|
|
end
|
|
end
|
|
end
|