az: rework tenant/tenant storage and fix display names

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.
This commit is contained in:
Dustin Howett
2020-04-15 12:50:46 -07:00
parent 9f6ebcb677
commit 38429b56cf
3 changed files with 66 additions and 40 deletions

View File

@@ -34,6 +34,13 @@ namespace Microsoft::Terminal::Azure
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) \

View File

@@ -16,8 +16,6 @@
#include "AzureConnection.g.cpp"
#include "AzureClient.h"
#include "winrt/Windows.System.UserProfile.h"
#include "../../types/inc/Utils.hpp"
@@ -52,9 +50,12 @@ static inline std::wstring _formatResWithColoredUserInputOptions(const std::wstr
return fmt::format(std::wstring_view{ GetLibraryResourceString(resourceKey) }, (_colorize(USER_INPUT_COLOR, GetLibraryResourceString(args)))...);
}
static inline std::wstring _formatTenantLine(int tenantNumber, const std::wstring_view tenantName, const std::wstring_view tenantID)
static inline std::wstring _formatTenant(int tenantNumber, const Tenant& tenant)
{
return fmt::format(std::wstring_view{ RS_(L"AzureIthTenant") }, _colorize(USER_INPUT_COLOR, std::to_wstring(tenantNumber)), _colorize(USER_INFO_COLOR, tenantName), tenantID);
return fmt::format(std::wstring_view{ RS_(L"AzureIthTenant") },
_colorize(USER_INPUT_COLOR, std::to_wstring(tenantNumber)),
_colorize(USER_INFO_COLOR, tenant.DisplayName.value_or(std::wstring{ RS_(L"AzureUnknownTenantName") })),
tenant.DefaultDomain.value_or(tenant.ID)); // use the domain name if possible, ID if not.
}
namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
@@ -278,25 +279,45 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
// - tenant - the unparsed tenant
// Return value:
// - a tuple containing the ID and display name of the tenant.
static std::tuple<utility::string_t, utility::string_t> _crackTenant(const json::value& tenant)
static Tenant _crackTenant(const json::value& jsonTenant)
{
auto tenantId{ tenant.at(L"tenantId").as_string() };
std::wstring displayName{};
if (tenant.has_string_field(L"displayName"))
Tenant tenant{};
if (jsonTenant.has_string_field(L"tenantID"))
{
displayName = tenant.at(L"displayName").as_string();
// for compatibility with version 1 credentials
tenant.ID = jsonTenant.at(L"tenantID").as_string();
}
else
{
displayName = std::wstring{ RS_(L"AzureUnknownTenantName") };
// This one comes in off the wire
tenant.ID = jsonTenant.at(L"tenantId").as_string();
}
if (tenant.has_string_field(L"defaultDomain"))
if (jsonTenant.has_string_field(L"displayName"))
{
auto defaultDomain{ tenant.at(L"defaultDomain").as_string() };
displayName = wil::str_printf<std::wstring>(L"%.*s, %.*s", displayName.size(), displayName.data(), defaultDomain.size(), defaultDomain.data());
tenant.DisplayName = jsonTenant.at(L"displayName").as_string();
}
if (jsonTenant.has_string_field(L"defaultDomain"))
{
tenant.DefaultDomain = jsonTenant.at(L"defaultDomain").as_string();
}
return tenant;
}
static void _packTenant(json::value& jsonTenant, const Tenant& tenant)
{
jsonTenant[L"tenantId"] = json::value::string(tenant.ID);
if (tenant.DisplayName.has_value())
{
jsonTenant[L"displayName"] = json::value::string(*tenant.DisplayName);
}
if (tenant.DefaultDomain.has_value())
{
jsonTenant[L"defaultDomain"] = json::value::string(*tenant.DefaultDomain);
}
return { tenantId, displayName };
}
// Method description:
@@ -417,6 +438,7 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
}
int numTenants{ 0 };
_tenantList.clear();
for (const auto& entry : credList)
{
auto nameJson = json::value::parse(entry.UserName().c_str());
@@ -434,7 +456,9 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
continue;
}
_WriteStringWithNewline(_formatTenantLine(numTenants, nameJson.at(L"displayName").as_string(), nameJson.at(L"tenantID").as_string()));
auto newTenant{ _tenantList.emplace_back(_crackTenant(nameJson)) };
_WriteStringWithNewline(_formatTenant(numTenants, newTenant));
numTenants++;
}
@@ -501,10 +525,8 @@ 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 nameJson = json::value::parse(desiredCredential.UserName().c_str());
auto passWordJson = json::value::parse(desiredCredential.Password().c_str());
_displayName = nameJson.at(L"displayName").as_string();
_tenantID = nameJson.at(L"tenantID").as_string();
_currentTenant = _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());
@@ -560,10 +582,8 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
_refreshToken = authenticatedResponse.at(L"refresh_token").as_string();
// Get the tenants and the required tenant id
const auto tenantsResponse = _GetTenants();
_tenantList = tenantsResponse.at(L"value");
const auto tenantListAsArray = _tenantList.as_array();
if (tenantListAsArray.size() == 0)
_PopulateTenantList();
if (_tenantList.size() == 0)
{
_WriteStringWithNewline(RS_(L"AzureNoTenants"));
_transitionToState(ConnectionState::Failed);
@@ -571,8 +591,7 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
}
else if (_tenantList.size() == 1)
{
const auto& chosenTenant = tenantListAsArray.at(0);
std::tie(_tenantID, _displayName) = _crackTenant(chosenTenant);
_currentTenant = til::at(_tenantList, 0);
// We have to refresh now that we have the tenantID
_RefreshTokens();
@@ -588,13 +607,10 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
// - helper function to list the user's tenants and let them decide which tenant they wish to connect to
void AzureConnection::_RunTenantChoiceState()
{
const auto tenantListAsArray = _tenantList.as_array();
auto numTenants = gsl::narrow<int>(tenantListAsArray.size());
auto numTenants = gsl::narrow<int>(_tenantList.size());
for (int i = 0; i < numTenants; i++)
{
const auto& tenant = tenantListAsArray.at(i);
const auto [tenantId, tenantDisplayName] = _crackTenant(tenant);
_WriteStringWithNewline(_formatTenantLine(i, tenantDisplayName, tenantId));
_WriteStringWithNewline(_formatTenant(i, _tenantList[i]));
}
_WriteStringWithNewline(RS_(L"AzureEnterTenant"));
@@ -628,8 +644,7 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
_WriteStringWithNewline(RS_(L"AzureNonNumberError"));
} while (true);
const auto& chosenTenant = tenantListAsArray.at(selectedTenant);
std::tie(_tenantID, _displayName) = _crackTenant(chosenTenant);
_currentTenant = _tenantList[selectedTenant];
// We have to refresh now that we have the tenantID
_RefreshTokens();
@@ -824,7 +839,7 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
// - helper function to acquire the user's Azure tenants
// Return value:
// - the response which contains a list of the user's Azure tenants
json::value AzureConnection::_GetTenants()
void AzureConnection::_PopulateTenantList()
{
// Initialize the client
http_client tenantClient(_resourceUri);
@@ -834,7 +849,11 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
tenantRequest.set_request_uri(L"tenants?api-version=2020-01-01");
// Send the request and return the response as a json value
return _SendAuthenticatedRequestReturningJson(tenantClient, tenantRequest);
auto tenantResponse{ _SendAuthenticatedRequestReturningJson(tenantClient, tenantRequest) };
auto tenantList{ tenantResponse.at(L"value").as_array() };
_tenantList.clear();
std::transform(tenantList.begin(), tenantList.end(), std::back_inserter(_tenantList), _crackTenant);
}
// Method description:
@@ -848,7 +867,7 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
// Initialize the request
http_request refreshRequest(L"POST");
refreshRequest.set_request_uri(_tenantID + L"/oauth2/token");
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");
@@ -928,8 +947,7 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
{
json::value userName;
userName[U("ver")] = CurrentCredentialVersion;
userName[U("displayName")] = json::value::string(_displayName);
userName[U("tenantID")] = json::value::string(_tenantID);
_packTenant(userName, *_currentTenant);
json::value passWord;
passWord[U("accessToken")] = json::value::string(_accessToken);
passWord[U("refreshToken")] = json::value::string(_refreshToken);

View File

@@ -13,6 +13,7 @@
#include "../cascadia/inc/cppwinrt_utils.h"
#include "ConnectionStateHolder.h"
#include "AzureClient.h"
namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
{
@@ -57,22 +58,22 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
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 };
web::json::value _tenantList;
utility::string_t _displayName;
utility::string_t _tenantID;
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);
web::json::value _GetTenants();
void _PopulateTenantList();
void _RefreshTokens();
web::json::value _GetCloudShellUserSettings();
utility::string_t _GetCloudShell();