terminal/src/cascadia/TerminalConnection/AzureClient.h
Dustin L. Howett (MSFT) c186c7d683
Rework error handling and state flow in the Azure connection (#5356)
This commit fixes a number of problems and code quality/health issues
with the AzureConnection.

This is a general tidying-up of the azure connection. It improves error
logging (like: it actually emits error logs...) and retry logic and the
state machine and it audits the exit points of the state machine for
exceptions and removes the HRESULT returns (so they either succeed and
transition to a new state or throw an exception or are going down
anyway).

There's also a change in here that changes how we display tenants. It
adds the "default domain" to the name, so that instead of seeing this:

Conhost (aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa)
Default Directory (bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb)

you see this

Conhost (conhost.onmicrosoft.com)
Default Directory (dustinhowett.onmicrosoft.com)

Changes:

* rework tenant/tenant storage and fix display names

  Switch to the 2020 tenant API.

  Instead of passing around four loose variables, create a Tenant class
  and use that for packing/unpacking into/out of json (and the windows
  credential store, where we "cleverly" used json for the tenant info
  there too).
  
  When displaying a tenant, use its display name if there is one, the
  unknown resource string if there isn't, and the default domain if
  there is one and the ID if there isn't.
  
  Fixes #5325.

* use {fmt} for formatting request bodies
* remove dead strings
* rework/rename Request/HeaderHelper to
  Send(Authenticated)ReqReturningJson
* rewrite polling to use std::chrono
* remove HR returns from state machine
* rename state handlers from _XHelper to _RunXState
* cleanup namespaces, prefix user input with >, remove namespaces
* Rework error handling
  - _RequestHelper no longer eats exceptions.
  - Delete the "no internet" error message.
  - Wrap exceptions coming out of Azure API in a well-known type.
  - Catch by type.
  - Extract error codes for known failures (keep polling, invalid
    grant).
  - When we get an Invalid Grant, dispose of the cached refresh token
    and force the user to log in again.
  - Catch all printable exceptions and print them.
  - Remove the NoConnect state completely -- just bail out when an
    exception hits the toplevel of the output thread.
  - Move 3x logic into _RefreshTokens and pop exceptions out of it.
  - Begin abstracting into AzureClient

Fixes #5325 (by addressing its chief complaint).
Fixes #4803 (by triggering auth flow again if the token expires).
Improves diagnosability for #4575.
2020-04-16 17:32:52 -07:00

54 lines
1.6 KiB
C++

// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
#pragma once
#include "cpprest/json.h"
namespace Microsoft::Terminal::Azure
{
class AzureException : public std::runtime_error
{
std::wstring _code;
public:
static bool IsErrorPayload(const web::json::value& errorObject)
{
return errorObject.has_string_field(L"error");
}
AzureException(const web::json::value& errorObject) :
runtime_error(til::u16u8(errorObject.at(L"error_description").as_string())), // surface the human-readable description as .what()
_code(errorObject.at(L"error").as_string())
{
}
std::wstring_view GetCode() const noexcept
{
return _code;
}
};
namespace ErrorCodes
{
static constexpr std::wstring_view AuthorizationPending{ L"authorization_pending" };
static constexpr std::wstring_view InvalidGrant{ L"invalid_grant" };
}
struct Tenant
{
std::wstring ID;
std::optional<std::wstring> DisplayName;
std::optional<std::wstring> DefaultDomain;
};
}
#define THROW_IF_AZURE_ERROR(payload) \
do \
{ \
if (AzureException::IsErrorPayload((payload))) \
{ \
throw AzureException((payload)); \
} \
} while (0)