# Graph + Exchange REST Cheatsheet

All examples assume:
- `$GT` = Graph token (`investigator` tier)
- `$EXO_R` = Exchange read token (`investigator-exo` tier) — Get-* cmdlets
- `$EXO_W` = Exchange write token (`exchange-op` tier) — Set-*/Remove-* cmdlets
- `$UT` = User Manager graph token (`user-manager` tier) — user write ops
- `$TID` = tenant ID, `$UPN`/`$UID` = user identifiers

Acquire tokens:
```bash
GT=$(bash .claude/skills/remediation-tool/scripts/get-token.sh $TID investigator)
EXO_R=$(bash .claude/skills/remediation-tool/scripts/get-token.sh $TID investigator-exo)
EXO_W=$(bash .claude/skills/remediation-tool/scripts/get-token.sh $TID exchange-op)   # remediation only
UT=$(bash .claude/skills/remediation-tool/scripts/get-token.sh $TID user-manager)     # remediation only
```

## Graph API (`https://graph.microsoft.com/v1.0`)

### User lookup / status

```bash
# By UPN
curl -s -H "Authorization: Bearer $GT" \
  "https://graph.microsoft.com/v1.0/users/$UPN?\$select=id,displayName,userPrincipalName,mail,accountEnabled,createdDateTime,lastPasswordChangeDateTime"

# All users (filter, paged)
curl -s -H "Authorization: Bearer $GT" \
  "https://graph.microsoft.com/v1.0/users?\$top=999&\$filter=accountEnabled%20eq%20true"
```

### Mailbox

```bash
# Visible inbox rules (Graph v1.0 — does NOT return hidden rules)
/users/$UPN/mailFolders/inbox/messageRules

# Mailbox settings (auto-reply, delegates meeting option, NOT forwarding flags)
/users/$UPN/mailboxSettings

# Recent sent / deleted
/users/$UPN/mailFolders/sentitems/messages?$top=25&$orderby=sentDateTime%20desc
/users/$UPN/mailFolders/deleteditems/messages?$top=25&$orderby=receivedDateTime%20desc
```

### Authentication methods

```bash
/users/$UPN/authentication/methods
# Watch for new methods added within the attack window
```

### OAuth + app role assignments

```bash
/users/$UPN/oauth2PermissionGrants      # user-level consents
/users/$UPN/appRoleAssignments          # apps assigned to this user
/servicePrincipals/$SP_ID/appRoleAssignments  # what scopes a SP has
```

### Sign-ins (needs Entra ID P1 or higher)

```bash
# Interactive sign-ins v1.0 (does NOT include non-interactive/service-principal)
/auditLogs/signIns?$filter=userId eq '$UID' and createdDateTime ge $FROM&$top=200

# All sign-in event types (beta endpoint)
/beta/auditLogs/signIns?$filter=userId eq '$UID' and (signInEventTypes/any(t:t eq 'nonInteractiveUser'))

# Foreign successful sign-ins tenant-wide
/auditLogs/signIns?$filter=(status/errorCode eq 0) and (location/countryOrRegion ne 'US')
```

### Directory audits

```bash
# Changes targeting a specific user
/auditLogs/directoryAudits?$filter=targetResources/any(t:t/id eq '$UID')

# Tenant-wide consent / auth-method / role events
/auditLogs/directoryAudits?$filter=activityDateTime ge $FROM
# Then client-side filter by activityDisplayName ~ Consent|Authentication Method|Add service principal|Add member to role
```

### Identity Protection (needs IdentityRiskyUser.Read.All)

```bash
/identityProtection/riskyUsers
/identityProtection/riskyUsers/$UID
/identityProtection/riskDetections?$filter=userId eq '$UID'
```

### B2B guests

```bash
# Get guest by gmail/external address
/users?$filter=startswith(userPrincipalName,'dunedolly21')

# Invite audits
/auditLogs/directoryAudits?$filter=activityDisplayName eq 'Invite external user'
```

## Exchange Online REST (`https://outlook.office365.com/adminapi/beta/{tenant-id}/InvokeCommand`)

POST with JSON body `{"CmdletInput":{"CmdletName":"<cmdlet>","Parameters":{...}}}`.
- **Read ops** (Get-*): use `$EXO_R` — Security Investigator token (`investigator-exo` tier)
- **Write ops** (Set-*, Remove-*): use `$EXO_W` — Exchange Operator token (`exchange-op` tier)

### Inbox rules (INCLUDING hidden)

```json
{"CmdletInput":{"CmdletName":"Get-InboxRule","Parameters":{"Mailbox":"user@domain.com","IncludeHidden":true}}}
```

Why this matters: attackers commonly create hidden rules that Graph v1.0 cannot see.

### Mailbox forwarding / properties

```json
{"CmdletInput":{"CmdletName":"Get-Mailbox","Parameters":{"Identity":"user@domain.com"}}}
```

Check: `ForwardingAddress`, `ForwardingSmtpAddress`, `DeliverToMailboxAndForward`, `GrantSendOnBehalfTo`, `HiddenFromAddressListsEnabled`.

### Mailbox permissions (delegates / FullAccess)

```json
{"CmdletInput":{"CmdletName":"Get-MailboxPermission","Parameters":{"Identity":"user@domain.com"}}}
```

Filter out `NT AUTHORITY\\SELF` — anything else is a delegate.

### SendAs permissions

```json
{"CmdletInput":{"CmdletName":"Get-RecipientPermission","Parameters":{"Identity":"user@domain.com"}}}
```

### Transport rules (tenant-wide mail flow)

```json
{"CmdletInput":{"CmdletName":"Get-TransportRule","Parameters":{}}}
```

Check for rules that reroute, delete, or exfiltrate mail.

### SMTP AUTH

```json
{"CmdletInput":{"CmdletName":"Get-CASMailbox","Parameters":{"Identity":"user@domain.com"}}}
```

Check `SmtpClientAuthenticationDisabled`. To disable SMTP AUTH on a single mailbox (remediation): `Set-CASMailbox -SmtpClientAuthenticationDisabled $true`.

## Rate limits / pagination

- Graph signIns endpoints cap `$top` at 999. For >999 results, follow `@odata.nextLink`.
- Exchange REST has undocumented throttling — if you hit 429, back off 30–60s.
- Token is valid ~60 minutes. Script caches for 55 min.
