diff --git a/README.md b/README.md index 9c5b0b3..fe050af 100644 --- a/README.md +++ b/README.md @@ -35,13 +35,13 @@ time-logging/ - `./TimeLogsSection` — rendered via the `task.detail.section` extension point. Lets a member add/view/delete time-log entries for the current task. The "log time" form and delete button both require - `viewer.can_write_tasks` (`tasks.write`), mirroring the routes' own + `viewer.can_write_tasks` (`tasks:write`), mirroring the routes' own gate; delete is further limited by `canManageTimeLog` to the caller's - own entries, or any entry with `time_logging.manage_all`. + own entries, or any entry with `time_logging:manage_all`. - `./ProjectTimeTrackingPage` — rendered via the `project.page` extension point at its own routed page (`/projects/:projectId/plugins/com.paca.time-logging/time-tracking`), reached through a nav item this plugin registers in the project sidebar. - Gated by the project-scoped `time_logging.view_all` custom permission — + Gated by the project-scoped `time_logging:view_all` custom permission — the nav item is hidden for members without it. Lists every time-log entry across every task in the project (date, member, task, note, duration), filterable by member and date range, with inline edit/delete. @@ -50,7 +50,7 @@ time-logging/ reached through a nav item this plugin registers in the admin sidebar. Cross-project view: every time-log entry across every project in the instance, filterable by project, user, and date range. Gated by the - plugin's own `time_logging.view_all` global custom permission, and + plugin's own `time_logging:view_all` global custom permission, and deliberately read-only (see the doc comment on `AdminTimeTrackingPageInner` for why editing doesn't belong here). - Communicates with the backend through the standard plugin API path: @@ -61,7 +61,7 @@ time-logging/ ### Authorization Logging, editing, and deleting time-log entries all require the built-in -`tasks.write` permission — tracking time against a task is treated as a +`tasks:write` permission — tracking time against a task is treated as a task-editing action, same as the task itself. On top of that, a caller may only edit or delete their **own** entries by default (`PATCH`/`DELETE` on `/tasks/:taskId/time-logs/:logId` return `403` otherwise). @@ -72,15 +72,20 @@ via the shared `canManageTimeLog` helper — but the backend re-checks both on every write, so it remains the actual authorization boundary. The plugin declares four custom permissions (`customPermissions` in -`plugin.json`) — note that both `time_logging.manage_all` and -`time_logging.view_all` are declared **twice**, once per scope, since the +`plugin.json`) — note that both `time_logging:manage_all` and +`time_logging:view_all` are declared **twice**, once per scope, since the same key name reads naturally in both role editors and the two scopes never share a permission map: -- `time_logging.manage_all` (scope `project`) — a project owner can grant +> The host is IAM-native: route gates (`requireActions` in `plugin.json`) and +> in-handler checks name IAM actions — `time_logging:manage_all`, +> `time_logging:view_all`, `tasks:write` — and roles grant those actions. The +> `customPermissions` keys below are those same action names. + +- `time_logging:manage_all` (scope `project`) — a project owner can grant this to a project role to let its holders edit/delete any project member's entries within that project, in addition to their own. -- `time_logging.manage_all` (scope `global`) — grants the same +- `time_logging:manage_all` (scope `global`) — grants the same edit/delete-any-entry capability, but instance-wide across every project, not just one. It appears in the *global* role editor. Both variants are checked identically via the SDK's `ctx.Permissions().Check(...)`, backed @@ -88,20 +93,20 @@ share a permission map: global and project permissions for project-scoped checks — so a global grant satisfies the check in every project without needing a matching per-project grant. -- `time_logging.view_all` (scope `project`) — required to reach the +- `time_logging:view_all` (scope `project`) — required to reach the project's "Time Tracking" nav item/page and its `/time-logs` and `/time-logs/summary` endpoints (the project-wide, every-member view — not `/time-logs/me`, which stays open to any project member). It appears in the *project* role editor and is enforced entirely by the host's - `requirePermissions` route middleware — no additional in-handler check. + `requireActions` route middleware — no additional in-handler check. **Migration note:** before this permission existed, any project member - with baseline `projects.read` could see this view; existing project roles + with baseline `projects:read` could see this view; existing project roles will need it granted explicitly to keep that access after upgrading. -- `time_logging.view_all` (scope `global`) — required to reach the +- `time_logging:view_all` (scope `global`) — required to reach the admin-sidebar "Time Tracking" page and its `/time-logs/summary-all`, `/time-logs/all`, and `/time-logs/users-all` endpoints. It appears in the *global* role editor and is enforced entirely by the host's - `requirePermissions` route middleware (declared per-route in + `requireActions` route middleware (declared per-route in `plugin.json`) — there is no additional in-handler check for it, since those routes are otherwise fully public within the instance once granted. @@ -123,17 +128,17 @@ authenticated member of the project. | Method | Path | Description | |--------|------|-------------| -| `GET` | `/tasks/:taskId/time-logs` | List all time-log entries for a task (requires `tasks.read`) | -| `POST` | `/tasks/:taskId/time-logs` | Log time against a task (requires `tasks.write`) | -| `PATCH` | `/tasks/:taskId/time-logs/:logId` | Update a time-log entry (requires `tasks.write`, plus own entries or any entry with `time_logging.manage_all`) | +| `GET` | `/tasks/:taskId/time-logs` | List all time-log entries for a task (requires `tasks:read`) | +| `POST` | `/tasks/:taskId/time-logs` | Log time against a task (requires `tasks:write`) | +| `PATCH` | `/tasks/:taskId/time-logs/:logId` | Update a time-log entry (requires `tasks:write`, plus own entries or any entry with `time_logging:manage_all`) | | `DELETE` | `/tasks/:taskId/time-logs/:logId` | Delete a time-log entry (same rule as `PATCH`) | -| `GET` | `/time-logs` | List every time-log entry across all tasks in the project (requires `time_logging.view_all`) | -| `GET` | `/time-logs/summary` | Total minutes logged per project member (requires `time_logging.view_all`) | -| `GET` | `/time-logs/me` | The caller's `member_id` and whether they hold `time_logging.manage_all` | +| `GET` | `/time-logs` | List every time-log entry across all tasks in the project (requires `time_logging:view_all`) | +| `GET` | `/time-logs/summary` | Total minutes logged per project member (requires `time_logging:view_all`) | +| `GET` | `/time-logs/me` | The caller's `member_id` and whether they hold `time_logging:manage_all` | ### Global/admin-scoped -No `:projectId` — gated by the plugin's own `time_logging.view_all` global +No `:projectId` — gated by the plugin's own `time_logging:view_all` global custom permission. | Method | Path | Description | diff --git a/backend/plugin.go b/backend/plugin.go index 0b3ccf6..3f758c7 100644 --- a/backend/plugin.go +++ b/backend/plugin.go @@ -17,15 +17,13 @@ func nowStr() string { return time.Now().UTC().Format(time.RFC3339Nano) } -// permManageAllTimeLogs is declared twice in plugin.json — once with -// scope "project" (grantable per project role) and once with scope -// "global" (grantable instance-wide) — sharing this one key. Either grant -// lets a caller create, edit, or delete time log entries attributed to other -// project members; a global grant applies in every project because -// paca.permission_check merges the caller's global and project permissions -// for project-scoped checks. Without either, a caller may only act on their -// own entries — see (*timeLoggingPlugin).canModify. -const permManageAllTimeLogs = "time_logging.manage_all" +// permManageAllTimeLogs is the IAM action letting a caller create, edit, or +// delete time log entries attributed to other project members. It is checked +// with paca.permission_check, which the host decides on project/ for a +// project-scoped request: a role allowing it in that project, or one +// allowing it everywhere ("project/*" or "*"), passes. Without it, a caller +// may only act on their own entries — see (*timeLoggingPlugin).canModify. +const permManageAllTimeLogs = "time_logging:manage_all" // timeLoggingPlugin implements plugin.Plugin. type timeLoggingPlugin struct { diff --git a/backend/plugin_test.go b/backend/plugin_test.go index 50a294d..92ab9ee 100644 --- a/backend/plugin_test.go +++ b/backend/plugin_test.go @@ -608,7 +608,7 @@ func TestCreateTimeLog_OwnMemberID_AllowedWithoutManageAll(t *testing.T) { } } -// ── Ownership enforcement (time_logging.manage_all) ─────────────────────────── +// ── Ownership enforcement (time_logging:manage_all) ─────────────────────────── func TestUpdateTimeLog_Forbidden_NotOwner(t *testing.T) { tc := setupPlugin(t) @@ -719,7 +719,7 @@ func TestDeleteTimeLog_ManageAll_AllowsOtherMembersEntries(t *testing.T) { // updateTimeLogGlobal/deleteTimeLogGlobal perform no in-handler permission // check of their own — like the other global-scope handlers in this file // (listAllTimeLogs, timeLogsSummaryAll, timeLogsUsersAll), they rely entirely -// on the host's requirePermissions(scope=global, time_logging.manage_all) +// on the host's requireActions(scope=global, time_logging:manage_all) // route gate, which plugintest.Context.Call does not simulate. These tests // exercise the handlers' own DB logic, not that gate. @@ -916,7 +916,7 @@ func TestViewer_DefaultNoWriteTasks(t *testing.T) { func TestViewer_WriteTasksGranted(t *testing.T) { tc := setupPlugin(t) - tc.Permissions.Grant("tasks.write") + tc.Permissions.Grant("tasks:write") res := tc.Call("GET", "/time-logs/me", callerReq()) var env struct { @@ -933,7 +933,7 @@ func TestViewer_WriteTasksGranted(t *testing.T) { // ── Manifest / route-table parity ───────────────────────────────────────────── // // The host only enforces a route's declared plugin.json middlewares -// (optionalAuthn/requirePermissions/etc.) when the request path matches an +// (optionalAuthn/requireActions/etc.) when the request path matches an // entry in plugin.json's backend.routes. If Init() registers a route here // that has no matching manifest entry (or the manifest path is wrong), the // host silently falls back to its weak default policy — optionalAuthn + diff --git a/backend/timelogs.go b/backend/timelogs.go index e8166b8..ddd8360 100644 --- a/backend/timelogs.go +++ b/backend/timelogs.go @@ -82,7 +82,7 @@ type timeLogAllPage struct { // project, returned by GET /time-logs/me. The frontend uses it to decide // whether to offer the "log time" form and edit/delete controls at all // (CanWriteTasks — logging/editing time entries is gated by the same -// tasks.write permission as the routes themselves) and, separately, whether +// tasks:write permission as the routes themselves) and, separately, whether // an entry belonging to another member may be edited/deleted (CanManageAll). type viewerInfo struct { MemberID string `json:"member_id"` @@ -168,7 +168,7 @@ func (s *scanner) intVal(col string) int { func (p *timeLoggingPlugin) viewer(req *plugin.Request, res *plugin.Response) { ok(res, viewerInfo{ MemberID: req.Caller.CallerID, - CanWriteTasks: p.perm.Check("tasks.write"), + CanWriteTasks: p.perm.Check("tasks:write"), CanManageAll: p.perm.Check(permManageAllTimeLogs), }) } @@ -182,10 +182,10 @@ func (p *timeLoggingPlugin) viewerAll(req *plugin.Request, res *plugin.Response) // listTimeLogs handles GET /tasks/:taskId/time-logs // Returns all time-log entries for the task (every member's), most recent -// spent_date first. Gated by tasks.read only, not time_logging.view_all — +// spent_date first. Gated by tasks:read only, not time_logging:view_all — // intentionally: per-task time logs are part of that task's own visible // history (like comments or activity), open to anyone who can already open -// the task. time_logging.view_all instead gates project-wide rollups +// the task. time_logging:view_all instead gates project-wide rollups // (listProjectTimeLogs, timeLogsSummary) where the concern is aggregate // reporting across a whole project, not a single task the caller can already // see. @@ -209,7 +209,7 @@ func (p *timeLoggingPlugin) listTimeLogs(req *plugin.Request, res *plugin.Respon // createTimeLog handles POST /tasks/:taskId/time-logs // Body: { "spent_date": "2026-07-13", "minutes_spent": 90, "note": "..." (optional) } // member_id defaults to the caller unless explicitly provided, in which case -// the caller must hold time_logging.manage_all to attribute the entry to +// the caller must hold time_logging:manage_all to attribute the entry to // someone else — see canModify. Without it, a caller could otherwise // fabricate time entries under another member's name, which is exactly the // capability manage_all is meant to gate (update/delete already require it @@ -248,7 +248,7 @@ func (p *timeLoggingPlugin) createTimeLog(req *plugin.Request, res *plugin.Respo return } if !p.canModify(req, *b.MemberID) { - res.Error(403, "you can only log time for yourself unless you hold time_logging.manage_all") + res.Error(403, "you can only log time for yourself unless you hold time_logging:manage_all") return } memberID = *b.MemberID @@ -452,7 +452,7 @@ func (p *timeLoggingPlugin) deleteTimeLog(req *plugin.Request, res *plugin.Respo // with no :projectId/:taskId in the path — unlike updateTimeLog/deleteTimeLog, // there's no single project to scope the route to, and the caller may not // even be a member of the entry's project. The host route gate requires the -// global-scope time_logging.manage_all permission (see plugin.json), which +// global-scope time_logging:manage_all permission (see plugin.json), which // per its own declared description already grants "edit or delete any // project member's time log entries, across every project" — so unlike // canModify's self-or-manage_all check, no additional per-row ownership test @@ -1062,7 +1062,7 @@ func (p *timeLoggingPlugin) handleTaskDeleted(evt *plugin.Event) { // canModify reports whether the caller may act on a time log entry owned by // memberID: either it's their own entry, or they hold the -// time_logging.manage_all custom permission. Used both to gate edit/delete of +// time_logging:manage_all custom permission. Used both to gate edit/delete of // an existing entry and, in createTimeLog, to gate attributing a new entry to // someone other than the caller. func (p *timeLoggingPlugin) canModify(req *plugin.Request, memberID string) bool { diff --git a/frontend/src/AdminTimeTrackingPage.tsx b/frontend/src/AdminTimeTrackingPage.tsx index 47a33cb..89fb288 100644 --- a/frontend/src/AdminTimeTrackingPage.tsx +++ b/frontend/src/AdminTimeTrackingPage.tsx @@ -3,13 +3,13 @@ import TimeTrackingPage from "./TimeTrackingPage"; /** * AdminTimeTrackingPage — the `admin.page` entry component exposed by the * time-logging plugin, reached via a dedicated nav item in the admin - * sidebar; gated by the plugin's own `time_logging.view_all` global custom + * sidebar; gated by the plugin's own `time_logging:view_all` global custom * permission (declared in plugin.json), not the built-in `users.write`. * Thin wrapper: the actual list/table/filter/edit UI lives in the shared * TimeTrackingPage component, also used by ProjectTimeTrackingPage. * * Edit/delete controls appear here only for callers who additionally hold - * the global `time_logging.manage_all` permission (see useTimeLogViewerAll), + * the global `time_logging:manage_all` permission (see useTimeLogViewerAll), * backed by the dedicated global-scope PATCH/DELETE /time-logs/all/:logId * routes — a global grant applies across every project regardless of the * caller's membership in any one of them, unlike the project-scoped page's diff --git a/frontend/src/TimeLogsSection.tsx b/frontend/src/TimeLogsSection.tsx index e98a8bd..89601fd 100644 --- a/frontend/src/TimeLogsSection.tsx +++ b/frontend/src/TimeLogsSection.tsx @@ -27,10 +27,10 @@ interface TimeLogsSectionProps { * (date + duration + optional note) against the current task and shows the * running total for the task. * - * The "log time" form and delete button both require `tasks.write` + * The "log time" form and delete button both require `tasks:write` * (`viewer.can_write_tasks`), matching the routes' own gate. Delete is * further restricted to own entries, or any entry with - * `time_logging.manage_all`, via `canManageTimeLog`; the backend re-checks + * `time_logging:manage_all`, via `canManageTimeLog`; the backend re-checks * both on every write, so it's the actual authorization boundary, not this * component's rendering. */ diff --git a/frontend/src/TimeTrackingPage.tsx b/frontend/src/TimeTrackingPage.tsx index e45b0ed..9eda8b8 100644 --- a/frontend/src/TimeTrackingPage.tsx +++ b/frontend/src/TimeTrackingPage.tsx @@ -75,12 +75,12 @@ interface DisplayRow { * * Project scope: lists one project's entries (GET .../time-logs), filterable * by member + date range; edit/delete gated per-row by canManageTimeLog (own - * entry, or the project/global time_logging.manage_all permission), against + * entry, or the project/global time_logging:manage_all permission), against * the project-scoped PATCH/DELETE .../tasks/:taskId/time-logs/:logId routes. * * Admin scope: lists every project's entries (GET /time-logs/all), filterable * by project + user + date range; edit/delete gated uniformly — not per-row — - * by whether the caller holds the global time_logging.manage_all permission + * by whether the caller holds the global time_logging:manage_all permission * (useTimeLogViewerAll), against the dedicated global-scope PATCH/DELETE * /time-logs/all/:logId routes. There's no per-row ownership check at this * scope: the caller may not even be a member of a given entry's project, so diff --git a/frontend/src/shared.tsx b/frontend/src/shared.tsx index b0a0884..8029219 100644 --- a/frontend/src/shared.tsx +++ b/frontend/src/shared.tsx @@ -103,11 +103,11 @@ export const PAGE_SIZES = [10, 25, 50] as const; /** * Fetches the current caller's project_members id and permission state for - * this project (tasks.write, time_logging.manage_all). List views use this + * this project (tasks:write, time_logging:manage_all). List views use this * to decide whether to offer the "log time" form and edit/delete controls * at all (`can_write_tasks`), and whether an entry belonging to another * member may be edited/deleted (`can_manage_all`) vs. only the caller's own. - * This is a UX affordance only: the backend re-checks tasks.write and + * This is a UX affordance only: the backend re-checks tasks:write and * ownership/manage_all on every write, so it remains the actual * authorization boundary. * @@ -131,7 +131,7 @@ export function useTimeLogViewer( } /** - * Fetches the current caller's global time_logging.manage_all permission + * Fetches the current caller's global time_logging:manage_all permission * state for the admin (cross-project) time tracking page. Unlike * useTimeLogViewer there's no per-project ownership to layer on top — a * global manage_all grant applies uniformly to every row on that page. @@ -154,7 +154,7 @@ export function useTimeLogViewerAll( /** * Whether the viewer may edit/delete a time log entry owned by memberId. - * Requires `can_write_tasks` (mirroring the routes' own tasks.write gate) + * Requires `can_write_tasks` (mirroring the routes' own tasks:write gate) * plus either ownership of the entry or `can_manage_all`. */ export function canManageTimeLog( diff --git a/frontend/src/types.ts b/frontend/src/types.ts index 818b3ae..960a012 100644 --- a/frontend/src/types.ts +++ b/frontend/src/types.ts @@ -42,7 +42,7 @@ export interface TimeLogWithContext { } /** Caller identity + permission state within the current project, returned - * by GET /projects/:projectId/time-logs/me. `can_write_tasks` (tasks.write) + * by GET /projects/:projectId/time-logs/me. `can_write_tasks` (tasks:write) * gates whether to offer the "log time" form and any edit/delete controls * at all — it mirrors the same permission the log/edit/delete routes * themselves require. `can_manage_all` additionally lets an entry belonging @@ -56,7 +56,7 @@ export interface TimeLogViewer { /** Caller's global permission state for the admin (cross-project) time * tracking page, returned by GET /time-logs/viewer-all. Unlike TimeLogViewer * there's no per-project ownership concept at this scope: `can_manage_all` - * (the global time_logging.manage_all grant) is all-or-nothing across every + * (the global time_logging:manage_all grant) is all-or-nothing across every * row on the page, backed by the global-scope PATCH/DELETE * /time-logs/all/:logId routes rather than per-entry ownership. */ export interface TimeLogViewerAll { diff --git a/mcp/src/index.ts b/mcp/src/index.ts index eb3865f..ace9a7a 100644 --- a/mcp/src/index.ts +++ b/mcp/src/index.ts @@ -109,7 +109,7 @@ const tools: Tool[] = [ memberId: { type: "string", description: - "UUID of the project member the time should be attributed to (optional; defaults to the caller). Attributing time to a different member requires the caller to hold the time_logging.manage_all permission.", + "UUID of the project member the time should be attributed to (optional; defaults to the caller). Attributing time to a different member requires the caller to hold the time_logging:manage_all permission.", }, }, required: ["projectId", "taskId", "spentDate", "minutesSpent"], diff --git a/plugin.json b/plugin.json index 6602ed2..da93372 100644 --- a/plugin.json +++ b/plugin.json @@ -2,33 +2,21 @@ "id": "com.paca.time-logging", "displayName": "Time Logging", "description": "Lets project members manually log time spent on tasks (date, duration, note) and view per-member totals for a project.", - "version": "0.2.3", - "minCoreVersion": "v0.13.3", + "version": "0.2.4", + "minCoreVersion": "v0.19.0", "permissions": ["db.read", "db.write", "events.subscribe"], "customPermissions": [ { - "key": "time_logging.manage_all", + "key": "time_logging:manage_all", + "scope": "both", "label": "Manage all time logs", - "description": "Edit or delete any project member's time log entries, not just their own.", - "scope": "project" + "description": "Edit or delete any project member's time log entries. Grant on a project resource to restrict to that project, or on a global resource for cross-project access." }, { - "key": "time_logging.view_all", + "key": "time_logging:view_all", + "scope": "both", "label": "View all time logs", - "description": "View the project-wide time tracking page listing every member's logged time, not just their own. Does not affect per-task time log visibility, which any project member can already see via tasks.read.", - "scope": "project" - }, - { - "key": "time_logging.view_all", - "label": "View all time logs", - "description": "View logged time summaries and entries across every project, not just the ones they're a member of.", - "scope": "global" - }, - { - "key": "time_logging.manage_all", - "label": "Manage all time logs", - "description": "Edit or delete any project member's time log entries, across every project.", - "scope": "global" + "description": "View the time tracking page listing every member's logged time. Grant on a project resource to restrict to that project, or on a global resource for cross-project access." } ], "backend": { @@ -41,9 +29,9 @@ { "name": "optionalAuthn" }, { "name": "requireFreshPassword" }, { - "name": "requirePermissions", + "name": "requireActions", "scope": "project", - "permissions": ["tasks.read"] + "actions": ["tasks:read"] } ] }, @@ -54,9 +42,9 @@ { "name": "optionalAuthn" }, { "name": "requireFreshPassword" }, { - "name": "requirePermissions", + "name": "requireActions", "scope": "project", - "permissions": ["tasks.write"] + "actions": ["tasks:write"] } ] }, @@ -67,9 +55,9 @@ { "name": "optionalAuthn" }, { "name": "requireFreshPassword" }, { - "name": "requirePermissions", + "name": "requireActions", "scope": "project", - "permissions": ["tasks.write"] + "actions": ["tasks:write"] } ] }, @@ -80,9 +68,9 @@ { "name": "optionalAuthn" }, { "name": "requireFreshPassword" }, { - "name": "requirePermissions", + "name": "requireActions", "scope": "project", - "permissions": ["tasks.write"] + "actions": ["tasks:write"] } ] }, @@ -93,9 +81,9 @@ { "name": "optionalAuthn" }, { "name": "requireFreshPassword" }, { - "name": "requirePermissions", + "name": "requireActions", "scope": "project", - "permissions": ["projects.read"] + "actions": ["projects:read"] } ] }, @@ -106,9 +94,9 @@ { "name": "optionalAuthn" }, { "name": "requireFreshPassword" }, { - "name": "requirePermissions", + "name": "requireActions", "scope": "project", - "permissions": ["time_logging.view_all"] + "actions": ["time_logging:view_all"] } ] }, @@ -119,9 +107,9 @@ { "name": "optionalAuthn" }, { "name": "requireFreshPassword" }, { - "name": "requirePermissions", + "name": "requireActions", "scope": "project", - "permissions": ["time_logging.view_all"] + "actions": ["time_logging:view_all"] } ] }, @@ -132,9 +120,9 @@ { "name": "optionalAuthn" }, { "name": "requireFreshPassword" }, { - "name": "requirePermissions", + "name": "requireActions", "scope": "global", - "permissions": ["time_logging.view_all"] + "actions": ["time_logging:view_all"] } ] }, @@ -145,9 +133,9 @@ { "name": "optionalAuthn" }, { "name": "requireFreshPassword" }, { - "name": "requirePermissions", + "name": "requireActions", "scope": "global", - "permissions": ["time_logging.view_all"] + "actions": ["time_logging:view_all"] } ] }, @@ -158,9 +146,9 @@ { "name": "optionalAuthn" }, { "name": "requireFreshPassword" }, { - "name": "requirePermissions", + "name": "requireActions", "scope": "global", - "permissions": ["time_logging.view_all"] + "actions": ["time_logging:view_all"] } ] }, @@ -171,9 +159,9 @@ { "name": "optionalAuthn" }, { "name": "requireFreshPassword" }, { - "name": "requirePermissions", + "name": "requireActions", "scope": "global", - "permissions": ["time_logging.view_all"] + "actions": ["time_logging:view_all"] } ] }, @@ -184,9 +172,9 @@ { "name": "optionalAuthn" }, { "name": "requireFreshPassword" }, { - "name": "requirePermissions", + "name": "requireActions", "scope": "global", - "permissions": ["time_logging.manage_all"] + "actions": ["time_logging:manage_all"] } ] }, @@ -197,9 +185,9 @@ { "name": "optionalAuthn" }, { "name": "requireFreshPassword" }, { - "name": "requirePermissions", + "name": "requireActions", "scope": "global", - "permissions": ["time_logging.manage_all"] + "actions": ["time_logging:manage_all"] } ] } @@ -232,7 +220,7 @@ "icon": "Clock", "component": "ProjectTimeTrackingPage", "order": 20, - "requiredPermission": "time_logging.view_all" + "requiredPermission": "time_logging:view_all" }, { "scope": "admin", @@ -241,7 +229,7 @@ "icon": "Clock", "component": "AdminTimeTrackingPage", "order": 20, - "requiredPermission": "time_logging.view_all" + "requiredPermission": "time_logging:view_all" } ] },