Files
TimeTracker/docs/SUBCONTRACTOR_ROLE.md
T
Dries Peeters ae9ee9dec1 feat: add subcontractor role with assigned clients (scope-restricted access)
- Add user_clients table and UserClient model for many-to-many user-client assignment
- Add 'subcontractor' system role; users with this role see only assigned clients and their projects
- User helpers: is_scope_restricted, get_allowed_client_ids(), get_allowed_project_ids()
- Admin user form: assign clients when role is Subcontractor (multi-select, JS toggle)
- Scope filtering: clients, projects, time entries, reports, invoices, timer, API v1
- Direct access to out-of-scope client/project returns 403 (web and API)
- Migration 127_add_user_clients_table; scope_filter utility and ProjectService scope_client_ids
- Docs: SUBCONTRACTOR_ROLE.md, ADVANCED_PERMISSIONS.md, RBAC, CLIENT_PORTAL, README, CHANGELOG

Addresses GitHub Discussion #476 (user with limited clients/projects).
2026-02-16 07:12:57 +01:00

3.9 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 (manual entry, edit), time entries report and exports. Only entries for allowed projects are included.
  • 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.