Permissions & Security Guide
Time cockpit implements a sophisticated multi-layered security system that combines role-based access control (RBAC) with row-level security (RLS) using dynamic Named Sets and permission conditions.
Security Architecture Overview
graph TD
User[User] --> UserDetail[APP_UserDetail]
UserDetail --> Roles[APP_UserDetailRole]
Roles --> Role[APP_UserRole]
Role --> Permissions[Entity Permissions]
Permissions --> EntityPerm[Entity-Level Permissions]
Permissions --> PropertyPerm[Property-Level Permissions]
EntityPerm --> AccessTypes[Read/Insert/Update/Delete/Execute]
EntityPerm --> Conditions[Permission Conditions]
Conditions --> NamedSets[Named Sets]
Conditions --> TCQL[TCQL Expressions]
NamedSets --> DynamicData[Dynamic Data Evaluation]
TCQL --> CurrentUser[Environment.CurrentUser]
TCQL --> RowData[Current Row Data]
Permission Concepts
1. Access Types
Every permission specifies which operations it controls:
| Access Type | Value | Description |
|---|---|---|
| Read | 1 | View entity data |
| Insert | 2 | Create new records |
| Update | 4 | Modify existing records |
| Delete | 8 | Remove records |
| Execute | 16 | Run actions on entities |
2. Permission Evaluation Flow
flowchart LR
Request[User Request] --> CheckEntity{Any Entity<br/>Permission<br/>defined?}
CheckEntity -->|No Permission| Deny[Access Denied]
CheckEntity -->|Has Permissions| EvalCondition{Evaluate Each<br/>Condition<br/>incl. optional role check}
EvalCondition -->|All False| Deny
EvalCondition -->|Any True\nOR semantics| CheckRow{Row-Level<br/>Security?}
CheckRow -->|Fails| DenyRow[Row Access Denied]
CheckRow -->|Passes| Allow[Access Granted]
Multiple permissions are OR-ed: if more than one permission is defined for an entity and access type, access is granted as soon as any one condition evaluates to
True.
3. Named Sets
Named Sets are dynamic queries that evaluate at runtime to provide context-aware security:
Purpose: Pre-calculate lists of entities the current user can access Evaluation: Runs when permission is checked Caching: Results cached per request for performance
Standard Roles
Time cockpit includes predefined roles with specific capabilities:
Admin Roles
BillingAdmin
- Full access to invoicing, projects, customers
- Can create/edit/delete invoices
- Views all timesheets for billing purposes
-- Example: Invoice write permission
'BillingAdmin' In Set('CurrentUserRoles')
HumanResourcesAdmin
- Manages users, departments, working time
- Approves absences (vacation, sick leave)
- Views all employee data
-- Example: UserDetail full access
'HumanResourcesAdmin' In Set('CurrentUserRoles')
BaseDataAdmin
- Manages master data (articles, units, customers)
- Cannot access financial or employee data
- Maintains reference tables
Manager Roles
ProjectManager
- Views projects they manage
- Sees timesheets on their projects
- Cannot modify others' time entries
-- Example: Project manager sees their projects
'ProjectManager' In Set('CurrentUserRoles') And
(Current.APP_Manager1 = Environment.CurrentUser.UserDetailUuid Or
Current.APP_Manager2 = Environment.CurrentUser.UserDetailUuid)
DepartmentLead
- Manages department members
- Approves absences for their department
- Views department timesheets
-- Named Set: APP_MyDepartmentsAsLead
From DL In APP_DepartmentLead
Where DL.APP_UserDetail = Environment.CurrentUser.UserDetailUuid
Select DL.APP_Department
-- Permission condition: Department lead sees their department
Current.APP_Department In Set('APP_MyDepartmentsAsLead')
Standard User
User (base role)
- Creates own timesheets
- Views own data only
- Submits vacation requests
Common Permission Patterns
Pattern 1: Own Data + Admins
Use Case: Users see their own records, admins see all
Example: Timesheets
:Iif(
'BillingAdmin' In Set('CurrentUserRoles') Or
'HumanResourcesAdmin' In Set('CurrentUserRoles'),
True, -- Admins see everything
Current.APP_UserDetail = Environment.CurrentUser.UserDetailUuid -- Users see own
) = True
Example: Vacation requests
:Iif(
'HumanResourcesAdmin' In Set('CurrentUserRoles'),
True, -- HR sees all absences
Current.APP_UserDetail = Environment.CurrentUser.UserDetailUuid -- Users see own
) = True
Pattern 2: Hierarchical Manager Access
Use Case: Managers see their subordinates' data
Example: Department-based timesheet access
:Iif(
'HumanResourcesAdmin' In Set('CurrentUserRoles'),
True, -- HR admin sees all
:Iif(
Current.APP_UserDetail = Environment.CurrentUser.UserDetailUuid,
True, -- Users see own timesheets
:Iif(
'DepartmentLead' In Set('CurrentUserRoles') And
Current.APP_UserDetail.APP_Department In Set('APP_MyDepartmentsAsLead'),
True, -- Department leads see their department
False
)
)
) = True
Pattern 3: Project-Based Access
Use Case: Project managers access project-related data
Example: Project timesheet visibility
:Iif(
'BillingAdmin' In Set('CurrentUserRoles'),
True,
:Iif(
Current.APP_UserDetail = Environment.CurrentUser.UserDetailUuid,
True, -- Own timesheets
:Iif(
'ProjectManager' In Set('CurrentUserRoles') And
(Current.APP_Project.APP_Manager1 = Environment.CurrentUser.UserDetailUuid Or
Current.APP_Project.APP_Manager2 = Environment.CurrentUser.UserDetailUuid),
True, -- Project manager's projects
False
)
)
) = True
Pattern 4: Conditional Write Access
Use Case: Restrict modifications based on state
Example: Can't edit billed timesheets
-- Read permission: see timesheet
'BillingAdmin' In Set('CurrentUserRoles') Or
Current.APP_UserDetail = Environment.CurrentUser.UserDetailUuid
-- Write permission: can only edit if not billed
:Iif(
'BillingAdmin' In Set('CurrentUserRoles'),
True, -- Admins can edit anything
:Iif(
Current.APP_UserDetail = Environment.CurrentUser.UserDetailUuid And
Current.APP_Billed = False, -- Not yet invoiced
True,
False
)
) = True
Example: Close own timesheets only
-- APP_DeviatingBookingCompletionDate property permission
:Iif(
'HumanResourcesAdmin' In Set('CurrentUserRoles'),
True,
:Iif(
Current.APP_UserDetailUuid = Environment.CurrentUser.UserDetailUuid And
(Current.APP_AllowDeviatingBookingCompletionDateUntil = Null Or
Current.APP_AllowDeviatingBookingCompletionDateUntil >= :Today()),
True,
False
)
) = True
Pattern 5: Feature Flag Based
Use Case: Enable/disable permissions based on tenant settings
-- Disable default permissions if feature flag is off
:Iif(:IsFeatureFlagEnabled('APP_DefaultPermissions'), False, True)
Named Sets Reference
Built-in Named Sets
CurrentUserRoles
Returns the roles assigned to the current user.
-- Definition
From R In APP_UserDetailRole
Where
R.APP_UserDetail = Environment.CurrentUser.UserDetailUuid
And (R.APP_ValidFrom = Null Or R.APP_ValidFrom <= :Today())
And (R.APP_ValidTo = Null Or R.APP_ValidTo >= :Today())
Select New With { R.APP_UserRole.APP_Code }
-- Usage
'BillingAdmin' In Set('CurrentUserRoles')
APP_MyDepartmentsAsLead
Returns departments where the current user is a department lead.
-- Definition
From DL In APP_DepartmentLead
Where
DL.APP_UserDetail = Environment.CurrentUser.UserDetailUuid
Select DL.APP_Department
-- Usage
Current.APP_Department In Set('APP_MyDepartmentsAsLead')
APP_MyManagedProjects
Returns projects where the current user is Manager1 or Manager2.
-- Definition
From P In APP_Project
Where
P.APP_Manager1 = Environment.CurrentUser.UserDetailUuid Or
P.APP_Manager2 = Environment.CurrentUser.UserDetailUuid
Select P
-- Usage
Current.APP_Project In Set('APP_MyManagedProjects')
Creating Custom Named Sets
Named sets can be created programmatically to support custom security scenarios:
Example: Projects where user has booked time
-- Named Set: APP_MyProjectsWithTime
From T In APP_Timesheet
Where T.APP_UserDetail = Environment.CurrentUser.UserDetailUuid
Select Distinct T.APP_Project
Property-Level Permissions
Restrict access to specific properties (fields) within an entity.
Use Case: Hide salary information from non-HR users
-- APP_UserDetail.APP_HourlyRate property permission
:Iif(
'HumanResourcesAdmin' In Set('CurrentUserRoles') Or
'BillingAdmin' In Set('CurrentUserRoles'),
True, -- Admins can see/edit hourly rate
:Iif(
Current.APP_UserDetailUuid = Environment.CurrentUser.UserDetailUuid,
True, -- Users can see their own rate
False -- Others cannot see
)
) = True
Use Case: Lock invoice numbers after creation
-- APP_Invoice.APP_InvoiceNumber - read-only after set
:Iif(
Current.APP_InvoiceNumberIsSet = True,
True, -- Read-only if already set
False -- Editable before set
)
Action Permissions
Control who can execute specific actions (e.g., Create Invoice, Approve Vacation).
Example: Billing admin only for invoicing
-- APP_CreateInvoiceAction execute permission
:Iif('BillingAdmin' In Set('CurrentUserRoles'), True, False) = True
Example: Department leads approve absences
-- APP_ApproveAbsenceAction
-- Implemented in action code - checks department membership
'DepartmentLead' In Set('CurrentUserRoles') Or
'HumanResourcesAdmin' In Set('CurrentUserRoles')
Security Best Practices
1. Principle of Least Privilege
❌ Bad: Grant broad access
'User' In Set('CurrentUserRoles') -- Too permissive
✅ Good: Grant specific access
:Iif(
'BillingAdmin' In Set('CurrentUserRoles'),
True,
Current.APP_UserDetail = Environment.CurrentUser.UserDetailUuid
) = True
2. Use Named Sets for Complex Logic
❌ Bad: Repeat complex queries in every permission
-- Repeated in multiple permissions
(From DL In APP_DepartmentLead
Where DL.APP_UserDetail = Environment.CurrentUser.UserDetailUuid
Select DL.APP_Department) Contains Current.APP_Department
✅ Good: Define once as named set
Current.APP_Department In Set('APP_MyDepartmentsAsLead')
3. Test Permission Boundaries
Always test:
- ✅ Users can access their own data
- ✅ Users CANNOT access others' data
- ✅ Managers can access subordinate data
- ✅ Managers CANNOT access peer data
- ✅ Admins can access everything
4. Document Custom Permissions
/* Permission: ProjectManagerReadAccess
Purpose: Project managers see timesheets on projects they manage
Roles: ProjectManager, BillingAdmin
Condition: User is Manager1 or Manager2 of the project */
:Iif(
'BillingAdmin' In Set('CurrentUserRoles'),
True,
:Iif(
'ProjectManager' In Set('CurrentUserRoles') And
(Current.APP_Project.APP_Manager1 = Environment.CurrentUser.UserDetailUuid Or
Current.APP_Project.APP_Manager2 = Environment.CurrentUser.UserDetailUuid),
True,
False
)
) = True
5. Audit Permission Changes
When modifying permissions:
- Document the change and reason
- Test with affected user roles
- Review with security/compliance team
- Monitor for unintended access patterns
Troubleshooting Permissions
"Access Denied" Errors
Step 1: Identify which permission failed
- Entity-level? (Can't read/write entity at all)
- Row-level? (Can see some records but not this one)
- Property-level? (Can't see/edit specific field)
- Action permission? (Can't execute action)
Step 2: Check user's roles
-- Query current user's roles
From R In APP_UserDetailRole
Where R.APP_UserDetail = Environment.CurrentUser.UserDetailUuid
And (R.APP_ValidFrom = Null Or R.APP_ValidFrom <= :Today())
And (R.APP_ValidTo = Null Or R.APP_ValidTo >= :Today())
Select R.APP_UserRole.APP_Code
Step 3: Evaluate permission condition manually
Test the condition with known values:
- Replace
Environment.CurrentUser.UserDetailUuidwith actual UUID - Replace
Current.XXXwith actual record values - Check Named Set results
Step 4: Check feature flags
-- Is default permissions enabled?
:IsFeatureFlagEnabled('APP_DefaultPermissions')
Permission Not Working As Expected
Common Issues:
Role not assigned: User doesn't have the required role
- Solution: Assign role via
APP_UserDetailRole
- Solution: Assign role via
Role expired:
ValidFrom/ValidTodates exclude today- Solution: Update date ranges
Named set returns empty: Dynamic query has no results
- Solution: Debug the named set query independently
Wrong department: User is in different department
- Solution: Update
APP_UserDetail.APP_Department
- Solution: Update
Permission disabled:
IsDisabledExpressionevaluates toTrue- Solution: Check feature flags or conditions
Advanced: Custom Security Scenarios
Scenario 1: Time-Based Access
Restrict editing to within booking completion period:
-- Can only edit timesheets before booking completion date
:Iif(
'BillingAdmin' In Set('CurrentUserRoles'),
True,
:Iif(
Current.APP_UserDetail = Environment.CurrentUser.UserDetailUuid And
Current.APP_DateActual > :BookingCompletionDateOfUser(Environment.CurrentUser.UserDetailUuid),
True, -- Can edit recent timesheets
False -- Cannot edit old/closed timesheets
)
) = True
Scenario 2: Customer-Specific Visibility
Let account managers see only their assigned customers:
-- Custom Named Set: APP_MyManagedCustomers
From C In APP_Customer
Where C.APP_AccountManager = Environment.CurrentUser.UserDetailUuid
Select C
-- Permission: Account manager sees their customers
:Iif(
'BillingAdmin' In Set('CurrentUserRoles'),
True,
Current In Set('APP_MyManagedCustomers')
) = True
Scenario 3: Multi-Tenant Isolation
Ensure users only see data from their company:
-- All entities: Tenant isolation
:Iif(
'SystemAdmin' In Set('CurrentUserRoles'), -- System admin sees all tenants
True,
Current.APP_Company = Environment.CurrentUser.UserDetail.APP_Company -- Same company only
) = True