Skip to content

Commit dfffcca

Browse files
Frank Karlitschekclaude
andcommitted
feat: exclude guest accounts from the employee directory
Guest accounts — users created by the Guests app — are external people invited to collaborate on files. They have no entitlement and take no leave, but the app treated every account as staff, so each guest sat in the balances report and the who's-off calendar forever with an empty allowance and nothing to show. Four services each walked IUserManager with their own copy of that loop, so a rule about who is *not* an employee had to be repeated four times to hold — and would silently not hold wherever it was forgotten. EmployeeDirectory is now the one place that answers "who are the employees", and ReportService, EntitlementService, CoverageService and ManagerResolver all ask it. Detection reads the user backend: a guest lives in the Guests app's own backend, which is what OCA\Guests\GuestManager::isGuest() checks too. Read that way the app needs no dependency on the Guests app — where it is absent or disabled no account has that backend and the rule is simply never true, so instances without guests are unaffected. Enforced, not merely hidden. Filtering only the lists would leave the rule one crafted request away from being bypassed, so the API rejects recording leave for a guest (including by HR, who may otherwise record for anyone), nominating one as a replacement, and setting one's entitlement. Guests also cannot be resolved as a line manager: that is more than tidiness, because a request routed to a guest approver could never be approved. The people pickers move off core's autocomplete to a new /api/employees/search, because only the server can tell a guest from a colleague — a client-side filter would be no filter at all. It wraps the same collaborator search core uses, so the admin's user-enumeration settings keep applying exactly as before; guests are removed from whatever that search returns. Existing records for someone who later becomes a guest are left untouched in the database; they simply stop being listed. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
1 parent 58596a3 commit dfffcca

24 files changed

Lines changed: 501 additions & 68 deletions

SPECIFICATION.md

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ There are four effective roles. A single user may hold several simultaneously
5151

5252
| Role | How assigned | Capabilities |
5353
|------|-------------|--------------|
54-
| **Employee** | Every logged-in user | Create/edit/cancel own requests, view own balance, view own history, see team who's-off calendar. |
54+
| **Employee** | Every logged-in user **except guest accounts** (§2.2) | Create/edit/cancel own requests, view own balance, view own history, see team who's-off calendar. |
5555
| **Line manager** | Derived from the LDAP `manager` attribute (see §2.1) — a user is a manager of everyone whose `manager` attribute points to them | Approve/reject/comment on direct reports' requests, view direct reports' calendars and balances, receive coverage-conflict warnings. |
5656
| **HR** | Membership of a configurable Nextcloud group (default group id `hr`, set in admin settings) | Company-wide overview, statistics, exports, manage entitlements, manage public-holiday calendar, override any decision, act on escalated requests, edit/adjust any request and balance. |
5757
| **App admin** | Nextcloud server admins | Configure app settings (§11): HR group, leave types, escalation window, default entitlements, CalDAV target. |
@@ -72,6 +72,41 @@ There are four effective roles. A single user may hold several simultaneously
7272
- **No manager found:** the request is created with `manager_uid = NULL` and is
7373
routed directly to HR (treated as immediately escalated — see §5.4).
7474

75+
### 2.2 Who counts as an employee (guest accounts)
76+
77+
Not every account on an instance is a member of staff. **Guest accounts — users
78+
created by the [Guests app](https://github.com/nextcloud/guests) — are external
79+
people invited to collaborate on files. They have no entitlement and take no
80+
leave, so the app does not treat them as employees.**
81+
82+
Without this rule every guest would sit in the balances report and the who's-off
83+
calendar forever, with an empty allowance and nothing to show.
84+
85+
- **One definition, one place.** `EmployeeDirectory` is the only component that
86+
enumerates users; `ReportService`, `EntitlementService`, `CoverageService` and
87+
`ManagerResolver` all ask it rather than walking `IUserManager` themselves. A
88+
rule stated in four copies is a rule that holds in three.
89+
- **Detection.** A guest is a user in the Guests app's own user backend, i.e.
90+
`IUser::getBackendClassName() === 'Guests'` — the same thing
91+
`OCA\Guests\GuestManager::isGuest()` checks. Read this way the app needs **no
92+
dependency on the Guests app**: where it is absent or disabled, no account has
93+
that backend and the rule is simply never true.
94+
- **Consequences.** Guests do not appear in balances, statistics, the sick-leave
95+
overview, exports, the who's-off calendar, the HR absence list or any people
96+
picker; they are nobody's direct report or peer, and cannot be resolved as a
97+
line manager (a request routed to one could never be approved).
98+
- **Enforced, not just hidden.** The API rejects creating leave for a guest —
99+
including by HR, who may otherwise record for anyone — nominating a guest as a
100+
replacement, and setting a guest's entitlement. Filtering only the UI would
101+
leave the rule one crafted request away from being bypassed.
102+
- **Pickers.** The people pickers call the app's own
103+
`GET /api/employees/search` rather than core's autocomplete, because only the
104+
server can tell a guest from a colleague. That endpoint wraps the same
105+
collaborator search, so the admin's user-enumeration settings still apply
106+
exactly as elsewhere; guests are removed from what it returns.
107+
- Existing records for someone who later becomes a guest are left untouched in
108+
the database — they simply stop being listed.
109+
75110
---
76111

77112
## 3. Core Concepts & Data Model

appinfo/routes.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,9 @@
2121
['name' => 'request#reject', 'url' => '/api/requests/{id}/reject', 'verb' => 'POST'],
2222
['name' => 'request#addComment', 'url' => '/api/requests/{id}/comments', 'verb' => 'POST'],
2323

24+
// People (autocomplete for the employee / replacement pickers)
25+
['name' => 'employee#search', 'url' => '/api/employees/search', 'verb' => 'GET'],
26+
2427
// Balances & entitlements
2528
['name' => 'balance#mine', 'url' => '/api/balance', 'verb' => 'GET'],
2629
['name' => 'balance#forEmployee', 'url' => '/api/employees/{uid}/balance', 'verb' => 'GET'],

js/NcCheckboxRadioSwitch-DVdt5Hkq-Dfqo0Dmj.chunk.mjs renamed to js/NcCheckboxRadioSwitch-DVdt5Hkq-anWxu1ok.chunk.mjs

Lines changed: 14 additions & 14 deletions
Large diffs are not rendered by default.

js/NcCheckboxRadioSwitch-DVdt5Hkq-Dfqo0Dmj.chunk.mjs.license renamed to js/NcCheckboxRadioSwitch-DVdt5Hkq-anWxu1ok.chunk.mjs.license

File renamed without changes.

js/NcCheckboxRadioSwitch-DVdt5Hkq-Dfqo0Dmj.chunk.mjs.map renamed to js/NcCheckboxRadioSwitch-DVdt5Hkq-anWxu1ok.chunk.mjs.map

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

js/absence-main.mjs

Lines changed: 1 addition & 1 deletion
Large diffs are not rendered by default.

js/absence-personal-settings.mjs

Lines changed: 1 addition & 1 deletion
Large diffs are not rendered by default.
Lines changed: 2 additions & 2 deletions
Large diffs are not rendered by default.
Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)