зеркало из https://github.com/microsoft/terminal.git
AzureConnection: remove our dependency on cpprestsdk (#14776)
This pull request removes, in full, our dependency on cpprestsdk. This allows us to shed 500KiB-1.2MiB from our package off the top and enables the following future investments: - Removal of the App CRT forwarders to save an additional ~500KiB - Switching over to the HybridCRT and removing our dependency on _any CRT_. cpprest was built on my dev box two or so years ago, and is in _utter_ violation of our compliance guidelines on SBOM et al. In addition, this change allows us to use the proxy server configured in Windows Settings. I did this in four steps (represented roughly by the individual commits): 1. Switch from cpprest's http_client/json to Windows.Web.Http and Windows.Data.Json 2. Switch from websocketpp to winhttp.dll's WebSocket implementation¹ 3. Remove all remaining utility classes 4. Purge all dependencies from all projects and scripts on cpprest. I also took this opportunity to add a feature flag that allows Dev builds to run AzureConnection in-process. ¹ Windows.Networking.Sockets' API is so unergonomic that it was simply infeasible (and also _horrible_) to use it. ## Validation Steps I've run the Azure Connection quite a bit inproc. Closes #4575. Might be related to #5977, #11714, and with the user agent thing maybe #14403.
This commit is contained in:
Родитель
42e8de3b52
Коммит
4903cfd484
|
@ -55,6 +55,8 @@ GETMOUSEHOVERTIME
|
|||
Hashtable
|
||||
HIGHCONTRASTON
|
||||
HIGHCONTRASTW
|
||||
hinternet
|
||||
HINTERNET
|
||||
hotkeys
|
||||
href
|
||||
hrgn
|
||||
|
@ -214,6 +216,8 @@ Viewbox
|
|||
virtualalloc
|
||||
wcsstr
|
||||
wcstoui
|
||||
WDJ
|
||||
winhttp
|
||||
winmain
|
||||
winsta
|
||||
winstamin
|
||||
|
@ -221,6 +225,7 @@ wmemcmp
|
|||
wpc
|
||||
WSF
|
||||
wsregex
|
||||
WWH
|
||||
wwinmain
|
||||
xchg
|
||||
XDocument
|
||||
|
|
|
@ -311,7 +311,6 @@ CPLINFO
|
|||
cplusplus
|
||||
CPPCORECHECK
|
||||
cppcorecheckrules
|
||||
cpprest
|
||||
cpprestsdk
|
||||
cppwinrt
|
||||
CProc
|
||||
|
@ -1437,7 +1436,6 @@ PPEB
|
|||
ppf
|
||||
ppguid
|
||||
ppidl
|
||||
pplx
|
||||
PPROC
|
||||
PPROCESS
|
||||
ppropvar
|
||||
|
@ -2114,7 +2112,6 @@ WDDMCONSOLECONTEXT
|
|||
wdm
|
||||
webpage
|
||||
websites
|
||||
websockets
|
||||
wekyb
|
||||
wex
|
||||
wextest
|
||||
|
|
|
@ -67,51 +67,6 @@
|
|||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
// THIRD PARTY SOFTWARE
|
||||
"MatchedPath": [
|
||||
"cpprest*.dll"
|
||||
],
|
||||
"SigningInfo": {
|
||||
"Operations": [
|
||||
{
|
||||
"KeyCode": "CP-231522",
|
||||
"OperationSetCode": "SigntoolSign",
|
||||
"Parameters": [
|
||||
{
|
||||
"parameterName": "OpusName",
|
||||
"parameterValue": "Microsoft"
|
||||
},
|
||||
{
|
||||
"parameterName": "OpusInfo",
|
||||
"parameterValue": "http://www.microsoft.com"
|
||||
},
|
||||
{
|
||||
"parameterName": "FileDigest",
|
||||
"parameterValue": "/fd \"SHA256\""
|
||||
},
|
||||
{
|
||||
"parameterName": "PageHash",
|
||||
"parameterValue": "/NPH"
|
||||
},
|
||||
{
|
||||
"parameterName": "TimeStamp",
|
||||
"parameterValue": "/tr \"http://rfc3161.gtm.corp.microsoft.com/TSS/HttpTspServer\" /td sha256"
|
||||
}
|
||||
],
|
||||
"ToolName": "sign",
|
||||
"ToolVersion": "1.0"
|
||||
},
|
||||
{
|
||||
"KeyCode": "CP-231522",
|
||||
"OperationSetCode": "SigntoolVerify",
|
||||
"Parameters": [],
|
||||
"ToolName": "sign",
|
||||
"ToolVersion": "1.0"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
@ -64,7 +64,7 @@ parameters:
|
|||
|
||||
variables:
|
||||
MakeAppxPath: 'C:\Program Files (x86)\Windows Kits\10\bin\10.0.22621.0\x86\MakeAppx.exe'
|
||||
TerminalInternalPackageVersion: "0.0.7"
|
||||
TerminalInternalPackageVersion: "0.0.8"
|
||||
# If we are building a branch called "release-*", change the NuGet suffix
|
||||
# to "preview". If we don't do that, XES will set the suffix to "release1"
|
||||
# because it truncates the value after the first period.
|
||||
|
|
|
@ -144,7 +144,7 @@ jobs:
|
|||
inputs:
|
||||
TargetPattern: guardianGlob
|
||||
# See https://aka.ms/gdn-globs for how to do match patterns
|
||||
AnalyzeTargetGlob: $(Build.SourcesDirectory)\bin\**\*.dll;$(Build.SourcesDirectory)\bin\**\*.exe;-:file|**\Microsoft.UI.Xaml.dll;-:file|**\Microsoft.Toolkit.Win32.UI.XamlHost.dll;-:file|**\vcruntime*.dll;-:file|**\vcomp*.dll;-:file|**\vccorlib*.dll;-:file|**\vcamp*.dll;-:file|**\msvcp*.dll;-:file|**\concrt*.dll;-:file|**\TerminalThemeHelpers*.dll;-:file|**\cpprest*.dll
|
||||
AnalyzeTargetGlob: $(Build.SourcesDirectory)\bin\**\*.dll;$(Build.SourcesDirectory)\bin\**\*.exe;-:file|**\Microsoft.UI.Xaml.dll;-:file|**\Microsoft.Toolkit.Win32.UI.XamlHost.dll;-:file|**\vcruntime*.dll;-:file|**\vcomp*.dll;-:file|**\vccorlib*.dll;-:file|**\vcamp*.dll;-:file|**\msvcp*.dll;-:file|**\concrt*.dll;-:file|**\TerminalThemeHelpers*.dll
|
||||
continueOnError: true
|
||||
|
||||
# Set XES_SERIALPOSTBUILDREADY to run Security and Compliance task once per build
|
||||
|
|
|
@ -96,11 +96,6 @@ Try {
|
|||
Throw "Failed to find App.xbf (TerminalApp project) in resources.pri"
|
||||
}
|
||||
|
||||
If (($null -eq (Get-Item "$AppxPackageRootPath\cpprest142_2_10.dll" -EA:Ignore)) -And
|
||||
($null -eq (Get-Item "$AppxPackageRootPath\cpprest142_2_10d.dll" -EA:Ignore))) {
|
||||
Throw "Failed to find cpprest142_2_10.dll -- check the WAP packaging project"
|
||||
}
|
||||
|
||||
If (($null -eq (Get-Item "$AppxPackageRootPath\wtd.exe" -EA:Ignore)) -And
|
||||
($null -eq (Get-Item "$AppxPackageRootPath\wt.exe" -EA:Ignore))) {
|
||||
Throw "Failed to find wt.exe/wtd.exe -- check the WAP packaging project"
|
||||
|
|
|
@ -5,7 +5,6 @@
|
|||
<package id="Microsoft.Internal.PGO-Helpers.Cpp" version="0.2.34" targetFramework="native" />
|
||||
<package id="Microsoft.Taef" version="10.60.210621002" targetFramework="native" />
|
||||
<package id="Microsoft.Windows.CppWinRT" version="2.0.210825.3" targetFramework="native" />
|
||||
<package id="vcpkg-cpprestsdk" version="2.10.14" targetFramework="native" />
|
||||
<package id="Microsoft.VCRTForwarders.140" version="1.0.4" targetFramework="native" />
|
||||
<package id="Microsoft.Internal.Windows.Terminal.ThemeHelpers" version="0.6.220404001" targetFramework="native" />
|
||||
<package id="Microsoft.VisualStudio.Setup.Configuration.Native" version="2.3.2262" targetFramework="native" developmentDependency="true" />
|
||||
|
|
|
@ -197,26 +197,6 @@
|
|||
<!-- **END VC LIBS HACK** -->
|
||||
|
||||
|
||||
<!-- **BEGIN TERMINAL CONNECTION HACK** -->
|
||||
<!-- This is the same as the above VC libs hack, but for TerminalConnection.
|
||||
TerminalConnection depends on cpprest*.dll, and if we don't include it in
|
||||
the packaging output, we'll crash as soon as we try to load
|
||||
TerminalConnection.dll.
|
||||
|
||||
The Sample sln needs to do this manually - the real exe has a
|
||||
ProjectReference to TerminalConnection.vcxproj and can figure this out on
|
||||
its own. -->
|
||||
<ItemGroup>
|
||||
<_TerminalConnectionDlls Include="$(OpenConsoleCommonOutDir)\TerminalConnection\*.dll" />
|
||||
|
||||
<PackagingOutputs Include="@(_TerminalConnectionDlls)">
|
||||
<ProjectName>$(ProjectName)</ProjectName>
|
||||
<OutputGroup>BuiltProjectOutputGroup</OutputGroup>
|
||||
<TargetPath>%(Filename)%(Extension)</TargetPath>
|
||||
</PackagingOutputs>
|
||||
</ItemGroup>
|
||||
<!-- **END TERMINAL CONNECTION HACK** -->
|
||||
|
||||
<!-- Same thing again here, with WindowsTerminal.exe -->
|
||||
<ItemGroup>
|
||||
<_WindowsTerminalExe Include="$(OpenConsoleCommonOutDir)\WindowsTerminal\*.exe" />
|
||||
|
|
|
@ -1247,10 +1247,17 @@ namespace winrt::TerminalApp::implementation
|
|||
if (connectionType == TerminalConnection::AzureConnection::ConnectionType() &&
|
||||
TerminalConnection::AzureConnection::IsAzureConnectionAvailable())
|
||||
{
|
||||
// TODO GH#4661: Replace this with directly using the AzCon when our VT is better
|
||||
std::filesystem::path azBridgePath{ wil::GetModuleFileNameW<std::wstring>(nullptr) };
|
||||
azBridgePath.replace_filename(L"TerminalAzBridge.exe");
|
||||
connection = TerminalConnection::ConptyConnection();
|
||||
if constexpr (Feature_AzureConnectionInProc::IsEnabled())
|
||||
{
|
||||
connection = TerminalConnection::AzureConnection{};
|
||||
}
|
||||
else
|
||||
{
|
||||
connection = TerminalConnection::ConptyConnection{};
|
||||
}
|
||||
|
||||
auto valueSet = TerminalConnection::ConptyConnection::CreateSettings(azBridgePath.wstring(),
|
||||
L".",
|
||||
L"Azure",
|
||||
|
|
|
@ -3,8 +3,6 @@
|
|||
|
||||
#pragma once
|
||||
|
||||
#include "cpprest/json.h"
|
||||
|
||||
namespace Microsoft::Terminal::Azure
|
||||
{
|
||||
class AzureException : public std::runtime_error
|
||||
|
@ -12,14 +10,24 @@ namespace Microsoft::Terminal::Azure
|
|||
std::wstring _code;
|
||||
|
||||
public:
|
||||
static bool IsErrorPayload(const web::json::value& errorObject)
|
||||
static bool IsErrorPayload(const winrt::Windows::Data::Json::JsonObject& errorObject)
|
||||
{
|
||||
return errorObject.has_string_field(L"error");
|
||||
if (!errorObject.HasKey(L"error"))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (errorObject.GetNamedValue(L"error").ValueType() != winrt::Windows::Data::Json::JsonValueType::String)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
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())
|
||||
AzureException(const winrt::Windows::Data::Json::JsonObject& errorObject) :
|
||||
runtime_error(til::u16u8(errorObject.GetNamedString(L"error_description"))), // surface the human-readable description as .what()
|
||||
_code(errorObject.GetNamedString(L"error"))
|
||||
{
|
||||
}
|
||||
|
||||
|
|
|
@ -15,22 +15,24 @@
|
|||
#include "winrt/Windows.System.UserProfile.h"
|
||||
#include "../../types/inc/Utils.hpp"
|
||||
|
||||
#include "winrt/Windows.Web.Http.Filters.h"
|
||||
|
||||
using namespace ::Microsoft::Console;
|
||||
using namespace ::Microsoft::Terminal::Azure;
|
||||
|
||||
using namespace web;
|
||||
using namespace web::http;
|
||||
using namespace web::http::client;
|
||||
using namespace web::websockets::client;
|
||||
using namespace winrt::Windows::Security::Credentials;
|
||||
|
||||
static constexpr int CurrentCredentialVersion = 1;
|
||||
static constexpr auto PasswordVaultResourceName = L"Terminal";
|
||||
static constexpr auto HttpUserAgent = L"Terminal/0.0";
|
||||
static constexpr int CurrentCredentialVersion = 2;
|
||||
static constexpr std::wstring_view PasswordVaultResourceName = L"Terminal";
|
||||
static constexpr std::wstring_view HttpUserAgent = L"Mozilla/5.0 (Windows NT 10.0) Terminal/1.0";
|
||||
|
||||
static constexpr int USER_INPUT_COLOR = 93; // yellow - the color of something the user can type
|
||||
static constexpr int USER_INFO_COLOR = 97; // white - the color of clarifying information
|
||||
|
||||
using namespace winrt::Windows::Foundation;
|
||||
namespace WDJ = ::winrt::Windows::Data::Json;
|
||||
namespace WSS = ::winrt::Windows::Storage::Streams;
|
||||
namespace WWH = ::winrt::Windows::Web::Http;
|
||||
|
||||
static constexpr winrt::guid AzureConnectionType = { 0xd9fcfdfa, 0xa479, 0x412c, { 0x83, 0xb7, 0xc5, 0x64, 0xe, 0x61, 0xcd, 0x62 } };
|
||||
|
||||
static inline std::wstring _colorize(const unsigned int colorCode, const std::wstring_view text)
|
||||
|
@ -115,6 +117,8 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
|
|||
// - creates the output thread (where we will do the authentication and actually connect to Azure)
|
||||
void AzureConnection::Start()
|
||||
{
|
||||
_httpClient = winrt::Windows::Web::Http::HttpClient{};
|
||||
_httpClient.DefaultRequestHeaders().UserAgent().TryParseAdd(HttpUserAgent);
|
||||
// Create our own output handling thread
|
||||
// Each connection needs to make sure to drain the output from its backing host.
|
||||
_hOutputThread.reset(CreateThread(
|
||||
|
@ -126,7 +130,7 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
|
|||
{
|
||||
return pInstance->_OutputThread();
|
||||
}
|
||||
return gsl::narrow_cast<DWORD>(E_INVALIDARG);
|
||||
return gsl::narrow<DWORD>(E_INVALIDARG);
|
||||
},
|
||||
this,
|
||||
0,
|
||||
|
@ -183,12 +187,8 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
|
|||
|
||||
if (_state == AzureState::TermConnected)
|
||||
{
|
||||
// If we're connected, we don't need to do any fun input shenanigans.
|
||||
websocket_outgoing_message msg;
|
||||
const auto str = winrt::to_string(data);
|
||||
msg.set_utf8_message(str);
|
||||
|
||||
_cloudShellSocket.send(msg).get();
|
||||
auto buff{ winrt::to_string(data) };
|
||||
WinHttpWebSocketSend(_webSocket.get(), WINHTTP_WEB_SOCKET_UTF8_MESSAGE_BUFFER_TYPE, buff.data(), gsl::narrow<DWORD>(buff.size()));
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -238,16 +238,17 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
|
|||
}
|
||||
else // We only transition to Connected when we've established the websocket.
|
||||
{
|
||||
// Initialize client
|
||||
http_client terminalClient(_cloudShellUri);
|
||||
auto uri{ fmt::format(L"{}terminals/{}/size?cols={}&rows={}&version=2019-01-01", _cloudShellUri, _terminalID, columns, rows) };
|
||||
|
||||
// Initialize the request
|
||||
http_request terminalRequest(L"POST");
|
||||
terminalRequest.set_request_uri(fmt::format(L"terminals/{}/size?cols={}&rows={}&version=2019-01-01", _terminalID, columns, rows));
|
||||
terminalRequest.set_body(json::value::null());
|
||||
WWH::HttpStringContent content{
|
||||
L"",
|
||||
WSS::UnicodeEncoding::Utf8,
|
||||
// LOAD-BEARING. the API returns "'content-type' should be 'application/json' or 'multipart/form-data'"
|
||||
L"application/json"
|
||||
};
|
||||
|
||||
// Send the request (don't care about the response)
|
||||
(void)_SendAuthenticatedRequestReturningJson(terminalClient, terminalRequest);
|
||||
std::ignore = _SendRequestReturningJson(uri, content);
|
||||
}
|
||||
}
|
||||
CATCH_LOG();
|
||||
|
@ -264,7 +265,10 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
|
|||
if (_state == AzureState::TermConnected)
|
||||
{
|
||||
// Close the websocket connection
|
||||
_cloudShellSocket.close();
|
||||
std::ignore = WinHttpWebSocketClose(_webSocket.get(), WINHTTP_WEB_SOCKET_SUCCESS_CLOSE_STATUS, nullptr, 0); // throw away the error
|
||||
_webSocket.reset();
|
||||
_socketConnectionHandle.reset();
|
||||
_socketSessionHandle.reset();
|
||||
}
|
||||
|
||||
if (_hOutputThread)
|
||||
|
@ -287,44 +291,46 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
|
|||
// - tenant - the unparsed tenant
|
||||
// Return value:
|
||||
// - a tuple containing the ID and display name of the tenant.
|
||||
static Tenant _crackTenant(const json::value& jsonTenant)
|
||||
static Tenant _crackTenant(const WDJ::IJsonValue& value)
|
||||
{
|
||||
auto jsonTenant{ value.GetObjectW() };
|
||||
|
||||
Tenant tenant{};
|
||||
if (jsonTenant.has_string_field(L"tenantID"))
|
||||
if (jsonTenant.HasKey(L"tenantID"))
|
||||
{
|
||||
// for compatibility with version 1 credentials
|
||||
tenant.ID = jsonTenant.at(L"tenantID").as_string();
|
||||
tenant.ID = jsonTenant.GetNamedString(L"tenantID");
|
||||
}
|
||||
else
|
||||
{
|
||||
// This one comes in off the wire
|
||||
tenant.ID = jsonTenant.at(L"tenantId").as_string();
|
||||
tenant.ID = jsonTenant.GetNamedString(L"tenantId");
|
||||
}
|
||||
|
||||
if (jsonTenant.has_string_field(L"displayName"))
|
||||
if (jsonTenant.HasKey(L"displayName"))
|
||||
{
|
||||
tenant.DisplayName = jsonTenant.at(L"displayName").as_string();
|
||||
tenant.DisplayName = jsonTenant.GetNamedString(L"displayName");
|
||||
}
|
||||
|
||||
if (jsonTenant.has_string_field(L"defaultDomain"))
|
||||
if (jsonTenant.HasKey(L"defaultDomain"))
|
||||
{
|
||||
tenant.DefaultDomain = jsonTenant.at(L"defaultDomain").as_string();
|
||||
tenant.DefaultDomain = jsonTenant.GetNamedString(L"defaultDomain");
|
||||
}
|
||||
|
||||
return tenant;
|
||||
}
|
||||
|
||||
static void _packTenant(json::value& jsonTenant, const Tenant& tenant)
|
||||
static void _packTenant(const WDJ::JsonObject& jsonTenant, const Tenant& tenant)
|
||||
{
|
||||
jsonTenant[L"tenantId"] = json::value::string(tenant.ID);
|
||||
jsonTenant.SetNamedValue(L"tenantId", WDJ::JsonValue::CreateStringValue(tenant.ID));
|
||||
if (tenant.DisplayName.has_value())
|
||||
{
|
||||
jsonTenant[L"displayName"] = json::value::string(*tenant.DisplayName);
|
||||
jsonTenant.SetNamedValue(L"displayName", WDJ::JsonValue::CreateStringValue(*tenant.DisplayName));
|
||||
}
|
||||
|
||||
if (tenant.DefaultDomain.has_value())
|
||||
{
|
||||
jsonTenant[L"defaultDomain"] = json::value::string(*tenant.DefaultDomain);
|
||||
jsonTenant.SetNamedValue(L"defaultDomain", WDJ::JsonValue::CreateStringValue(*tenant.DefaultDomain));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -383,35 +389,42 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
|
|||
case AzureState::TermConnected:
|
||||
{
|
||||
_transitionToState(ConnectionState::Connected);
|
||||
|
||||
while (true)
|
||||
{
|
||||
// Read from websocket
|
||||
pplx::task<websocket_incoming_message> msgT;
|
||||
try
|
||||
WINHTTP_WEB_SOCKET_BUFFER_TYPE bufferType{};
|
||||
DWORD read{};
|
||||
THROW_IF_WIN32_ERROR(WinHttpWebSocketReceive(_webSocket.get(), _buffer.data(), gsl::narrow<DWORD>(_buffer.size()), &read, &bufferType));
|
||||
|
||||
switch (bufferType)
|
||||
{
|
||||
msgT = _cloudShellSocket.receive();
|
||||
msgT.wait();
|
||||
case WINHTTP_WEB_SOCKET_UTF8_FRAGMENT_BUFFER_TYPE:
|
||||
case WINHTTP_WEB_SOCKET_UTF8_MESSAGE_BUFFER_TYPE:
|
||||
{
|
||||
const auto result{ til::u8u16(std::string_view{ _buffer.data(), read }, _u16Str, _u8State) };
|
||||
if (FAILED(result))
|
||||
{
|
||||
// EXIT POINT
|
||||
_transitionToState(ConnectionState::Failed);
|
||||
return gsl::narrow<DWORD>(result);
|
||||
}
|
||||
|
||||
if (_u16Str.empty())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// Pass the output to our registered event handlers
|
||||
_TerminalOutputHandlers(_u16Str);
|
||||
break;
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
// Websocket has been closed; consider it a graceful exit?
|
||||
// This should result in our termination.
|
||||
case WINHTTP_WEB_SOCKET_CLOSE_BUFFER_TYPE:
|
||||
// EXIT POINT
|
||||
if (_transitionToState(ConnectionState::Closed))
|
||||
{
|
||||
// End the output thread.
|
||||
return S_FALSE;
|
||||
}
|
||||
}
|
||||
|
||||
auto msg = msgT.get();
|
||||
auto msgStringTask = msg.extract_string();
|
||||
auto msgString = msgStringTask.get();
|
||||
|
||||
// Convert to hstring
|
||||
const auto hstr = winrt::to_hstring(msgString);
|
||||
|
||||
// Pass the output to our registered event handlers
|
||||
_TerminalOutputHandlers(hstr);
|
||||
}
|
||||
return S_OK;
|
||||
}
|
||||
|
@ -449,25 +462,29 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
|
|||
_tenantList.clear();
|
||||
for (const auto& entry : credList)
|
||||
{
|
||||
auto nameJson = json::value::parse(entry.UserName().c_str());
|
||||
std::optional<int> credentialVersion;
|
||||
if (nameJson.has_integer_field(U("ver")))
|
||||
try
|
||||
{
|
||||
credentialVersion = nameJson.at(U("ver")).as_integer();
|
||||
auto nameJson = WDJ::JsonObject::Parse(entry.UserName());
|
||||
std::optional<int> credentialVersion;
|
||||
if (nameJson.HasKey(L"ver"))
|
||||
{
|
||||
credentialVersion = static_cast<int>(nameJson.GetNamedNumber(L"ver"));
|
||||
}
|
||||
|
||||
if (!credentialVersion.has_value() || credentialVersion.value() != CurrentCredentialVersion)
|
||||
{
|
||||
// ignore credentials that aren't from the latest credential revision
|
||||
vault.Remove(entry);
|
||||
oldVersionEncountered = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
auto newTenant{ _tenantList.emplace_back(_crackTenant(nameJson)) };
|
||||
|
||||
_WriteStringWithNewline(_formatTenant(numTenants, newTenant));
|
||||
numTenants++;
|
||||
}
|
||||
|
||||
if (!credentialVersion.has_value() || credentialVersion.value() != CurrentCredentialVersion)
|
||||
{
|
||||
// ignore credentials that aren't from the latest credential revision
|
||||
vault.Remove(entry);
|
||||
oldVersionEncountered = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
auto newTenant{ _tenantList.emplace_back(_crackTenant(nameJson)) };
|
||||
|
||||
_WriteStringWithNewline(_formatTenant(numTenants, newTenant));
|
||||
numTenants++;
|
||||
CATCH_LOG();
|
||||
}
|
||||
|
||||
if (!numTenants)
|
||||
|
@ -533,11 +550,11 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
|
|||
// User wants to login with one of the saved connection settings
|
||||
auto desiredCredential = credList.GetAt(selectedTenant);
|
||||
desiredCredential.RetrievePassword();
|
||||
auto passWordJson = json::value::parse(desiredCredential.Password().c_str());
|
||||
auto passWordJson = WDJ::JsonObject::Parse(desiredCredential.Password());
|
||||
_currentTenant = til::at(_tenantList, selectedTenant); // we already unpacked the name info, so we should just use it
|
||||
_accessToken = passWordJson.at(L"accessToken").as_string();
|
||||
_refreshToken = passWordJson.at(L"refreshToken").as_string();
|
||||
_expiry = std::stoi(passWordJson.at(L"expiry").as_string());
|
||||
_setAccessToken(passWordJson.GetNamedString(L"accessToken"));
|
||||
_refreshToken = passWordJson.GetNamedString(L"refreshToken");
|
||||
_expiry = std::stoi(winrt::to_string(passWordJson.GetNamedString(L"expiry")));
|
||||
|
||||
const auto t1 = std::chrono::system_clock::now();
|
||||
const auto timeNow = std::chrono::duration_cast<std::chrono::seconds>(t1.time_since_epoch()).count();
|
||||
|
@ -577,17 +594,17 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
|
|||
const auto deviceCodeResponse = _GetDeviceCode();
|
||||
|
||||
// Print the message and store the device code, polling interval and expiry
|
||||
const auto message = winrt::to_hstring(deviceCodeResponse.at(L"message").as_string().c_str());
|
||||
const auto message{ deviceCodeResponse.GetNamedString(L"message") };
|
||||
_WriteStringWithNewline(message);
|
||||
_WriteStringWithNewline(RS_(L"AzureCodeExpiry"));
|
||||
const auto devCode = deviceCodeResponse.at(L"device_code").as_string();
|
||||
const auto pollInterval = std::stoi(deviceCodeResponse.at(L"interval").as_string());
|
||||
const auto expiresIn = std::stoi(deviceCodeResponse.at(L"expires_in").as_string());
|
||||
const auto devCode = deviceCodeResponse.GetNamedString(L"device_code");
|
||||
const auto pollInterval = std::stoi(winrt::to_string(deviceCodeResponse.GetNamedString(L"interval")));
|
||||
const auto expiresIn = std::stoi(winrt::to_string(deviceCodeResponse.GetNamedString(L"expires_in")));
|
||||
|
||||
// Wait for user authentication and obtain the access/refresh tokens
|
||||
auto authenticatedResponse = _WaitForUser(devCode, pollInterval, expiresIn);
|
||||
_accessToken = authenticatedResponse.at(L"access_token").as_string();
|
||||
_refreshToken = authenticatedResponse.at(L"refresh_token").as_string();
|
||||
_setAccessToken(authenticatedResponse.GetNamedString(L"access_token"));
|
||||
_refreshToken = authenticatedResponse.GetNamedString(L"refresh_token");
|
||||
|
||||
// Get the tenants and the required tenant id
|
||||
_PopulateTenantList();
|
||||
|
@ -696,19 +713,18 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
|
|||
// - Helper function to parse the preferred shell type from user settings returned by cloud console API.
|
||||
// We need this function because the field might be missing in the settings
|
||||
// created with old versions of cloud console API.
|
||||
std::optional<utility::string_t> AzureConnection::_ParsePreferredShellType(const web::json::value& settingsResponse)
|
||||
winrt::hstring AzureConnection::_ParsePreferredShellType(const WDJ::JsonObject& settingsResponse)
|
||||
{
|
||||
if (settingsResponse.has_object_field(L"properties"))
|
||||
if (settingsResponse.HasKey(L"properties"))
|
||||
{
|
||||
const auto userSettings = settingsResponse.at(L"properties");
|
||||
if (userSettings.has_string_field(L"preferredShellType"))
|
||||
const auto userSettings = settingsResponse.GetNamedObject(L"properties");
|
||||
if (userSettings.HasKey(L"preferredShellType"))
|
||||
{
|
||||
const auto preferredShellTypeValue = userSettings.at(L"preferredShellType");
|
||||
return preferredShellTypeValue.as_string();
|
||||
return userSettings.GetNamedString(L"preferredShellType");
|
||||
}
|
||||
}
|
||||
|
||||
return std::nullopt;
|
||||
return L"pwsh";
|
||||
}
|
||||
|
||||
// Method description:
|
||||
|
@ -717,7 +733,7 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
|
|||
{
|
||||
// Get user's cloud shell settings
|
||||
const auto settingsResponse = _GetCloudShellUserSettings();
|
||||
if (settingsResponse.has_field(L"error"))
|
||||
if (settingsResponse.HasKey(L"error"))
|
||||
{
|
||||
_WriteStringWithNewline(RS_(L"AzureNoCloudAccount"));
|
||||
_transitionToState(ConnectionState::Failed);
|
||||
|
@ -732,12 +748,36 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
|
|||
// Request for a terminal for said cloud shell
|
||||
const auto shellType = _ParsePreferredShellType(settingsResponse);
|
||||
_WriteStringWithNewline(RS_(L"AzureRequestingTerminal"));
|
||||
const auto socketUri = _GetTerminal(shellType.value_or(L"pwsh"));
|
||||
const auto socketUri = _GetTerminal(shellType);
|
||||
_TerminalOutputHandlers(L"\r\n");
|
||||
|
||||
// Step 8: connecting to said terminal
|
||||
const auto connReqTask = _cloudShellSocket.connect(socketUri);
|
||||
connReqTask.wait();
|
||||
//// Step 8: connecting to said terminal
|
||||
{
|
||||
wil::unique_winhttp_hinternet sessionHandle, connectionHandle, requestHandle, socketHandle;
|
||||
Uri parsedUri{ socketUri };
|
||||
sessionHandle.reset(WinHttpOpen(HttpUserAgent.data(), WINHTTP_ACCESS_TYPE_AUTOMATIC_PROXY, nullptr, nullptr, 0));
|
||||
THROW_LAST_ERROR_IF(!sessionHandle);
|
||||
|
||||
connectionHandle.reset(WinHttpConnect(sessionHandle.get(), parsedUri.Host().c_str(), INTERNET_DEFAULT_HTTPS_PORT, 0));
|
||||
THROW_LAST_ERROR_IF(!connectionHandle);
|
||||
|
||||
requestHandle.reset(WinHttpOpenRequest(connectionHandle.get(), L"GET", parsedUri.Path().c_str(), nullptr, nullptr, nullptr, WINHTTP_FLAG_SECURE));
|
||||
THROW_LAST_ERROR_IF(!requestHandle);
|
||||
|
||||
THROW_IF_WIN32_BOOL_FALSE(WinHttpSetOption(requestHandle.get(), WINHTTP_OPTION_UPGRADE_TO_WEB_SOCKET, nullptr, 0));
|
||||
#pragma warning(suppress : 26477) // WINHTTP_NO_ADDITIONAL_HEADERS expands to NULL rather than nullptr (who would have thought?)
|
||||
THROW_IF_WIN32_BOOL_FALSE(WinHttpSendRequest(requestHandle.get(), WINHTTP_NO_ADDITIONAL_HEADERS, 0, nullptr, 0, 0, 0));
|
||||
THROW_IF_WIN32_BOOL_FALSE(WinHttpReceiveResponse(requestHandle.get(), nullptr));
|
||||
|
||||
socketHandle.reset(WinHttpWebSocketCompleteUpgrade(requestHandle.get(), 0));
|
||||
THROW_LAST_ERROR_IF(!socketHandle);
|
||||
|
||||
requestHandle.reset(); // We no longer need the request once we've upgraded it.
|
||||
// We have to keep the socket session and connection handles.
|
||||
_socketSessionHandle = std::move(sessionHandle);
|
||||
_socketConnectionHandle = std::move(connectionHandle);
|
||||
_webSocket = std::move(socketHandle);
|
||||
}
|
||||
|
||||
_state = AzureState::TermConnected;
|
||||
|
||||
|
@ -752,58 +792,52 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
|
|||
// Method description:
|
||||
// - helper function to send requests with default headers and extract responses as json values
|
||||
// Arguments:
|
||||
// - a http_client
|
||||
// - a http_request for the client to send
|
||||
// - the URI
|
||||
// - optional body content
|
||||
// - an optional HTTP method (defaults to POST if content is present, GET otherwise)
|
||||
// Return value:
|
||||
// - the response from the server as a json value
|
||||
json::value AzureConnection::_SendRequestReturningJson(http_client& theClient, http_request theRequest)
|
||||
WDJ::JsonObject AzureConnection::_SendRequestReturningJson(std::wstring_view uri, const WWH::IHttpContent& content, WWH::HttpMethod method)
|
||||
{
|
||||
auto& headers{ theRequest.headers() };
|
||||
headers.add(L"User-Agent", HttpUserAgent);
|
||||
headers.add(L"Accept", L"application/json");
|
||||
if (!method)
|
||||
{
|
||||
method = content == nullptr ? WWH::HttpMethod::Get() : WWH::HttpMethod::Post();
|
||||
}
|
||||
|
||||
json::value jsonResult;
|
||||
const auto responseTask = theClient.request(theRequest);
|
||||
responseTask.wait();
|
||||
const auto response = responseTask.get();
|
||||
const auto responseJsonTask = response.extract_json();
|
||||
responseJsonTask.wait();
|
||||
jsonResult = responseJsonTask.get();
|
||||
WWH::HttpRequestMessage request{ method, Uri{ uri } };
|
||||
request.Content(content);
|
||||
|
||||
auto headers{ request.Headers() };
|
||||
headers.Accept().TryParseAdd(L"application/json");
|
||||
|
||||
const auto response{ _httpClient.SendRequestAsync(request).get() };
|
||||
const auto string{ response.Content().ReadAsStringAsync().get() };
|
||||
const auto jsonResult{ WDJ::JsonObject::Parse(string) };
|
||||
|
||||
THROW_IF_AZURE_ERROR(jsonResult);
|
||||
return jsonResult;
|
||||
}
|
||||
|
||||
// Method description:
|
||||
// - helper function to send _authenticated_ requests with json bodies whose responses are expected
|
||||
// to be json. builds on _SendRequestReturningJson.
|
||||
// Arguments:
|
||||
// - the http_request
|
||||
json::value AzureConnection::_SendAuthenticatedRequestReturningJson(http_client& theClient, http_request theRequest)
|
||||
void AzureConnection::_setAccessToken(std::wstring_view accessToken)
|
||||
{
|
||||
auto& headers{ theRequest.headers() };
|
||||
headers.add(L"Authorization", L"Bearer " + _accessToken);
|
||||
|
||||
return _SendRequestReturningJson(theClient, std::move(theRequest));
|
||||
_accessToken = accessToken;
|
||||
_httpClient.DefaultRequestHeaders().Authorization(WWH::Headers::HttpCredentialsHeaderValue{ L"Bearer", _accessToken });
|
||||
}
|
||||
|
||||
// Method description:
|
||||
// - helper function to start the device code flow
|
||||
// Return value:
|
||||
// - the response to the device code flow initiation
|
||||
json::value AzureConnection::_GetDeviceCode()
|
||||
WDJ::JsonObject AzureConnection::_GetDeviceCode()
|
||||
{
|
||||
// Initialize the client
|
||||
http_client loginClient(_loginUri);
|
||||
|
||||
// Initialize the request
|
||||
http_request commonRequest(L"POST");
|
||||
commonRequest.set_request_uri(L"common/oauth2/devicecode");
|
||||
const auto body{ fmt::format(L"client_id={}&resource={}", AzureClientID, _wantedResource) };
|
||||
commonRequest.set_body(body.c_str(), L"application/x-www-form-urlencoded");
|
||||
|
||||
// Send the request and receive the response as a json value
|
||||
return _SendRequestReturningJson(loginClient, commonRequest);
|
||||
auto uri{ fmt::format(L"{}common/oauth2/devicecode", _loginUri) };
|
||||
WWH::HttpFormUrlEncodedContent content{
|
||||
std::unordered_map<winrt::hstring, winrt::hstring>{
|
||||
{ winrt::hstring{ L"client_id" }, winrt::hstring{ AzureClientID } },
|
||||
{ winrt::hstring{ L"resource" }, winrt::hstring{ _wantedResource } },
|
||||
}
|
||||
};
|
||||
return _SendRequestReturningJson(uri, content);
|
||||
}
|
||||
|
||||
// Method description:
|
||||
|
@ -815,14 +849,17 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
|
|||
// Return value:
|
||||
// - if authentication is done successfully, then return the response from the server
|
||||
// - else, throw an exception
|
||||
json::value AzureConnection::_WaitForUser(const utility::string_t deviceCode, int pollInterval, int expiresIn)
|
||||
WDJ::JsonObject AzureConnection::_WaitForUser(const winrt::hstring& deviceCode, int pollInterval, int expiresIn)
|
||||
{
|
||||
// Initialize the client
|
||||
http_client pollingClient(_loginUri);
|
||||
|
||||
// Continuously send a poll request until the user authenticates
|
||||
const auto body{ fmt::format(L"grant_type=device_code&resource={}&client_id={}&code={}", _wantedResource, AzureClientID, deviceCode) };
|
||||
const auto requestUri = L"common/oauth2/token";
|
||||
auto uri{ fmt::format(L"{}common/oauth2/token", _loginUri) };
|
||||
WWH::HttpFormUrlEncodedContent content{
|
||||
std::unordered_map<winrt::hstring, winrt::hstring>{
|
||||
{ winrt::hstring{ L"grant_type" }, winrt::hstring{ L"device_code" } },
|
||||
{ winrt::hstring{ L"client_id" }, winrt::hstring{ AzureClientID } },
|
||||
{ winrt::hstring{ L"resource" }, winrt::hstring{ _wantedResource } },
|
||||
{ winrt::hstring{ L"code" }, deviceCode },
|
||||
}
|
||||
};
|
||||
|
||||
// use a steady clock here so it's not impacted by local time discontinuities
|
||||
const auto tokenExpiry{ std::chrono::steady_clock::now() + std::chrono::seconds(expiresIn) };
|
||||
|
@ -837,14 +874,11 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
|
|||
break;
|
||||
}
|
||||
|
||||
http_request pollRequest(L"POST");
|
||||
pollRequest.set_request_uri(requestUri);
|
||||
pollRequest.set_body(body.c_str(), L"application/x-www-form-urlencoded");
|
||||
|
||||
try
|
||||
{
|
||||
auto response{ _SendRequestReturningJson(pollingClient, pollRequest) };
|
||||
auto response = _SendRequestReturningJson(uri, content);
|
||||
_WriteStringWithNewline(RS_(L"AzureSuccessfullyAuthenticated"));
|
||||
|
||||
// Got a valid response: we're done
|
||||
return response;
|
||||
}
|
||||
|
@ -859,7 +893,7 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
|
|||
} // uncaught exceptions bubble up to the caller
|
||||
}
|
||||
|
||||
return json::value::null();
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Method description:
|
||||
|
@ -868,16 +902,11 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
|
|||
// - the response which contains a list of the user's Azure tenants
|
||||
void AzureConnection::_PopulateTenantList()
|
||||
{
|
||||
// Initialize the client
|
||||
http_client tenantClient(_resourceUri);
|
||||
|
||||
// Initialize the request
|
||||
http_request tenantRequest(L"GET");
|
||||
tenantRequest.set_request_uri(L"tenants?api-version=2020-01-01");
|
||||
auto uri{ fmt::format(L"{}tenants?api-version=2020-01-01", _resourceUri) };
|
||||
|
||||
// Send the request and return the response as a json value
|
||||
auto tenantResponse{ _SendAuthenticatedRequestReturningJson(tenantClient, tenantRequest) };
|
||||
auto tenantList{ tenantResponse.at(L"value").as_array() };
|
||||
auto tenantResponse{ _SendRequestReturningJson(uri, nullptr) };
|
||||
auto tenantList{ tenantResponse.GetNamedArray(L"value") };
|
||||
|
||||
_tenantList.clear();
|
||||
std::transform(tenantList.begin(), tenantList.end(), std::back_inserter(_tenantList), _crackTenant);
|
||||
|
@ -889,82 +918,73 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
|
|||
// - the response with the new tokens
|
||||
void AzureConnection::_RefreshTokens()
|
||||
{
|
||||
// Initialize the client
|
||||
http_client refreshClient(_loginUri);
|
||||
|
||||
// Initialize the request
|
||||
http_request refreshRequest(L"POST");
|
||||
refreshRequest.set_request_uri(_currentTenant->ID + L"/oauth2/token");
|
||||
const auto body{ fmt::format(L"client_id={}&resource={}&grant_type=refresh_token&refresh_token={}", AzureClientID, _wantedResource, _refreshToken) };
|
||||
refreshRequest.set_body(body.c_str(), L"application/x-www-form-urlencoded");
|
||||
auto uri{ fmt::format(L"{}{}/oauth2/token", _loginUri, _currentTenant->ID) };
|
||||
WWH::HttpFormUrlEncodedContent content{
|
||||
std::unordered_map<winrt::hstring, winrt::hstring>{
|
||||
{ winrt::hstring{ L"grant_type" }, winrt::hstring{ L"refresh_token" } },
|
||||
{ winrt::hstring{ L"client_id" }, winrt::hstring{ AzureClientID } },
|
||||
{ winrt::hstring{ L"resource" }, winrt::hstring{ _wantedResource } },
|
||||
{ winrt::hstring{ L"refresh_token" }, winrt::hstring{ _refreshToken } },
|
||||
}
|
||||
};
|
||||
|
||||
// Send the request and return the response as a json value
|
||||
auto refreshResponse{ _SendRequestReturningJson(refreshClient, refreshRequest) };
|
||||
_accessToken = refreshResponse.at(L"access_token").as_string();
|
||||
_refreshToken = refreshResponse.at(L"refresh_token").as_string();
|
||||
_expiry = std::stoi(refreshResponse.at(L"expires_on").as_string());
|
||||
auto refreshResponse{ _SendRequestReturningJson(uri, content) };
|
||||
_setAccessToken(refreshResponse.GetNamedString(L"access_token"));
|
||||
_refreshToken = refreshResponse.GetNamedString(L"refresh_token");
|
||||
_expiry = std::stoi(winrt::to_string(refreshResponse.GetNamedString(L"expires_on")));
|
||||
}
|
||||
|
||||
// Method description:
|
||||
// - helper function to get the user's cloud shell settings
|
||||
// Return value:
|
||||
// - the user's cloud shell settings
|
||||
json::value AzureConnection::_GetCloudShellUserSettings()
|
||||
WDJ::JsonObject AzureConnection::_GetCloudShellUserSettings()
|
||||
{
|
||||
// Initialize client
|
||||
http_client settingsClient(_resourceUri);
|
||||
|
||||
// Initialize request
|
||||
http_request settingsRequest(L"GET");
|
||||
settingsRequest.set_request_uri(L"providers/Microsoft.Portal/userSettings/cloudconsole?api-version=2018-10-01");
|
||||
|
||||
return _SendAuthenticatedRequestReturningJson(settingsClient, settingsRequest);
|
||||
auto uri{ fmt::format(L"{}providers/Microsoft.Portal/userSettings/cloudconsole?api-version=2020-04-01-preview", _resourceUri) };
|
||||
return _SendRequestReturningJson(uri, nullptr);
|
||||
}
|
||||
|
||||
// Method description:
|
||||
// - helper function to request for a cloud shell
|
||||
// Return value:
|
||||
// - the uri for the cloud shell
|
||||
utility::string_t AzureConnection::_GetCloudShell()
|
||||
winrt::hstring AzureConnection::_GetCloudShell()
|
||||
{
|
||||
// Initialize client
|
||||
http_client cloudShellClient(_resourceUri);
|
||||
auto uri{ fmt::format(L"{}providers/Microsoft.Portal/consoles/default?api-version=2020-04-01-preview", _resourceUri) };
|
||||
|
||||
// Initialize request
|
||||
http_request shellRequest(L"PUT");
|
||||
shellRequest.set_request_uri(L"providers/Microsoft.Portal/consoles/default?api-version=2018-10-01");
|
||||
// { "properties": { "osType": "linux" } }
|
||||
auto body = json::value::object({ { U("properties"), json::value::object({ { U("osType"), json::value::string(U("linux")) } }) } });
|
||||
shellRequest.set_body(body);
|
||||
WWH::HttpStringContent content{
|
||||
LR"-({"properties": {"osType": "linux"}})-",
|
||||
WSS::UnicodeEncoding::Utf8,
|
||||
L"application/json"
|
||||
};
|
||||
|
||||
// Send the request and get the response as a json value
|
||||
const auto cloudShell = _SendAuthenticatedRequestReturningJson(cloudShellClient, shellRequest);
|
||||
const auto cloudShell = _SendRequestReturningJson(uri, content, WWH::HttpMethod::Put());
|
||||
|
||||
// Return the uri
|
||||
return cloudShell.at(L"properties").at(L"uri").as_string() + L"/";
|
||||
return winrt::hstring{ std::wstring{ cloudShell.GetNamedObject(L"properties").GetNamedString(L"uri") } + L"/" };
|
||||
}
|
||||
|
||||
// Method description:
|
||||
// - helper function to request for a terminal
|
||||
// Return value:
|
||||
// - the uri for the terminal
|
||||
utility::string_t AzureConnection::_GetTerminal(utility::string_t shellType)
|
||||
winrt::hstring AzureConnection::_GetTerminal(const winrt::hstring& shellType)
|
||||
{
|
||||
// Initialize client
|
||||
http_client terminalClient(_cloudShellUri);
|
||||
auto uri{ fmt::format(L"{}terminals?cols={}&rows={}&version=2019-01-01&shell={}", _cloudShellUri, _initialCols, _initialRows, shellType) };
|
||||
|
||||
// Initialize the request
|
||||
http_request terminalRequest(L"POST");
|
||||
terminalRequest.set_request_uri(fmt::format(L"terminals?cols={}&rows={}&version=2019-01-01&shell={}", _initialCols, _initialRows, shellType));
|
||||
// LOAD-BEARING. the API returns "'content-type' should be 'application/json' or 'multipart/form-data'"
|
||||
terminalRequest.set_body(json::value::null());
|
||||
WWH::HttpStringContent content{
|
||||
L"",
|
||||
WSS::UnicodeEncoding::Utf8,
|
||||
// LOAD-BEARING. the API returns "'content-type' should be 'application/json' or 'multipart/form-data'"
|
||||
L"application/json"
|
||||
};
|
||||
|
||||
// Send the request and get the response as a json value
|
||||
const auto terminalResponse = _SendAuthenticatedRequestReturningJson(terminalClient, terminalRequest);
|
||||
_terminalID = terminalResponse.at(L"id").as_string();
|
||||
const auto terminalResponse = _SendRequestReturningJson(uri, content);
|
||||
_terminalID = terminalResponse.GetNamedString(L"id");
|
||||
|
||||
// Return the uri
|
||||
return terminalResponse.at(L"socketUri").as_string();
|
||||
return terminalResponse.GetNamedString(L"socketUri");
|
||||
}
|
||||
|
||||
// Method description:
|
||||
|
@ -972,16 +992,17 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
|
|||
// - we store the display name, tenant ID, access/refresh tokens, and token expiry
|
||||
void AzureConnection::_StoreCredential()
|
||||
{
|
||||
json::value userName;
|
||||
userName[U("ver")] = CurrentCredentialVersion;
|
||||
WDJ::JsonObject userName;
|
||||
userName.SetNamedValue(L"ver", WDJ::JsonValue::CreateNumberValue(CurrentCredentialVersion));
|
||||
_packTenant(userName, *_currentTenant);
|
||||
json::value passWord;
|
||||
passWord[U("accessToken")] = json::value::string(_accessToken);
|
||||
passWord[U("refreshToken")] = json::value::string(_refreshToken);
|
||||
passWord[U("expiry")] = json::value::string(std::to_wstring(_expiry));
|
||||
|
||||
WDJ::JsonObject passWord;
|
||||
passWord.SetNamedValue(L"accessToken", WDJ::JsonValue::CreateStringValue(_accessToken));
|
||||
passWord.SetNamedValue(L"refreshToken", WDJ::JsonValue::CreateStringValue(_refreshToken));
|
||||
passWord.SetNamedValue(L"expiry", WDJ::JsonValue::CreateStringValue(std::to_wstring(_expiry)));
|
||||
|
||||
PasswordVault vault;
|
||||
PasswordCredential newCredential{ PasswordVaultResourceName, userName.serialize(), passWord.serialize() };
|
||||
PasswordCredential newCredential{ PasswordVaultResourceName, userName.Stringify(), passWord.Stringify() };
|
||||
vault.Add(newCredential);
|
||||
}
|
||||
|
||||
|
|
|
@ -5,9 +5,6 @@
|
|||
|
||||
#include "AzureConnection.g.h"
|
||||
|
||||
#include <cpprest/http_client.h>
|
||||
#include <cpprest/http_listener.h>
|
||||
#include <cpprest/ws_client.h>
|
||||
#include <mutex>
|
||||
#include <condition_variable>
|
||||
|
||||
|
@ -56,30 +53,30 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
|
|||
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/") };
|
||||
static constexpr std::wstring_view _loginUri{ L"https://login.microsoftonline.com/" };
|
||||
static constexpr std::wstring_view _resourceUri{ L"https://management.azure.com/" };
|
||||
static constexpr std::wstring_view _wantedResource{ L"https://management.core.windows.net/" };
|
||||
const int _expireLimit{ 2700 };
|
||||
utility::string_t _accessToken;
|
||||
utility::string_t _refreshToken;
|
||||
winrt::hstring _accessToken;
|
||||
winrt::hstring _refreshToken;
|
||||
int _expiry{ 0 };
|
||||
utility::string_t _cloudShellUri;
|
||||
utility::string_t _terminalID;
|
||||
winrt::hstring _cloudShellUri;
|
||||
winrt::hstring _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);
|
||||
winrt::Windows::Data::Json::JsonObject _SendRequestReturningJson(std::wstring_view uri, const winrt::Windows::Web::Http::IHttpContent& content = nullptr, winrt::Windows::Web::Http::HttpMethod method = nullptr);
|
||||
void _setAccessToken(std::wstring_view accessToken);
|
||||
winrt::Windows::Data::Json::JsonObject _GetDeviceCode();
|
||||
winrt::Windows::Data::Json::JsonObject _WaitForUser(const winrt::hstring& 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);
|
||||
winrt::Windows::Data::Json::JsonObject _GetCloudShellUserSettings();
|
||||
winrt::hstring _GetCloudShell();
|
||||
winrt::hstring _GetTerminal(const winrt::hstring& shellType);
|
||||
void _StoreCredential();
|
||||
void _RemoveCredentials();
|
||||
|
||||
|
@ -95,9 +92,16 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
|
|||
|
||||
std::optional<std::wstring> _ReadUserInput(InputMode mode);
|
||||
|
||||
web::websockets::client::websocket_client _cloudShellSocket;
|
||||
winrt::Windows::Web::Http::HttpClient _httpClient{ nullptr };
|
||||
wil::unique_winhttp_hinternet _socketSessionHandle;
|
||||
wil::unique_winhttp_hinternet _socketConnectionHandle;
|
||||
wil::unique_winhttp_hinternet _webSocket;
|
||||
|
||||
static std::optional<utility::string_t> _ParsePreferredShellType(const web::json::value& settingsResponse);
|
||||
til::u8state _u8State{};
|
||||
std::wstring _u16Str;
|
||||
std::array<char, 4096> _buffer{};
|
||||
|
||||
static winrt::hstring _ParsePreferredShellType(const winrt::Windows::Data::Json::JsonObject& settingsResponse);
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -11,7 +11,6 @@
|
|||
</PropertyGroup>
|
||||
<PropertyGroup Label="NuGet Dependencies">
|
||||
<TerminalCppWinrt>true</TerminalCppWinrt>
|
||||
<TerminalCppRestSDK>true</TerminalCppRestSDK>
|
||||
</PropertyGroup>
|
||||
<Import Project="..\..\..\common.openconsole.props" Condition="'$(OpenConsoleDir)'==''" />
|
||||
<Import Project="$(OpenConsoleDir)src\common.nugetversions.props" />
|
||||
|
@ -99,39 +98,4 @@
|
|||
|
||||
<!-- This -must- go after cppwinrt.build.post.props because that includes many VS-provided props including appcontainer.common.props, which stomps on what cppwinrt.targets did. -->
|
||||
<Import Project="$(OpenConsoleDir)src\common.nugetversions.targets" />
|
||||
|
||||
<!--
|
||||
BODGY:
|
||||
|
||||
We depend on `cpprest142*.dll`, which comes from our vcpkg dependency. As a
|
||||
part of the vcpkg dependency restore, msbuild will call the `deployBinary()`
|
||||
function in
|
||||
`packages\vcpkg-cpprestsdk.2.10.14\scripts\BuildSystems\msbuild\AppLocal.ps1`.
|
||||
That function does the actual job of copying the file. It copies it outside of
|
||||
MsBuild. MsBuild then, in the `_CopyFilesMarkedCopyLocal` target, determines
|
||||
that it needs to copy `cpprest142*.dll`, because that dll is a member of
|
||||
`@(ReferencesCopiedInThisBuild)`. However, the file's already been copied, so
|
||||
MsBuild never copies it. But that also prevents MsBuild from setting
|
||||
`WroteAtLeastOneFile`, which then means that MsBuild will never create the
|
||||
.CopyComplete file for this project.
|
||||
|
||||
Because that file is missing, MsBuild will never think the project is up to
|
||||
date, and the FastUpToDate check in VS will always force MsBuild to run a pass
|
||||
on this project.
|
||||
|
||||
To mitigate this, we're adding this other target here, which runs after
|
||||
_CopyFilesMarkedCopyLocal, and always creates the CopyUpToDateMarker. This
|
||||
makes the FastUpToDate check succeed.
|
||||
-->
|
||||
|
||||
<Target
|
||||
Name="_Post_CopyFilesMarkedCopyLocal"
|
||||
AfterTargets="_CopyFilesMarkedCopyLocal"
|
||||
Condition="'@(ReferenceCopyLocalPaths)' != ''">
|
||||
|
||||
<Touch Files="@(CopyUpToDateMarker)"
|
||||
AlwaysCreate="true" />
|
||||
</Target>
|
||||
|
||||
|
||||
</Project>
|
||||
|
|
|
@ -24,8 +24,14 @@
|
|||
|
||||
#include "winrt/Windows.Security.Credentials.h"
|
||||
#include "winrt/Windows.Foundation.Collections.h"
|
||||
#include "winrt/Windows.Web.Http.h"
|
||||
#include "winrt/Windows.Web.Http.Headers.h"
|
||||
#include "winrt/Windows.Data.Json.h"
|
||||
#include <Windows.h>
|
||||
|
||||
#include <winhttp.h>
|
||||
#include <wil/resource.h>
|
||||
|
||||
#include <TraceLoggingProvider.h>
|
||||
TRACELOGGING_DECLARE_PROVIDER(g_hTerminalConnectionProvider);
|
||||
#include <telemetry/ProjectTelemetry.h>
|
||||
|
|
|
@ -43,9 +43,6 @@
|
|||
<!-- TAEF -->
|
||||
<Import Project="$(MSBuildThisFileDirectory)..\packages\Microsoft.Taef.10.60.210621002\build\Microsoft.Taef.targets" Condition="'$(TerminalTAEF)' == 'true' and Exists('$(MSBuildThisFileDirectory)..\packages\Microsoft.Taef.10.60.210621002\build\Microsoft.Taef.targets')" />
|
||||
|
||||
<!-- CppRestSDK -->
|
||||
<Import Project="$(MSBuildThisFileDirectory)..\packages\vcpkg-cpprestsdk.2.10.14\build\native\vcpkg-cpprestsdk.targets" Condition="'$(TerminalCppRestSDK)' == 'true' and Exists('$(MSBuildThisFileDirectory)..\packages\vcpkg-cpprestsdk.2.10.14\build\native\vcpkg-cpprestsdk.targets')" />
|
||||
|
||||
<!-- VCRTForwarders -->
|
||||
<Import Project="$(MSBuildThisFileDirectory)..\packages\Microsoft.VCRTForwarders.140.1.0.4\build\native\Microsoft.VCRTForwarders.140.targets" Condition="'$(TerminalVCRTForwarders)' == 'true' and Exists('$(MSBuildThisFileDirectory)..\packages\Microsoft.VCRTForwarders.140.1.0.4\build\native\Microsoft.VCRTForwarders.140.targets')" />
|
||||
|
||||
|
@ -79,9 +76,6 @@
|
|||
<!-- TAEF -->
|
||||
<Error Condition="'$(TerminalTAEF)' == 'true' AND !Exists('$(MSBuildThisFileDirectory)..\packages\Microsoft.Taef.10.60.210621002\build\Microsoft.Taef.targets')" Text="$([System.String]::Format('$(ErrorText)', '$(MSBuildThisFileDirectory)..\packages\Microsoft.Taef.10.60.210621002\build\Microsoft.Taef.targets'))" />
|
||||
|
||||
<!-- CppRestSDK -->
|
||||
<Error Condition="'$(TerminalCppRestSDK)' == 'true' AND !Exists('$(MSBuildThisFileDirectory)..\packages\vcpkg-cpprestsdk.2.10.14\build\native\vcpkg-cpprestsdk.targets')" Text="$([System.String]::Format('$(ErrorText)', '$(MSBuildThisFileDirectory)..\packages\vcpkg-cpprestsdk.2.10.14\build\native\vcpkg-cpprestsdk.targets'))" />
|
||||
|
||||
<!-- VCRTForwarders -->
|
||||
<Error Condition="'$(TerminalVCRTForwarders)' == 'true' AND !Exists('$(MSBuildThisFileDirectory)..\packages\Microsoft.VCRTForwarders.140.1.0.4\build\native\Microsoft.VCRTForwarders.140.targets')" Text="$([System.String]::Format('$(ErrorText)', '$(MSBuildThisFileDirectory)..\packages\Microsoft.VCRTForwarders.140.1.0.4\build\native\Microsoft.VCRTForwarders.140.targets'))" />
|
||||
|
||||
|
|
|
@ -145,4 +145,14 @@
|
|||
<stage>AlwaysDisabled</stage>
|
||||
</feature>
|
||||
|
||||
<feature>
|
||||
<name>Feature_AzureConnectionInProc</name>
|
||||
<description>Host the AzureConnection inside Terminal rather than via TerminalAzBridge</description>
|
||||
<id>4661</id>
|
||||
<stage>AlwaysDisabled</stage>
|
||||
<alwaysEnabledBrandingTokens>
|
||||
<brandingToken>Dev</brandingToken>
|
||||
</alwaysEnabledBrandingTokens>
|
||||
</feature>
|
||||
|
||||
</featureStaging>
|
||||
|
|
Загрузка…
Ссылка в новой задаче