Microsoft Entra ID
Microsoft Entra ID (Azure AD) is the identity / directory source. The integration pulls users, groups and group memberships from the Microsoft Graph API and powers the Users surface, which links each person to the assets they own and to any badge / HR enrichment data. It follows the shared integration lifecycle.
- Router:
app/api/v1/endpoints/entra.py, mounted at/api/v1/entra - Client:
app/services/entra_client.py(EntraClient) - Config collection:
entra_config(keyed bytenant_id)
Authentication
Entra reuses the Intune app registration — the
same Azure app, same MSAL client-credentials flow (certificate or secret), same
https://graph.microsoft.com/.default scope — it just targets the directory
endpoints and needs different permissions consented.
Required Graph application permissions (admin consent on the app registration):
User.Read.AllGroup.Read.All(orGroupMember.Read.All/Directory.Read.All)
If consent is missing, Graph returns 403 and the client raises a clear
EntraAuthError naming the permission to grant. Env seeding
(_seed_entra_config) reuses the Intune env vars (INTUNE_TENANT_ID,
INTUNE_CLIENT_ID, INTUNE_CERT_THUMBPRINT + PEM) and seeds the default
tenant with certificate auth. test-connection fetches one user to validate
credentials and consent.
What the sync pulls
The background _run_sync pulls three things:
| Data | Source call | Written to |
|---|---|---|
| Users | get_users (/users with $select + paging) | entra_users |
| Groups | get_groups (/groups) | entra_groups |
| Group memberships | get_group_members per group | resolved into each user’s groups list |
- Users capture UPN, display name, mail, job title, department, office,
mobile,
onPremisesSamAccountName, and enabled state. Crucially, the sync parsesproxyAddressesinto analiaseslist (thesmtp:-prefixed SMTP addresses, lowercased) — these let badge/HR rows whose email is an alias rather than the UPN still link to the right person. - Groups capture display name, description, mail, and derived flags
(
is_m365fromgroupTypescontainingUnified,is_securityfromsecurityEnabled), plus visibility. - Memberships are resolved best-effort per group into a map of
upn → [group displayName], stored on each user asgroups. A failure on any single group is non-fatal.
Stale users and groups (whose ids are no longer live) are deleted.
The Users surface
Beyond the standard integration routes, this router exposes a Users surface that reconciles a person across the whole system:
GET /api/v1/entra/users— searchable, paginated user list.GET /api/v1/entra/user/{upn}— one user, enriched with:- Linked assets (
_linked_devices) — devices this person owns, matched by UPN againstintune_devices.user_upnand by UPN / display name againstinventory_items(user_email/user_name). Each device resolves to the id the asset-detail page accepts (preferring the Intune UUID). - CSV enrichment — badge / HR rows from
csv_users, matched by email against the user’s UPN, primary mail and any SMTP alias, so alias-based records still attach. Convenience flags (e.g.badge_admin) are surfaced at the top level.
- Linked assets (
This is how a machine, a directory identity, and an HR/badge record for the same human collapse into a single Users view.
Routes
| Method | Path | Purpose |
|---|---|---|
POST / GET / DELETE | /api/v1/entra/config | Save / get (redacted) / remove config + data |
POST | /api/v1/entra/test-connection | Live Graph credential + consent test |
POST | /api/v1/entra/sync | Start a background sync |
GET | /api/v1/entra/sync-status | Poll sync status |
GET | /api/v1/entra/stats | User / group / enabled-user counts |
GET | /api/v1/entra/users | List users (search, enabled-only) |
GET | /api/v1/entra/user/{upn} | One user + linked devices + CSV enrichment |
See the overview for the lifecycle these routes share, and the Users guide for the screen this powers.