0
0
Fork 0
mirror of https://github.com/discourse/discourse.git synced 2026-08-05 18:46:18 +08:00
discourse/db/migrate/20260610064425_introduce_permission_acl_tables.rb
Martin Brennan 5823e4e3b2
FEATURE: Access control list UI and backend (#41009)
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.
2026-06-29 09:44:58 +10:00

35 lines
1.4 KiB
Ruby
Vendored

# frozen_string_literal: true
class IntroducePermissionAclTables < ActiveRecord::Migration[8.0]
def up
create_table :access_control_lists do |t|
t.string :target_type, null: false, limit: 255
t.bigint :target_id, null: false
t.string :owner, null: false, limit: 100
t.string :permission, null: false, limit: 100
t.bigint :allowed_user_ids, array: true, null: false, default: []
t.bigint :allowed_group_ids, array: true, null: false, default: []
t.timestamps
end
add_index :access_control_lists, %i[target_type target_id permission], unique: true
execute "CREATE INDEX idx_access_control_lists_allowed_user_ids ON access_control_lists USING GIN(allowed_user_ids)"
execute "CREATE INDEX idx_access_control_lists_allowed_group_ids ON access_control_lists USING GIN(allowed_group_ids)"
end
def down
drop_table :access_control_lists
if index_exists?(:access_control_lists, :allowed_user_ids, using: :gin)
execute "DROP INDEX idx_access_control_lists_allowed_user_ids"
end
if index_exists?(:access_control_lists, :allowed_group_ids, using: :gin)
execute "DROP INDEX idx_access_control_lists_allowed_group_ids"
end
if index_exists?(:access_control_lists, %i[target_type target_id permission], unique: true)
remove_index :access_control_lists, column: %i[target_type target_id permission]
end
end
end