diff --git a/.claude/skills/remediation-tool/scripts/onboard-tenant.sh b/.claude/skills/remediation-tool/scripts/onboard-tenant.sh index 2453804..c74ac9a 100644 --- a/.claude/skills/remediation-tool/scripts/onboard-tenant.sh +++ b/.claude/skills/remediation-tool/scripts/onboard-tenant.sh @@ -1,20 +1,23 @@ #!/usr/bin/env bash # Assign required Entra directory roles to ComputerGuru MSP service principals -# in a newly-consented customer tenant. +# in a newly-consented customer tenant, and programmatically consent all other +# ComputerGuru apps so only Tenant Admin requires a manual customer consent click. # # Usage: onboard-tenant.sh [--dry-run] # # What this script does: # 1. Resolves the tenant ID # 2. Acquires a Tenant Admin token (fails gracefully if not consented) -# 3. For each SP that needs directory roles, checks current assignments -# 4. Assigns any missing roles -# 5. Prints a final status table +# 3. Creates SPs for Security Investigator, Exchange Operator, User Manager, +# and Defender Add-on (equivalent to admin consent for each) +# 4. Grants all required Graph/EXO/Defender appRoleAssignments to each SP +# 5. Assigns required directory roles to each SP +# 6. Prints a final status table # # Exit codes: # 0 all roles present or successfully assigned # 1 resolve failure -# 2 Tenant Admin not consented (consent URLs printed) +# 2 Tenant Admin not consented (consent URL printed) # 3 vault error # 10 partial failure (some roles could not be assigned) set -euo pipefail @@ -32,11 +35,72 @@ APP_USER_MGR="64fac46b-8b44-41ad-93ee-7da03927576c" APP_TENANT_ADMIN="709e6eed-0711-4875-9c44-2d3518c47063" APP_DEFENDER="dbf8ad1a-54f4-4bb8-8a9e-ea5b9634635b" -# ── Role definition GUIDs ───────────────────────────────────────────────────── +# ── Resource app IDs (well-known Microsoft multi-tenant apps) ───────────────── +GRAPH_APP_ID="00000003-0000-0000-c000-000000000000" +EXO_APP_ID="00000002-0000-0ff1-ce00-000000000000" +DEFENDER_APP_ID="fc780465-2017-40d4-a0c5-307022471b92" + +# ── Directory role GUIDs ────────────────────────────────────────────────────── ROLE_EXCHANGE_ADMIN="29232cdf-9323-42fd-ade2-1d097af3e4de" ROLE_USER_ADMIN="fe930be7-5e62-47db-91af-98c3a49a38b1" ROLE_AUTH_ADMIN="c4e39bd9-1100-46d3-8c65-fb160da0071f" +# ── Graph appRole GUIDs per app (from requiredResourceAccess in home tenant) ── +# Security Investigator — Graph +SEC_INV_GRAPH_ROLES=( + "df021288-bdef-4463-88db-98f22de89214" + "b0afded3-3588-46d8-8b3d-9842eff778da" + "7ab1d382-f21e-4acd-a863-ba3e13f7da61" + "40f97065-369a-49f4-947c-6a255697ae91" + "810c84a8-4a9e-49e6-bf7d-12d183f40d01" + "9a5d68dd-52b0-4cc2-bd40-abcf44ac3a30" + "38d9df27-64da-44fd-b7c5-a6fbac20248f" + "dc5007c0-2d7d-4c42-879c-2dab87571379" + "246dd0d5-5bd0-4def-940b-0421030a5b68" + "498476ce-e0fe-48b0-b801-37ba7e2685c6" +) +# Security Investigator — Exchange Online +SEC_INV_EXO_ROLES=( + "dc890d15-9560-4a4c-9b7f-a736ec74ec40" +) + +# Exchange Operator — Graph +EXCH_OP_GRAPH_ROLES=( + "df021288-bdef-4463-88db-98f22de89214" + "6931bccd-447a-43d1-b442-00a195474933" + "e2a3a72e-5f79-4c64-b1b1-878b674786c9" + "77f3a031-c388-4f99-b373-dc68676a979e" + "498476ce-e0fe-48b0-b801-37ba7e2685c6" +) +# Exchange Operator — Exchange Online +EXCH_OP_EXO_ROLES=( + "dc890d15-9560-4a4c-9b7f-a736ec74ec40" + "dc50a0fb-09a3-484d-be87-e023b12c6440" +) + +# User Manager — Graph only +USER_MGR_GRAPH_ROLES=( + "741f803b-c850-494e-b5df-cde7c675a1ca" + "19dbc75e-c2e2-444c-a770-ec69d8559fc7" + "62a82d76-70ea-41e2-9197-370581804d09" + "50483e42-d915-4231-9639-7fdb7fd190e5" + "77f3a031-c388-4f99-b373-dc68676a979e" + "498476ce-e0fe-48b0-b801-37ba7e2685c6" +) + +# Defender Add-on — Graph +DEFENDER_GRAPH_ROLES=( + "bf394140-e372-4bf9-a898-299cfc7564e5" +) +# Defender Add-on — Defender ATP +DEFENDER_ATP_ROLES=( + "71fe6b80-7034-4028-9ed8-0f316df9c3ff" + "ea8291d3-4b9a-44b5-bc3a-6cea3026dc79" + "93489bf5-0fbc-4f2d-b901-33f2fe08ff05" + "41269fc5-d04d-4bfd-bce7-43a51cea049a" + "528ca142-c849-4a5b-935e-10b8b9c38a84" +) + CONSENT_BASE="https://login.microsoftonline.com" CONSENT_REDIRECT="https://azcomputerguru.com" @@ -45,33 +109,25 @@ print_consent_urls() { local tenant_id="$1" echo "" echo "[INFO] Consent URLs for tenant $tenant_id (provide to customer Global Admin):" - echo " [1] Tenant Admin (consent FIRST — needed for role assignment):" + echo " [1] Tenant Admin (consent FIRST — needed for programmatic onboarding of all other apps):" echo " ${CONSENT_BASE}/${tenant_id}/adminconsent?client_id=${APP_TENANT_ADMIN}&redirect_uri=${CONSENT_REDIRECT}&prompt=consent" echo "" - echo " [2] Security Investigator:" - echo " ${CONSENT_BASE}/${tenant_id}/adminconsent?client_id=${APP_SEC_INV}&redirect_uri=${CONSENT_REDIRECT}&prompt=consent" - echo "" - echo " [3] Exchange Operator:" - echo " ${CONSENT_BASE}/${tenant_id}/adminconsent?client_id=${APP_EXCH_OP}&redirect_uri=${CONSENT_REDIRECT}&prompt=consent" - echo "" - echo " [4] User Manager:" - echo " ${CONSENT_BASE}/${tenant_id}/adminconsent?client_id=${APP_USER_MGR}&redirect_uri=${CONSENT_REDIRECT}&prompt=consent" - echo "" - echo " [5] Defender Add-on (MDE-licensed tenants only):" - echo " ${CONSENT_BASE}/${tenant_id}/adminconsent?client_id=${APP_DEFENDER}&redirect_uri=${CONSENT_REDIRECT}&prompt=consent" - echo "" - echo " Entra role assignment URL (for manual verification after script runs):" - echo " https://entra.microsoft.com/#@${tenant_id}/view/Microsoft_AAD_IAM/RolesAndAdministratorsMenuBlade" - echo "" - echo "[INFO] After the customer admin accepts Tenant Admin consent, run:" + echo " After the admin accepts Tenant Admin consent, run:" echo " bash $0 $TARGET" + echo "" + echo " The script will then automatically consent all other apps in the suite." + echo "" + echo " (Optional — only needed if Tenant Admin consent failed for individual apps):" + echo " [2] Security Investigator: ${CONSENT_BASE}/${tenant_id}/adminconsent?client_id=${APP_SEC_INV}&redirect_uri=${CONSENT_REDIRECT}&prompt=consent" + echo " [3] Exchange Operator: ${CONSENT_BASE}/${tenant_id}/adminconsent?client_id=${APP_EXCH_OP}&redirect_uri=${CONSENT_REDIRECT}&prompt=consent" + echo " [4] User Manager: ${CONSENT_BASE}/${tenant_id}/adminconsent?client_id=${APP_USER_MGR}&redirect_uri=${CONSENT_REDIRECT}&prompt=consent" + echo " [5] Defender Add-on (MDE-licensed tenants only): ${CONSENT_BASE}/${tenant_id}/adminconsent?client_id=${APP_DEFENDER}&redirect_uri=${CONSENT_REDIRECT}&prompt=consent" } # ── Helper: get SP OID in tenant ────────────────────────────────────────────── get_sp_oid() { local token="$1" local app_id="$2" - local tenant_id="$3" local resp resp=$(curl -s --max-time 15 \ -H "Authorization: Bearer $token" \ @@ -82,7 +138,188 @@ get_sp_oid() { echo "$resp" | jq -r '.value[0].id // empty' } -# ── Helper: check if role already assigned ──────────────────────────────────── +# ── Helper: create SP for our app if not present ────────────────────────────── +create_sp_if_missing() { + local token="$1" + local app_id="$2" + local app_name="$3" + + local oid + oid=$(get_sp_oid "$token" "$app_id") + + if [[ -n "$oid" ]]; then + echo " [OK] $app_name SP already present: $oid" >&2 + echo "$oid" + return 0 + fi + + if [[ "$DRY_RUN" == "true" ]]; then + echo " [DRY-RUN] Would create SP for $app_name ($app_id)" >&2 + echo "" + return 0 + fi + + local resp + resp=$(curl -s --max-time 15 \ + -H "Authorization: Bearer $token" \ + -H "Content-Type: application/json" \ + -X POST \ + "https://graph.microsoft.com/v1.0/servicePrincipals" \ + -d "{\"appId\": \"$app_id\"}") + + local new_oid + new_oid=$(echo "$resp" | jq -r '.id // empty') + + if [[ -z "$new_oid" ]]; then + local err_code + err_code=$(echo "$resp" | jq -r '.error.code // empty') + if [[ "$err_code" == "Request_MultipleObjectsWithSameKeyValue" ]] || echo "$resp" | grep -qi "conflicting"; then + oid=$(get_sp_oid "$token" "$app_id") + echo " [OK] $app_name SP already exists: $oid" >&2 + echo "$oid" + return 0 + fi + echo " [ERROR] Failed to create SP for $app_name: $(echo "$resp" | jq -r '.error.message // empty')" >&2 + return 1 + fi + + echo " [CREATED] $app_name SP: $new_oid" >&2 + echo "$new_oid" +} + +# ── Helper: grant single appRoleAssignment (idempotent) ─────────────────────── +grant_app_role() { + local token="$1" + local principal_oid="$2" + local resource_oid="$3" + local role_id="$4" + + # Check if already granted + local already + already=$(curl -s --max-time 15 \ + -H "Authorization: Bearer $token" \ + "https://graph.microsoft.com/v1.0/servicePrincipals/$principal_oid/appRoleAssignments" \ + | jq --arg rid "$role_id" '[.value[] | select(.appRoleId == $rid)] | length > 0') + + if [[ "$already" == "true" ]]; then + return 0 + fi + + if [[ "$DRY_RUN" == "true" ]]; then + echo " [DRY-RUN] Would grant role $role_id" + return 0 + fi + + local body + body=$(jq -n \ + --arg principal "$principal_oid" \ + --arg resource "$resource_oid" \ + --arg role "$role_id" \ + '{"principalId": $principal, "resourceId": $resource, "appRoleId": $role}') + + local resp + resp=$(curl -s --max-time 15 \ + -H "Authorization: Bearer $token" \ + -H "Content-Type: application/json" \ + -X POST \ + "https://graph.microsoft.com/v1.0/servicePrincipals/$principal_oid/appRoleAssignments" \ + -d "$body") + + local granted_id + granted_id=$(echo "$resp" | jq -r '.id // empty') + + if [[ -z "$granted_id" ]]; then + local err_code + err_code=$(echo "$resp" | jq -r '.error.code // empty') + if [[ "$err_code" == "Request_MultipleObjectsWithSameKeyValue" ]] || echo "$resp" | grep -qi "conflicting"; then + return 0 + fi + echo " [ERROR] grant_app_role failed for $role_id: $(echo "$resp" | jq -r '.error.message // "unknown"')" >&2 + return 1 + fi +} + +# ── Helper: consent app + grant all permissions ─────────────────────────────── +# Usage: consent_app \ +# \ +# [] [] +consent_app() { + local token="$1" + local app_id="$2" + local app_name="$3" + local graph_sp_oid="$4" + local exo_sp_oid="${5:-}" + local defender_sp_oid="${6:-}" + local graph_roles_varname="$7" + local exo_roles_varname="${8:-}" + local atp_roles_varname="${9:-}" + + echo "" + echo "[CONSENT] $app_name ($app_id)" + + # Create SP (or confirm existing) + local sp_oid + sp_oid=$(create_sp_if_missing "$token" "$app_id" "$app_name") + if [[ -z "$sp_oid" ]]; then + echo " [ERROR] Cannot proceed — SP creation failed" >&2 + return 1 + fi + + local grant_errors=0 + + # Grant Graph permissions + eval "local graph_roles=(\"\${${graph_roles_varname}[@]}\")" + local granted=0 skipped=0 errors=0 + for role_id in "${graph_roles[@]}"; do + if grant_app_role "$token" "$sp_oid" "$graph_sp_oid" "$role_id"; then + ((granted++)) || true + else + ((errors++)) || true + ((grant_errors++)) || true + fi + done + echo " Graph permissions: ${#graph_roles[@]} total — $errors error(s)" + + # Grant Exchange Online permissions + if [[ -n "$exo_roles_varname" ]] && [[ -n "$exo_sp_oid" ]]; then + eval "local exo_roles=(\"\${${exo_roles_varname}[@]}\")" + local exo_errors=0 + for role_id in "${exo_roles[@]}"; do + if ! grant_app_role "$token" "$sp_oid" "$exo_sp_oid" "$role_id"; then + ((exo_errors++)) || true + ((grant_errors++)) || true + fi + done + echo " Exchange Online permissions: ${#exo_roles[@]} total — $exo_errors error(s)" + elif [[ -n "$exo_roles_varname" ]] && [[ -z "$exo_sp_oid" ]]; then + echo " [WARNING] Exchange Online SP not found — EXO permissions skipped" + fi + + # Grant Defender ATP permissions + if [[ -n "$atp_roles_varname" ]] && [[ -n "$defender_sp_oid" ]]; then + eval "local atp_roles=(\"\${${atp_roles_varname}[@]}\")" + local atp_errors=0 + for role_id in "${atp_roles[@]}"; do + if ! grant_app_role "$token" "$sp_oid" "$defender_sp_oid" "$role_id"; then + ((atp_errors++)) || true + ((grant_errors++)) || true + fi + done + echo " Defender ATP permissions: ${#atp_roles[@]} total — $atp_errors error(s)" + elif [[ -n "$atp_roles_varname" ]] && [[ -z "$defender_sp_oid" ]]; then + echo " [INFO] Defender ATP SP not found — tenant likely not MDE-licensed, skipping" + fi + + if [[ $grant_errors -eq 0 ]]; then + echo " [OK] $app_name fully consented and permissions granted" + return 0 + else + echo " [WARNING] $app_name consent completed with $grant_errors permission error(s)" + return 1 + fi +} + +# ── Helper: check if directory role already assigned ───────────────────────── role_assigned() { local token="$1" local sp_oid="$2" @@ -124,7 +361,6 @@ assign_role() { local assigned_id assigned_id=$(echo "$resp" | jq -r '.id // empty') if [[ -z "$assigned_id" ]]; then - # Already assigned returns a conflict; treat that as OK local err_code err_code=$(echo "$resp" | jq -r '.error.code // empty') if [[ "$err_code" == "Conflict" ]] || [[ "$err_code" == "Request_MultipleObjectsWithSameKeyValue" ]] || \ @@ -147,18 +383,11 @@ if [[ -z "$TENANT_ID" ]]; then exit 1 fi -# Attempt to display the domain in output even if input was a GUID DISPLAY_NAME="$TARGET" -if [[ "$TARGET" == "$TENANT_ID" ]]; then - DISPLAY_NAME="$TENANT_ID" -fi echo "[OK] Tenant: $DISPLAY_NAME ($TENANT_ID)" # ── Step 2: Acquire Tenant Admin token ─────────────────────────────────────── echo "[INFO] Acquiring Tenant Admin token for $TENANT_ID..." -TENANT_ADMIN_TOKEN="" -TOKEN_ERR="" -# Capture both stdout and stderr; get-token.sh exits non-zero on failure set +e TENANT_ADMIN_TOKEN_OUT=$("$SCRIPT_DIR/get-token.sh" "$TENANT_ID" "tenant-admin" 2>/tmp/onboard-token-err.txt) GET_TOKEN_EXIT=$? @@ -166,7 +395,6 @@ TOKEN_ERR=$(cat /tmp/onboard-token-err.txt 2>/dev/null || true) set -e if [[ $GET_TOKEN_EXIT -ne 0 ]]; then - # Check for AADSTS7000229 (SP not consented / not found) if echo "$TOKEN_ERR" | grep -qi "7000229\|AADSTS7000229\|service principal\|not been authorized\|not found"; then echo "[WARNING] Tenant Admin app not yet consented in tenant $TENANT_ID" print_consent_urls "$TENANT_ID" @@ -178,36 +406,78 @@ if [[ $GET_TOKEN_EXIT -ne 0 ]]; then fi TENANT_ADMIN_TOKEN="$TENANT_ADMIN_TOKEN_OUT" -# Verify the SP exists (belt-and-suspenders) -TA_SP_OID=$(get_sp_oid "$TENANT_ADMIN_TOKEN" "$APP_TENANT_ADMIN" "$TENANT_ID") +TA_SP_OID=$(get_sp_oid "$TENANT_ADMIN_TOKEN" "$APP_TENANT_ADMIN") if [[ -z "$TA_SP_OID" ]]; then echo "[WARNING] Tenant Admin SP not found in tenant — app not consented yet" print_consent_urls "$TENANT_ID" exit 2 fi -echo "[OK] Tenant Admin consented -- SP: $TA_SP_OID" +echo "[OK] Tenant Admin consented — SP: $TA_SP_OID" [[ "$DRY_RUN" == "true" ]] && echo "[INFO] --dry-run mode: no changes will be made" -# ── Step 3 & 4: Check/assign roles per SP ───────────────────────────────────── - -# Track overall status for final table -declare -A STATUS_MAP # key = "SP_NAME:ROLE_NAME", value = "OK" | "ASSIGNED" | "ERROR" | "DRY-RUN" - -# Parallel approach: fetch all SP OIDs first, then process roles +# ── Step 3: Locate resource SPs in customer tenant ─────────────────────────── echo "" -echo "[INFO] Checking service principal presence in tenant..." +echo "[INFO] Locating resource service principals in tenant..." -SEC_INV_OID=$(get_sp_oid "$TENANT_ADMIN_TOKEN" "$APP_SEC_INV" "$TENANT_ID") -USER_MGR_OID=$(get_sp_oid "$TENANT_ADMIN_TOKEN" "$APP_USER_MGR" "$TENANT_ID") +GRAPH_SP_OID=$(get_sp_oid "$TENANT_ADMIN_TOKEN" "$GRAPH_APP_ID") +EXO_SP_OID=$(get_sp_oid "$TENANT_ADMIN_TOKEN" "$EXO_APP_ID") +DEFENDER_SP_OID=$(get_sp_oid "$TENANT_ADMIN_TOKEN" "$DEFENDER_APP_ID") + +[[ -n "$GRAPH_SP_OID" ]] && echo " [OK] Microsoft Graph SP: $GRAPH_SP_OID" || echo " [ERROR] Microsoft Graph SP not found — cannot proceed" >&2 +[[ -n "$EXO_SP_OID" ]] && echo " [OK] Exchange Online SP: $EXO_SP_OID" || echo " [WARNING] Exchange Online SP not found (no Exchange license?)" +[[ -n "$DEFENDER_SP_OID" ]] && echo " [OK] Defender ATP SP: $DEFENDER_SP_OID" || echo " [INFO] Defender ATP SP not found (tenant likely not MDE-licensed)" + +if [[ -z "$GRAPH_SP_OID" ]]; then + echo "[ERROR] Microsoft Graph SP missing — cannot grant app permissions" >&2 + exit 1 +fi + +# ── Step 4: Programmatic consent — create SPs + grant appRoleAssignments ────── +echo "" +echo "[INFO] Consenting app suite in tenant (programmatic — no customer click needed)..." + +CONSENT_PARTIAL=false + +consent_app "$TENANT_ADMIN_TOKEN" "$APP_SEC_INV" "Security Investigator" \ + "$GRAPH_SP_OID" "$EXO_SP_OID" "" \ + "SEC_INV_GRAPH_ROLES" "SEC_INV_EXO_ROLES" \ + || CONSENT_PARTIAL=true + +consent_app "$TENANT_ADMIN_TOKEN" "$APP_EXCH_OP" "Exchange Operator" \ + "$GRAPH_SP_OID" "$EXO_SP_OID" "" \ + "EXCH_OP_GRAPH_ROLES" "EXCH_OP_EXO_ROLES" \ + || CONSENT_PARTIAL=true + +consent_app "$TENANT_ADMIN_TOKEN" "$APP_USER_MGR" "User Manager" \ + "$GRAPH_SP_OID" "" "" \ + "USER_MGR_GRAPH_ROLES" \ + || CONSENT_PARTIAL=true + +if [[ -n "$DEFENDER_SP_OID" ]]; then + consent_app "$TENANT_ADMIN_TOKEN" "$APP_DEFENDER" "Defender Add-on" \ + "$GRAPH_SP_OID" "" "$DEFENDER_SP_OID" \ + "DEFENDER_GRAPH_ROLES" "" "DEFENDER_ATP_ROLES" \ + || CONSENT_PARTIAL=true +else + echo "" + echo "[INFO] Skipping Defender Add-on consent (no MDE license detected)" +fi + +# ── Step 5: Check/assign directory roles per SP ─────────────────────────────── +declare -A STATUS_MAP + +echo "" +echo "[INFO] Checking and assigning directory roles..." + +SEC_INV_OID=$(get_sp_oid "$TENANT_ADMIN_TOKEN" "$APP_SEC_INV") +USER_MGR_OID=$(get_sp_oid "$TENANT_ADMIN_TOKEN" "$APP_USER_MGR") PARTIAL_FAILURE=false -# ── Security Investigator -> Exchange Administrator ─────────────────────────── +# Security Investigator -> Exchange Administrator if [[ -z "$SEC_INV_OID" ]]; then - echo "[WARNING] Security Investigator SP not found in tenant (not consented)" - echo " Consent URL:" - echo " ${CONSENT_BASE}/${TENANT_ID}/adminconsent?client_id=${APP_SEC_INV}&redirect_uri=${CONSENT_REDIRECT}&prompt=consent" + echo "[WARNING] Security Investigator SP still not found after consent attempt" STATUS_MAP["Security Investigator:Exchange Administrator"]="MISSING SP" else echo "" @@ -219,11 +489,7 @@ else else echo " Exchange Administrator: MISSING -> ASSIGNING..." if assign_role "$TENANT_ADMIN_TOKEN" "$SEC_INV_OID" "$ROLE_EXCHANGE_ADMIN" "Exchange Administrator"; then - if [[ "$DRY_RUN" == "true" ]]; then - STATUS_MAP["Security Investigator:Exchange Administrator"]="DRY-RUN" - else - STATUS_MAP["Security Investigator:Exchange Administrator"]="ASSIGNED" - fi + STATUS_MAP["Security Investigator:Exchange Administrator"]=$( [[ "$DRY_RUN" == "true" ]] && echo "DRY-RUN" || echo "ASSIGNED" ) else STATUS_MAP["Security Investigator:Exchange Administrator"]="ERROR" PARTIAL_FAILURE=true @@ -231,11 +497,9 @@ else fi fi -# ── User Manager -> User Administrator + Authentication Administrator ────────── +# User Manager -> User Administrator + Authentication Administrator if [[ -z "$USER_MGR_OID" ]]; then - echo "[WARNING] User Manager SP not found in tenant (not consented)" - echo " Consent URL:" - echo " ${CONSENT_BASE}/${TENANT_ID}/adminconsent?client_id=${APP_USER_MGR}&redirect_uri=${CONSENT_REDIRECT}&prompt=consent" + echo "[WARNING] User Manager SP still not found after consent attempt" STATUS_MAP["User Manager:User Administrator"]="MISSING SP" STATUS_MAP["User Manager:Authentication Administrator"]="MISSING SP" else @@ -249,11 +513,7 @@ else else echo " User Administrator: MISSING -> ASSIGNING..." if assign_role "$TENANT_ADMIN_TOKEN" "$USER_MGR_OID" "$ROLE_USER_ADMIN" "User Administrator"; then - if [[ "$DRY_RUN" == "true" ]]; then - STATUS_MAP["User Manager:User Administrator"]="DRY-RUN" - else - STATUS_MAP["User Manager:User Administrator"]="ASSIGNED" - fi + STATUS_MAP["User Manager:User Administrator"]=$( [[ "$DRY_RUN" == "true" ]] && echo "DRY-RUN" || echo "ASSIGNED" ) else STATUS_MAP["User Manager:User Administrator"]="ERROR" PARTIAL_FAILURE=true @@ -267,11 +527,7 @@ else else echo " Authentication Administrator: MISSING -> ASSIGNING..." if assign_role "$TENANT_ADMIN_TOKEN" "$USER_MGR_OID" "$ROLE_AUTH_ADMIN" "Authentication Administrator"; then - if [[ "$DRY_RUN" == "true" ]]; then - STATUS_MAP["User Manager:Authentication Administrator"]="DRY-RUN" - else - STATUS_MAP["User Manager:Authentication Administrator"]="ASSIGNED" - fi + STATUS_MAP["User Manager:Authentication Administrator"]=$( [[ "$DRY_RUN" == "true" ]] && echo "DRY-RUN" || echo "ASSIGNED" ) else STATUS_MAP["User Manager:Authentication Administrator"]="ERROR" PARTIAL_FAILURE=true @@ -281,7 +537,7 @@ fi # ── Step 6: Final status table ──────────────────────────────────────────────── echo "" -if [[ "$PARTIAL_FAILURE" == "true" ]]; then +if [[ "$PARTIAL_FAILURE" == "true" ]] || [[ "$CONSENT_PARTIAL" == "true" ]]; then echo "[WARNING] Onboarding completed with errors for $DISPLAY_NAME" else if [[ "$DRY_RUN" == "true" ]]; then @@ -292,16 +548,14 @@ else fi echo "SP roles status:" -# Security Investigator SEC_EXCH="${STATUS_MAP["Security Investigator:Exchange Administrator"]:-SKIPPED}" echo " Security Investigator:" -printf " Exchange Administrator: %s\n" "[$SEC_EXCH]" +printf " Exchange Administrator: %s\n" "[$SEC_EXCH]" -# User Manager UA="${STATUS_MAP["User Manager:User Administrator"]:-SKIPPED}" AA="${STATUS_MAP["User Manager:Authentication Administrator"]:-SKIPPED}" echo " User Manager:" -printf " User Administrator: %s\n" "[$UA]" +printf " User Administrator: %s\n" "[$UA]" printf " Authentication Administrator: %s\n" "[$AA]" if [[ "$PARTIAL_FAILURE" == "true" ]]; then