feat(roles): prepare migration ReBAC to roles
prepare migration of permission to roles
this simplify drastically database (permission are now simply defined in code)
and will permit reuse of the same ReBAC engine to define owners of drives
mapping:
```
Role::Viewer => &[Permission::Read],
Role::Commenter => &[Permission::Read, Permission::Comment],
Role::Contributor => &[Permission::Read, Permission::Create],
Role::Editor => &[
Permission::Read,
Permission::Comment,
Permission::Create,
Permission::Update,
],
Role::Owner => &[
Permission::Read,
Permission::Comment,
Permission::Create,
Permission::Update,
Permission::Share,
Permission::Delete,
Permission::Manage,
],
```
This commit is contained in:
@@ -454,7 +454,7 @@ class MySharesList {
|
||||
);
|
||||
menu.appendChild(this._menuSeparator());
|
||||
|
||||
for (const role of /** @type {('admin'|'editor'|'viewer')[]} */ (['admin', 'editor', 'viewer'])) {
|
||||
for (const role of /** @type {('owner'|'editor'|'viewer')[]} */ (['owner', 'editor', 'viewer'])) {
|
||||
const isCurrent = grant.role === role;
|
||||
const mi = this._menuItem(isCurrent ? 'fas fa-check' : '', roleLabel(role), false, async () => {
|
||||
menu.remove();
|
||||
|
||||
@@ -23,7 +23,7 @@ import { i18n } from '../core/i18n.js';
|
||||
* @returns {'manage'|'edit'|'view'}
|
||||
*/
|
||||
function roleMod(role) {
|
||||
if (role === 'admin') return 'manage';
|
||||
if (role === 'owner') return 'manage';
|
||||
if (role === 'editor') return 'edit';
|
||||
return 'view';
|
||||
}
|
||||
@@ -31,14 +31,17 @@ function roleMod(role) {
|
||||
/**
|
||||
* Translate a role identifier into a localized human-readable label.
|
||||
* Exported so callers that just want the label (e.g. context-menu rows)
|
||||
* can reuse the same wording the chip uses.
|
||||
* can reuse the same wording the chip uses. Unknown roles fall back to
|
||||
* the raw role string — `commenter` and `contributor` exist server-side
|
||||
* but aren't surfaced in the UI today, so they'll display as-is until a
|
||||
* future UI exposure adds proper labels.
|
||||
* @param {string} role
|
||||
* @returns {string}
|
||||
*/
|
||||
export function roleLabel(role) {
|
||||
/** @type {Record<string,string>} */
|
||||
const m = {
|
||||
admin: i18n.t('share.role.canManage', 'Can manage'),
|
||||
owner: i18n.t('share.role.canManage', 'Can manage'),
|
||||
editor: i18n.t('share.role.canEdit', 'Can edit'),
|
||||
viewer: i18n.t('share.role.canView', 'Can view')
|
||||
};
|
||||
@@ -51,7 +54,7 @@ export function roleLabel(role) {
|
||||
* @returns {string}
|
||||
*/
|
||||
function roleIcon(role) {
|
||||
if (role === 'admin') return 'fa-crown';
|
||||
if (role === 'owner') return 'fa-crown';
|
||||
if (role === 'editor') return 'fa-pencil-alt';
|
||||
return 'fa-eye';
|
||||
}
|
||||
|
||||
@@ -73,11 +73,21 @@ function _looksLikeEmail(q) {
|
||||
return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(q);
|
||||
}
|
||||
|
||||
/** Permissions that belong to each role (must mirror the Rust DTO). */
|
||||
/**
|
||||
* Permissions that belong to each role (mirrors `Role::expand()` in
|
||||
* `src/application/dtos/grant_dto.rs`). The share modal only renders
|
||||
* Viewer/Editor/Owner as picker buttons today; `commenter` and
|
||||
* `contributor` are kept here for fidelity with the server-side enum so
|
||||
* a future UI exposure doesn't need a mirror-table update. The `manage`
|
||||
* permission in the owner bundle is reserved for Drive- and Group-level
|
||||
* admin actions (no-op on file/folder resources).
|
||||
*/
|
||||
const ROLE_PERMISSIONS = {
|
||||
viewer: ['read'],
|
||||
commenter: ['read', 'comment'],
|
||||
contributor: ['read', 'create'],
|
||||
editor: ['read', 'comment', 'create', 'update'],
|
||||
admin: ['read', 'comment', 'create', 'update', 'share', 'delete']
|
||||
owner: ['read', 'comment', 'create', 'update', 'share', 'delete', 'manage']
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -113,7 +123,7 @@ async function _searchGroups(q) {
|
||||
*/
|
||||
function _roleFromGrants(subjectGrants) {
|
||||
const perms = new Set(subjectGrants.map((g) => g.permission));
|
||||
if (perms.has('delete') || perms.has('share')) return 'admin';
|
||||
if (perms.has('delete') || perms.has('share')) return 'owner';
|
||||
if (perms.has('create') || perms.has('update')) return 'editor';
|
||||
return 'viewer';
|
||||
}
|
||||
@@ -363,7 +373,7 @@ const shareModal = {
|
||||
for (const [val, label] of [
|
||||
['viewer', i18n.t('share.role.canView', 'Can view')],
|
||||
['editor', i18n.t('share.role.canEdit', 'Can edit')],
|
||||
['admin', i18n.t('share.role.canManage', 'Can manage')]
|
||||
['owner', i18n.t('share.role.canManage', 'Can manage')]
|
||||
]) {
|
||||
const opt = document.createElement('option');
|
||||
opt.value = val;
|
||||
@@ -649,7 +659,7 @@ const shareModal = {
|
||||
// matching the UX contract and the kebab-menu / role-select dropdown
|
||||
// order. Renaming the labels from "Manager"/"Editor"/"Viewer" to
|
||||
// "Can manage"/"Can edit"/"Can view" left this iteration order stale.
|
||||
const groups = /** @type {ShareRoleEnum[]} */ (['admin', 'editor', 'viewer']);
|
||||
const groups = /** @type {ShareRoleEnum[]} */ (['owner', 'editor', 'viewer']);
|
||||
let memberIndex = 0;
|
||||
|
||||
for (const role of groups) {
|
||||
@@ -663,7 +673,7 @@ const shareModal = {
|
||||
header.className = 'smd-group-header';
|
||||
|
||||
const labelMap = {
|
||||
admin: i18n.t('share.role.canManage', 'Can manage'),
|
||||
owner: i18n.t('share.role.canManage', 'Can manage'),
|
||||
editor: i18n.t('share.role.canEdit', 'Can edit'),
|
||||
viewer: i18n.t('share.role.canView', 'Can view')
|
||||
};
|
||||
@@ -711,7 +721,7 @@ const shareModal = {
|
||||
for (const [val, label] of [
|
||||
['viewer', i18n.t('share.role.canView', 'Can view')],
|
||||
['editor', i18n.t('share.role.canEdit', 'Can edit')],
|
||||
['admin', i18n.t('share.role.canManage', 'Can manage')]
|
||||
['owner', i18n.t('share.role.canManage', 'Can manage')]
|
||||
]) {
|
||||
const opt = document.createElement('option');
|
||||
opt.value = val;
|
||||
|
||||
@@ -367,7 +367,7 @@
|
||||
* @property {'user'|'group'|'token'|'external'} subject_type
|
||||
* @property {string} subject_id
|
||||
* @property {string} subject_display - Username (users) or share name (tokens).
|
||||
* @property {'viewer'|'editor'|'admin'} role
|
||||
* @property {'viewer'|'commenter'|'contributor'|'editor'|'owner'} role - Server-emitted role string. `commenter` and `contributor` are reserved for future UI exposure; today the share modal only renders `viewer`/`editor`/`owner` (see `ShareRoleEnum`).
|
||||
* @property {string} granted_at - ISO-8601
|
||||
* @property {string|null} [expires_at] - ISO-8601 or absent.
|
||||
* @property {boolean} has_password - True when a token subject has a password set.
|
||||
@@ -453,8 +453,11 @@
|
||||
// ------------------- share modal
|
||||
|
||||
/**
|
||||
* Share roles (DTO-layer sugar for the ReBAC permission sets).
|
||||
* @typedef {'viewer'|'editor'|'admin'} ShareRoleEnum
|
||||
* Share-modal-exposed roles. The server's `Role` enum also includes
|
||||
* `commenter` and `contributor` (see `OutgoingResourceGrant.role`); those
|
||||
* are reserved for future UI exposure and are not offered as picker options
|
||||
* today. The "Can manage" UI label maps to `owner`.
|
||||
* @typedef {'viewer'|'editor'|'owner'} ShareRoleEnum
|
||||
*/
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user