Show account names and roles on cards from scheduled scans
Cards from scheduled scans of editor-saved profiles showed the account's
GUID as its name and no Elev/Ansat badge: the profile editor stored bare
id strings, the scheduler passes them straight to run_scan, and run_scan
normalised them to displayName=<id>, userRole="other". Before 1.7.10 the
scan-start profile overwrite kept repairing profiles to named objects,
which is why this surfaced now.
- run_scan: _enrich_bare_users() looks bare accounts up in the directory
(list_users, falling back to lookup_user) for name and role.
- M365Connector.build_role_sku_map(): the SKU map /api/users built inline,
now shared so cards and the account list classify roles identically.
lookup_user() also returns assignedLicenses.
- Profile editor saves {id, displayName, userRole} (manual accounts keep
email/manual), like the 💾 save.
- _accountPill: an account_name equal to account_id counts as missing;
name and role are filled from the loaded user list, so existing cards
display correctly without a rescan.
- Excel export: the scan's account-name map no longer breaks on bare id
strings (.get() on a str raised and dropped the whole map).
Tests: tests/test_account_names.py (10); the run_scan test fails on the
old engine with "Collecting emails — guid-henrik…".
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
c55db48894
commit
20156ffc93
8 files changed
+283
-37
No files matched your search
+25
-1
@@ -226,7 +226,8 @@ class M365Connector:
|
||||
|
||||
def lookup_user(self, upn: str) -> dict:
|
||||
"""Look up one directory user by UPN or email (requires User.Read.All)."""
|
||||
return self._get(f"/users/{upn}", {"$select": "id,displayName,mail,userPrincipalName"})
|
||||
return self._get(f"/users/{upn}",
|
||||
{"$select": "id,displayName,mail,userPrincipalName,assignedLicenses"})
|
||||
|
||||
def list_users(self, top: int = 999) -> list:
|
||||
"""List licensed domain users in the tenant (requires User.Read.All).
|
||||
@@ -1156,6 +1157,29 @@ class M365Connector:
|
||||
break
|
||||
return result
|
||||
|
||||
def build_role_sku_map(self, users: list) -> dict:
|
||||
"""The {skuId: skuPartNumber} map classify_user_role() needs.
|
||||
|
||||
Tenant SKUs (get_subscribed_skus) merged with a sample of per-user
|
||||
licenseDetails — the tenant call may only return the admin's own SKUs.
|
||||
Shared by GET /api/users and the scan engine so both classify roles
|
||||
the same way. Never raises; an empty map means ID-only classification.
|
||||
"""
|
||||
try:
|
||||
sku_map = self.get_subscribed_skus()
|
||||
except Exception:
|
||||
sku_map = {}
|
||||
try:
|
||||
per_user = self.build_sku_map_from_users(users)
|
||||
if per_user:
|
||||
added = len(set(per_user) - set(sku_map))
|
||||
sku_map.update(per_user)
|
||||
if added:
|
||||
logger.info("[skus] merged %d additional SKU(s) from per-user licenseDetails", added)
|
||||
except Exception:
|
||||
pass
|
||||
return sku_map
|
||||
|
||||
def classify_user_role(self, assigned_licenses: list,
|
||||
sku_map: dict) -> str:
|
||||
"""Return 'student', 'staff', or 'other' based on assigned O365 licenses.
|
||||
|
||||
Reference in new issue
Block a user