Files
TimeTracker/docs/SUBCONTRACTOR_ROLE.md
T
Dries Peeters de2a7db026 fix: restrict subcontractors to assigned projects/clients when starting timers (fixes #558)
- Enforce scope in timer routes: start_timer (POST), start_timer_for_project (GET),
  and start_timer_from_template; deny with flash+redirect when project/client not allowed
- Add user_can_access_project check in api_start_timer (legacy API), API v1 timer/start,
  and kiosk start-timer; return 403 with clear error message
- Scope dashboard Start Timer modal: load active_projects and active_clients via
  apply_project_scope_to_model/apply_client_scope_to_model so subcontractors only see
  assigned options
- Document timer start scope in SUBCONTRACTOR_ROLE.md (web, API, kiosk, 403/redirect)
2026-03-11 16:49:26 +01:00

4.1 KiB
Raw Blame History

Subcontractor Role and Assigned Clients

Overview

The Subcontractor role lets you restrict specific users to only see and work with assigned clients and their projects. This is useful for:

  • External or part-time staff who should not see other clients
  • Confidentiality: limit visibility to only the clients a user works with
  • Multi-tenant-style usage where each user has a clear subset of clients

Subcontractors use the main application (same UI as regular users). This is different from the Client Portal, which is a separate read-only portal for a single client (see CLIENT_PORTAL.md).

How It Works

  1. Role: Assign the user the Subcontractor role (Admin → Users → Edit user → Role: Subcontractor).
  2. Assigned clients: In the same form, when the role is Subcontractor, the section Assigned Clients (Subcontractor) appears. Select one or more clients. Save.
  3. Scope: That user then sees only those clients, their projects, and related data everywhere in the app (clients list, projects list, time entries, reports, invoices, timer, API). Access to any other client or project returns 403 Forbidden.

Enabling Subcontractor Access

For Administrators

  1. Go to AdminUsers.
  2. Click Edit on the user.
  3. Set Role to Subcontractor.
  4. The section Assigned Clients (Subcontractor) appears. Select all clients this user should have access to (multi-select; hold Ctrl/Cmd to select multiple).
  5. Click Save.

If you change the role away from Subcontractor, assigned clients are cleared. If you set the role back to Subcontractor, you can assign clients again.

Requirements

  • The user must have the Subcontractor role (system role, created by flask seed_permissions_cmd if missing).
  • At least one client should be assigned. If none are assigned, the user will see no clients or projects.

Where Scope Is Applied

  • Clients: List and detail views; edit client. Other clients are hidden and direct URLs return 403.
  • Projects: List, export, view, edit. Only projects belonging to assigned clients are shown; others 403.
  • Time entries: Timer start (web POST/GET, from template, legacy API, API v1, kiosk), manual entry, and edit. Starting a timer on a project or client the user is not assigned to returns 403 or a redirect with an error. Time entries report and exports only include allowed projects.
  • Invoices: Create invoice (project dropdown), and invoice data for reports.
  • Reports: All report screens and export form use scoped clients and projects; time entries report only includes allowed projects.
  • API v1: List/get clients and projects, global search, and client contacts are scoped; direct access to other resources returns 403.

Admins and users with other roles are not restricted; only users with the Subcontractor role are scoped to their assigned clients.

Technical Details

Data Model

  • Table: user_clients (association: user_id, client_id). Migration: 127_add_user_clients_table.
  • User model: User.assigned_clients (many-to-many with Client). Helpers: get_allowed_client_ids(), get_allowed_project_ids(), is_scope_restricted.

Scope Helpers

The module app.utils.scope_filter provides:

  • apply_client_scope_to_model(Client, user) filter expression for client queries (or None for full access).
  • apply_project_scope_to_model(Project, user) filter expression for project queries.
  • user_can_access_client(user, client_id), user_can_access_project(user, project_id) for 403 checks on direct access.

Seeding the Subcontractor Role

If the role does not exist, run:

flask seed_permissions_cmd

This creates the default roles, including Subcontractor, and syncs permissions.