# Gotchas — Permissions, Roles, Consent

## Onboarding a new tenant

Run `onboard-tenant.sh` after admin consents each app. This assigns required directory roles automatically.

Quick steps:
1. Send consent URLs (Tenant Admin FIRST, then others)
2. After admin accepts: `bash scripts/onboard-tenant.sh <domain>`
3. Verify output shows all roles [OK]
4. Update tenant table below

If Tenant Admin is not yet consented, onboard-tenant.sh will output all needed consent URLs.

## App Suite (tiered architecture)

Five multi-tenant apps replace the old single over-permissioned app. Use minimum necessary tier.

| Tier | Display name in customer tenant | App ID | Vault file |
|---|---|---|---|
| `investigator` / `investigator-exo` | ComputerGuru Security Investigator | `bfbc12a4-f0dd-4e12-b06d-997e7271e10c` | `computerguru-security-investigator.sops.yaml` |
| `exchange-op` | ComputerGuru Exchange Operator | `b43e7342-5b4b-492f-890f-bb5a4f7f40e9` | `computerguru-exchange-operator.sops.yaml` |
| `user-manager` | ComputerGuru User Manager | `64fac46b-8b44-41ad-93ee-7da03927576c` | `computerguru-user-manager.sops.yaml` |
| `tenant-admin` | ComputerGuru Tenant Admin | `709e6eed-0711-4875-9c44-2d3518c47063` | `computerguru-tenant-admin.sops.yaml` |
| `defender` | ComputerGuru Defender Add-on | `dbf8ad1a-54f4-4bb8-8a9e-ea5b9634635b` | `computerguru-defender-addon.sops.yaml` |

**Deprecated (do not use):** ~~ComputerGuru - AI Remediation~~ (`fabb3421`) — old single-app with 159 permissions including Defender ATP. Broke consent on tenants without MDE license. Retire/delete from portal when confirmed no active tenants depend on it.

When searching customer admin portals for a service principal (role assignments, app role assignments, CA exclusions), search by the display name for that tier (e.g., "ComputerGuru Security Investigator").

## Per-tenant prerequisites

Graph API permissions alone are not enough. Most privileged operations require directory roles on the specific service principal *in that tenant*:

| Operation | App tier | Required directory role on that SP |
|---|---|---|
| Exchange REST read (Get-InboxRule, Get-Mailbox) | `investigator-exo` | Exchange Administrator |
| Exchange REST write (Set-Mailbox, Remove-InboxRule) | `exchange-op` | Exchange Administrator |
| Password reset, user property updates | `user-manager` | User Administrator |
| MFA method reset | `user-manager` | Authentication Administrator |
| Conditional Access reads/writes | `tenant-admin` | Conditional Access Administrator OR Security Administrator |
| Teams policies | `tenant-admin` | Teams Administrator |

### How to assign a role to an SP in a customer tenant

1. Sign into the customer's Entra admin center as Global Admin:
   `https://entra.microsoft.com/#@{customer-domain}`
2. Identity -> Roles & admins -> All roles -> select the role (e.g., Exchange Administrator)
3. Add assignments -> search by the app display name (e.g., "ComputerGuru Security Investigator") -> Assign
   (Active, permanent — service principals cannot activate eligible assignments)

## Admin consent URLs

Each app must be individually consented in each customer tenant. Format:

```
https://login.microsoftonline.com/{tenant-id}/adminconsent?client_id={app-id}&redirect_uri=https://azcomputerguru.com&prompt=consent
```

**Security Investigator** (consent this first — needed for all breach checks):
```
https://login.microsoftonline.com/{TENANT_ID}/adminconsent?client_id=bfbc12a4-f0dd-4e12-b06d-997e7271e10c&redirect_uri=https://azcomputerguru.com&prompt=consent
```

**Exchange Operator** (consent when remediation scope is needed):
```
https://login.microsoftonline.com/{TENANT_ID}/adminconsent?client_id=b43e7342-5b4b-492f-890f-bb5a4f7f40e9&redirect_uri=https://azcomputerguru.com&prompt=consent
```

**User Manager**:
```
https://login.microsoftonline.com/{TENANT_ID}/adminconsent?client_id=64fac46b-8b44-41ad-93ee-7da03927576c&redirect_uri=https://azcomputerguru.com&prompt=consent
```

**Tenant Admin**:
```
https://login.microsoftonline.com/{TENANT_ID}/adminconsent?client_id=709e6eed-0711-4875-9c44-2d3518c47063&redirect_uri=https://azcomputerguru.com&prompt=consent
```

**Defender Add-on** (MDE-licensed tenants only — AADSTS650052 if no MDE license):
```
https://login.microsoftonline.com/{TENANT_ID}/adminconsent?client_id=dbf8ad1a-54f4-4bb8-8a9e-ea5b9634635b&redirect_uri=https://azcomputerguru.com&prompt=consent
```

The customer admin signs in as Global Admin, clicks Accept. Redirect lands on azcomputerguru.com — expected. Verify via `/servicePrincipals/{sp-id}/appRoleAssignments` (grants timestamped today confirm success).

## Diagnosing "required scopes are missing"

Token returned 403 with `"required scopes are missing in the token"`:

1. Decode the JWT payload (2nd segment, base64url) and check the `roles` claim.
2. If the expected scope is missing from `roles`:
   - Confirm the scope is in the app manifest in the home tenant (saved, not just selected).
   - Grant admin consent in the home tenant.
   - Re-run the customer admin consent URL above for that specific app.
3. If the scope IS in `roles` but you still get 403: check for a missing directory role (see table above).

## Diagnosing Exchange REST 403

- Wrong token scope: must request `https://outlook.office365.com/.default` (use `investigator-exo` or `exchange-op` tier, NOT `investigator`).
- Missing Exchange Administrator role on the specific SP in that tenant.
- Propagation delay: newly assigned role can take up to 15 minutes to reach Exchange Online. If just assigned, wait and retry.

## AADSTS650052 — service not licensed

If token request or API call returns AADSTS650052 referencing `WindowsDefenderATP` (`fc780465`): the tenant does not have an MDE license. Do not use the `defender` tier for this tenant. Security investigation proceeds with `investigator` + `investigator-exo` only.

## Common, benign "failures" in sign-in logs

- `error 50140` "Keep me signed in interrupt" — KMSI prompt, not a real failure.
- `error 65001` "has not consented to use the application" — fires during onboarding and before consent granted. If `appDisplayName` matches any ComputerGuru app, those are our own consent attempts, not attacker activity.
- `error 50126` from the sysadmin account during onboarding is typo/retry noise — check `ipAddress` against Mike's known IPs before flagging.

## Tenants where apps are consented (as of 2026-04-20)

| Tenant | Tenant ID | Sec Inv | Exch Op | User Mgr | Tenant Admin | Defender | Exch Admin (Sec Inv) | User Admin (User Mgr) | Auth Admin (User Mgr) | Notes |
|---|---|---|---|---|---|---|---|---|---|---|
| Valleywide Plastering | 5c53ae9f... | old app only | — | — | — | — | — | old app only | — | Needs migration to new app suite |
| Dataforth | 7dfa3ce8... | old app only | — | — | — | — | old app only | old app only | — | Needs migration |
| Cascades Tucson | 207fa277-e9d8-4eb7-ada1-1064d2221498 | old app only | — | — | — | — | old app only | old app only | — | IdentityRiskyUser scope still not consented as of 2026-04-16 |
| Grabblaw | 032b383e-96e4-491b-880d-3fd3295672c3 | YES (2026-04-20) | — | YES (2026-04-20) | YES (2026-04-20) | — | ASSIGNED (2026-04-20) | ASSIGNED (2026-04-20) | ASSIGNED (2026-04-20) | Fully onboarded |
| martylryan.com | (resolve via script) | YES (2026-04-20) | — | YES (old app) | YES (2026-04-20) | — | ASSIGNED (2026-04-20) | ASSIGNED (2026-04-20) | ASSIGNED (2026-04-20) | Fully onboarded |
| mvaninc.com | 5affaf1e-de89-416b-a655-1b2cf615d5b1 | YES (2026-04-21) | — | YES (2026-04-21) | YES (2026-04-21) | — | — | — | — | Fully onboarded. Incident 2026-04-21: sysadmin GA account unauthorized sign-in from OKC via device PRT (MITCH-LAPTOP/JUNE). Remediated: pw reset, sessions revoked. CA policy (MFA all users) still pending — Mike to create. |

**Migration note:** Valleywide, Dataforth, and Cascades still use the old deprecated app. Next visit: consent Security Investigator + assign Exchange Administrator role to new SP, then retire old app consent.

Keep this table updated when rolling out to new tenants or migrating existing ones. Run `onboard-tenant.sh` after each consent and update the role columns from the script's final status output.
