|
| 1 | +"""Discussion sample: one native Python read through Exchange's internal transport.""" |
| 2 | + |
| 3 | +import argparse |
| 4 | +import asyncio |
| 5 | +import json |
| 6 | +from uuid import UUID, uuid4 |
| 7 | + |
| 8 | +import httpx |
| 9 | +from azure.identity import DeviceCodeCredential |
| 10 | +from kiota_abstractions.method import Method |
| 11 | +from kiota_abstractions.native_response_handler import NativeResponseHandler |
| 12 | +from kiota_abstractions.request_information import RequestInformation |
| 13 | +from kiota_authentication_azure.azure_identity_authentication_provider import ( |
| 14 | + AzureIdentityAuthenticationProvider, |
| 15 | +) |
| 16 | +from kiota_http.httpx_request_adapter import HttpxRequestAdapter |
| 17 | +from kiota_http.kiota_client_factory import KiotaClientFactory |
| 18 | +from kiota_http.middleware.options import ( |
| 19 | + RedirectHandlerOption, |
| 20 | + ResponseHandlerOption, |
| 21 | + RetryHandlerOption, |
| 22 | +) |
| 23 | + |
| 24 | + |
| 25 | +async def read_quarantine(tenant_id: str, client_id: str, anchor_mailbox: str) -> None: |
| 26 | + """Authenticate with the caller's own app and print a count, without message data.""" |
| 27 | + tenant_id, client_id = str(UUID(tenant_id)), str(UUID(client_id)) |
| 28 | + if ( |
| 29 | + not anchor_mailbox.startswith("UPN:") |
| 30 | + or not anchor_mailbox[4:].strip() |
| 31 | + or any(character in anchor_mailbox for character in "\r\n") |
| 32 | + ): |
| 33 | + raise ValueError( |
| 34 | + "anchor_mailbox must be a nonempty UPN:<lab-user> routing hint" |
| 35 | + ) |
| 36 | + |
| 37 | + request = RequestInformation(Method.POST) |
| 38 | + request.url = ( |
| 39 | + f"https://outlook.office365.com/adminapi/beta/{tenant_id}/InvokeCommand" |
| 40 | + ) |
| 41 | + for name, value in { |
| 42 | + "Accept": "application/json", |
| 43 | + "Accept-Charset": "UTF-8", |
| 44 | + "Content-Type": "application/json", |
| 45 | + "X-CmdletName": "Get-QuarantineMessage", |
| 46 | + "X-AnchorMailbox": anchor_mailbox, |
| 47 | + "X-ResponseFormat": "auto", |
| 48 | + "X-SerializationLevel": "Partial", |
| 49 | + "Prefer": "odata.maxpagesize=1000", |
| 50 | + "client-request-id": str(uuid4()), |
| 51 | + }.items(): |
| 52 | + request.headers.add(name, value) |
| 53 | + request.content = json.dumps( |
| 54 | + { |
| 55 | + "CmdletInput": { |
| 56 | + "CmdletName": "Get-QuarantineMessage", |
| 57 | + "Parameters": {"Page": 1, "PageSize": 1}, |
| 58 | + } |
| 59 | + } |
| 60 | + ).encode("utf-8") |
| 61 | + request.add_request_options( |
| 62 | + [ |
| 63 | + ResponseHandlerOption(NativeResponseHandler()), |
| 64 | + RedirectHandlerOption(should_redirect=False), |
| 65 | + RetryHandlerOption(max_retries=3), |
| 66 | + ] |
| 67 | + ) |
| 68 | + |
| 69 | + with DeviceCodeCredential( |
| 70 | + tenant_id=tenant_id, client_id=client_id |
| 71 | + ) as credential: |
| 72 | + authentication = AzureIdentityAuthenticationProvider( |
| 73 | + credential, |
| 74 | + scopes=["https://outlook.office365.com/.default"], |
| 75 | + allowed_hosts=["outlook.office365.com"], |
| 76 | + ) |
| 77 | + async with KiotaClientFactory.create_with_default_middleware() as http_client: |
| 78 | + adapter = HttpxRequestAdapter(authentication, http_client=http_client) |
| 79 | + response = await adapter.send_primitive_async(request, "bytes", None) |
| 80 | + if not isinstance(response, httpx.Response): |
| 81 | + raise TypeError("Expected the native HTTPX response") |
| 82 | + try: |
| 83 | + response.raise_for_status() |
| 84 | + payload = response.json() |
| 85 | + if not isinstance(payload, dict): |
| 86 | + raise TypeError("Expected an Exchange response object") |
| 87 | + if "error" in payload or payload.get("@adminapi.errors"): |
| 88 | + raise RuntimeError("Exchange returned a command error") |
| 89 | + records = payload.get("value") |
| 90 | + if not isinstance(records, list) or any( |
| 91 | + not isinstance(record, dict) for record in records |
| 92 | + ): |
| 93 | + raise RuntimeError("Expected a value array of records") |
| 94 | + if any("_cliXml" in record for record in records): |
| 95 | + raise RuntimeError( |
| 96 | + "PowerShell CLIXML deserialization is outside this sample" |
| 97 | + ) |
| 98 | + if payload.get("@odata.nextLink"): |
| 99 | + raise RuntimeError("Transport continuation is outside this sample") |
| 100 | + request_id = response.headers.get("request-id") or response.headers.get( |
| 101 | + "x-ms-request-id" |
| 102 | + ) |
| 103 | + print(f"Returned {len(records)} record(s).") |
| 104 | + print(f"Request ID: {request_id or '(not supplied)'}") |
| 105 | + finally: |
| 106 | + await response.aclose() |
| 107 | + |
| 108 | + |
| 109 | +if __name__ == "__main__": |
| 110 | + parser = argparse.ArgumentParser(description=__doc__) |
| 111 | + parser.add_argument("--tenant-id", required=True) |
| 112 | + parser.add_argument("--client-id", required=True) |
| 113 | + parser.add_argument("--anchor-mailbox", required=True) |
| 114 | + arguments = parser.parse_args() |
| 115 | + asyncio.run( |
| 116 | + read_quarantine( |
| 117 | + arguments.tenant_id, arguments.client_id, arguments.anchor_mailbox |
| 118 | + ) |
| 119 | + ) |
0 commit comments