terminal/src/cascadia/TerminalConnection/AzureConnection.h
Don-Vito 0c0830b2f2
Azure: fall back to powershell when no preferred shell is set (#8197)
I am still not sure what is the full set of scenarios that the problem
might occur, but for me it occurred for an "old" cloud shell account,
and didn't reproduce since I have reconfigured it. These behavior might
be explained by the fact that "preferred shell type" did not exist in
the API originally and thus was not set. In such case, Terminal
succeeds to retrieve to the settings but then crashes when reading the
missing field.  To fix it, I handle the case where the field is missing
and fallback to PowerShell.

## Validation Steps Performed
* Tested manually, only once.

Closes #7056
2020-11-09 14:55:45 -08:00

109 lines
3.7 KiB
C++

// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
#pragma once
#include "AzureConnection.g.h"
#include <cpprest/http_client.h>
#include <cpprest/http_listener.h>
#include <cpprest/ws_client.h>
#include <mutex>
#include <condition_variable>
#include "../cascadia/inc/cppwinrt_utils.h"
#include "ConnectionStateHolder.h"
#include "AzureClient.h"
namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
{
struct AzureConnection : AzureConnectionT<AzureConnection>, ConnectionStateHolder<AzureConnection>
{
static winrt::guid ConnectionType() noexcept;
static bool IsAzureConnectionAvailable() noexcept;
AzureConnection(const uint32_t rows, const uint32_t cols);
void Start();
void WriteInput(hstring const& data);
void Resize(uint32_t rows, uint32_t columns);
void Close();
WINRT_CALLBACK(TerminalOutput, TerminalOutputHandler);
private:
uint32_t _initialRows{};
uint32_t _initialCols{};
enum class AzureState
{
AccessStored,
DeviceFlow,
TenantChoice,
StoreTokens,
TermConnecting,
TermConnected,
};
AzureState _state{ AzureState::AccessStored };
wil::unique_handle _hOutputThread;
DWORD _OutputThread();
void _RunAccessState();
void _RunDeviceFlowState();
void _RunTenantChoiceState();
void _RunStoreState();
void _RunConnectState();
const utility::string_t _loginUri{ U("https://login.microsoftonline.com/") };
const utility::string_t _resourceUri{ U("https://management.azure.com/") };
const utility::string_t _wantedResource{ U("https://management.core.windows.net/") };
const int _expireLimit{ 2700 };
utility::string_t _accessToken;
utility::string_t _refreshToken;
int _expiry{ 0 };
utility::string_t _cloudShellUri;
utility::string_t _terminalID;
std::vector<::Microsoft::Terminal::Azure::Tenant> _tenantList;
std::optional<::Microsoft::Terminal::Azure::Tenant> _currentTenant;
void _WriteStringWithNewline(const std::wstring_view str);
void _WriteCaughtExceptionRecord();
web::json::value _SendRequestReturningJson(web::http::client::http_client& theClient, web::http::http_request theRequest);
web::json::value _SendAuthenticatedRequestReturningJson(web::http::client::http_client& theClient, web::http::http_request theRequest);
web::json::value _GetDeviceCode();
web::json::value _WaitForUser(utility::string_t deviceCode, int pollInterval, int expiresIn);
void _PopulateTenantList();
void _RefreshTokens();
web::json::value _GetCloudShellUserSettings();
utility::string_t _GetCloudShell();
utility::string_t _GetTerminal(utility::string_t shellType);
void _StoreCredential();
void _RemoveCredentials();
enum class InputMode
{
None = 0,
Line
};
InputMode _currentInputMode{ InputMode::None };
std::wstring _userInput;
std::condition_variable _inputEvent;
std::mutex _inputMutex;
std::optional<std::wstring> _ReadUserInput(InputMode mode);
web::websockets::client::websocket_client _cloudShellSocket;
static std::optional<utility::string_t> _ParsePreferredShellType(const web::json::value& settingsResponse);
};
}
namespace winrt::Microsoft::Terminal::TerminalConnection::factory_implementation
{
struct AzureConnection : AzureConnectionT<AzureConnection, implementation::AzureConnection>
{
};
}