[GH-ISSUE #6651] Users absent from the external IdP are reported as "active" regardless of stored is_blocked (cannot re-block, name blanked) #12226

Open
opened 2026-08-05 01:32:41 -04:00 by saavagebueno · 1 comment
Owner

Originally created by @tnucera on GitHub (Jul 2, 2026).
Original GitHub issue: https://github.com/netbirdio/netbird/issues/6651

Setup

  • Self-hosted management, external OIDC IdP (IdpManagerConfig.ManagerType set to a generic OIDC provider such as Okta).
  • Found by reading the source at v0.64.6 and confirmed still present in v0.73.2.

Symptom

A user that was blocked in NetBird (is_blocked=true) and is then deactivated / deleted / app-unassigned in the IdP starts showing in GET /api/users (and the dashboard) with:

  • status: "active" — even though the stored user still has Blocked=true;
  • an empty name (the dashboard then falls back to displaying the raw user ID).

Re-blocking the user via the API appears to no-op (the stored record is already Blocked=true, so SaveUser sees no change) while the UI keeps showing active. Net effect: an operator cannot tell from the API/UI that a deprovisioned user is actually blocked, and cannot "re-block" it.

Root cause

management/server/user.go, BuildUserInfosForAccount. For a non-embedded IdP, when the IdP returns data but this user is not found in it (findUserInIDPUserdata returns false), the UserInfo is built with hardcoded values instead of reflecting the stored user:

  • Name: "" (v0.73.2 ~line 1102)
  • Status: string(types.UserStatusActive) (v0.73.2 ~line 1105)

So the reported status ignores user.Blocked, and the name is blanked. It is refreshed by the IdP cache (no login required), which matches reports of the state changing "on its own".

Expected

For a user absent from the IdP response, the API should still reflect the stored state:

  • status derived from user.Blocked (e.g. blocked), not hardcoded active;
  • name / email not blanked (fall back to the stored values, as the len(queriedUsers)==0 branch already does via ToUserInfo(nil)).

Notes

  • The underlying access block still holds at login (the login path checks the stored IsBlocked()); this is an API/reporting correctness bug, but it is security-adjacent because it hides the true block state of deprovisioned users.
  • Code pointers (v0.73.2): management/server/user.goBuildUserInfosForAccount lines ~1088–1105.

Version

  • Reproduced against management v0.64.6; the code path is unchanged in v0.73.2.
Originally created by @tnucera on GitHub (Jul 2, 2026). Original GitHub issue: https://github.com/netbirdio/netbird/issues/6651 ### Setup - Self-hosted management, external OIDC IdP (`IdpManagerConfig.ManagerType` set to a generic OIDC provider such as Okta). - Found by reading the source at `v0.64.6` and confirmed still present in `v0.73.2`. ### Symptom A user that was blocked in NetBird (`is_blocked=true`) and is then **deactivated / deleted / app-unassigned in the IdP** starts showing in `GET /api/users` (and the dashboard) with: - `status: "active"` — even though the stored user still has `Blocked=true`; - an empty `name` (the dashboard then falls back to displaying the raw user ID). Re-blocking the user via the API appears to no-op (the stored record is already `Blocked=true`, so `SaveUser` sees no change) while the UI keeps showing `active`. Net effect: an operator cannot tell from the API/UI that a deprovisioned user is actually blocked, and cannot "re-block" it. ### Root cause `management/server/user.go`, `BuildUserInfosForAccount`. For a non-embedded IdP, when the IdP returns data but this user is **not** found in it (`findUserInIDPUserdata` returns `false`), the `UserInfo` is built with hardcoded values instead of reflecting the stored user: - `Name: ""` (v0.73.2 ~line 1102) - `Status: string(types.UserStatusActive)` (v0.73.2 ~line 1105) So the reported `status` ignores `user.Blocked`, and the name is blanked. It is refreshed by the IdP cache (no login required), which matches reports of the state changing "on its own". ### Expected For a user absent from the IdP response, the API should still reflect the **stored** state: - `status` derived from `user.Blocked` (e.g. `blocked`), not hardcoded `active`; - `name` / `email` not blanked (fall back to the stored values, as the `len(queriedUsers)==0` branch already does via `ToUserInfo(nil)`). ### Notes - The underlying access block still holds at login (the login path checks the stored `IsBlocked()`); this is an **API/reporting correctness** bug, but it is security-adjacent because it hides the true block state of deprovisioned users. - Code pointers (v0.73.2): `management/server/user.go` → `BuildUserInfosForAccount` lines ~1088–1105. ### Version - Reproduced against management `v0.64.6`; the code path is unchanged in `v0.73.2`.
Author
Owner

@linear-code[bot] commented on GitHub (Jul 2, 2026):

NET-1351

<!-- gh-comment-id:4866434153 --> @linear-code[bot] commented on GitHub (Jul 2, 2026): <!-- linear-linkback --> <p><a href="https://linear.app/netbird/issue/NET-1351">NET-1351</a></p>
Sign in to join this conversation.
No Label
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: DYNR/netbird#12226