Skip to content

Commit

Permalink
Hide WinRT - Part II (#290)
Browse files Browse the repository at this point in the history
And season finale. With the following set of changes we are ready to
work on the mock client API.

There was a bug in the DLL's vcxproj: The `DefineConstants` property is
a C# thing. With VC++, we need to add a `ClCompile` item with the
`PreprocessorDefinitions` flag set. That's the way we do in WSL launcher
end-to-end tests
(https://github.com/ubuntu/WSL/blob/main/meta/UbuntuPreview/src/DistroLauncher/Preprocessor.props).

Closes UDENG-1380
  • Loading branch information
CarlosNihelton authored Sep 20, 2023
2 parents 83ec4ed + 3cc36e3 commit 9f60462
Show file tree
Hide file tree
Showing 10 changed files with 174 additions and 144 deletions.
27 changes: 18 additions & 9 deletions msix/storeapi/StoreApi.cpp
Original file line number Diff line number Diff line change
@@ -1,11 +1,21 @@
#include "StoreApi.hpp"

#include <combaseapi.h>
#include <winrt/base.h>

#include <agent/ServerStoreService.hpp>
#include <base/Exception.hpp>
#include <cstring> // For strnlen
#include <exception>
#include <string>

#include "framework.hpp"

#ifndef DNDEBUG
#include <format>
#include <iostream>
#endif

#include "StoreApi.hpp"
#include "framework.hpp"

// Syntactic sugar to convert the enum [value] into a Int.
constexpr Int toInt(StoreApi::ErrorCode value) {
return static_cast<Int>(value);
Expand All @@ -24,9 +34,9 @@ static constexpr std::size_t MaxProductIdLen = 129;
StoreApi::ErrorCode validateArg(const char* input, std::size_t maxLength);

void logError(std::string_view functionName, std::string_view errMsg) {
#ifndef DNDEBUG
std::cerr << std::format("storeapi: {}: {}\n", functionName , errMsg);
#endif
#ifndef DNDEBUG
std::cerr << std::format("storeapi: {}: {}\n", functionName, errMsg);
#endif
}

#define LOG_ERROR(msg) \
Expand Down Expand Up @@ -75,10 +85,9 @@ Int GenerateUserJWT(const char* accessToken, char** userJWT,
}

try {
auto user = StoreApi::UserInfo::Current().get();

StoreApi::ServerStoreService service{};
const std::string jwt = service.GenerateUserJwt(accessToken, user).get();
auto user = service.CurrentUserInfo();
const std::string jwt = service.GenerateUserJwt(accessToken, user);

// Allocates memory using some OS API so we can free this buffer on the
// other side of the ABI without assumptions on specifics of the programming
Expand Down
2 changes: 0 additions & 2 deletions msix/storeapi/StoreApi.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
// the ABI. Zero or positive values have no special meaning other than success.
#pragma once

#include <agent/ServerStoreService.hpp>
#include <cstdint>

extern "C" {
Expand Down Expand Up @@ -37,5 +36,4 @@ DLL_EXPORT Int GetSubscriptionExpirationDate(const char* productID,
DLL_EXPORT Int GenerateUserJWT(const char* accessToken,
// output
char** userJWT, std::uint64_t* userJWTLen);

}
9 changes: 5 additions & 4 deletions msix/storeapi/storeapi.vcxproj
Original file line number Diff line number Diff line change
Expand Up @@ -43,9 +43,11 @@
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<PropertyGroup Label="UserMacros" />
<PropertyGroup Label="Mock API" Condition="'$(UP4W_TEST_WITH_MS_STORE_MOCK)' == ''">
<DefineConstants>UP4W_TEST_WITH_MS_STORE_MOCK</DefineConstants>
</PropertyGroup>
<ItemDefinitionGroup Label="Mock API" Condition="'$(UP4W_TEST_WITH_MS_STORE_MOCK)' != ''">
<ClCompile>
<PreprocessorDefinitions>UP4W_TEST_WITH_MS_STORE_MOCK;%(PreProcessorDefinitions)</PreprocessorDefinitions>
</ClCompile>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<ClCompile>
<WarningLevel>Level3</WarningLevel>
Expand Down Expand Up @@ -97,7 +99,6 @@
<ClInclude Include="StoreApi.hpp" />
</ItemGroup>
<ItemGroup>
<ClCompile Include="..\..\storeapi\agent\ServerStoreService.cpp" />
<ClCompile Include="..\..\storeapi\base\impl\StoreContext.hpp" />
<ClCompile Include="dllmain.cpp" />
<ClCompile Include="StoreApi.cpp" />
Expand Down
57 changes: 0 additions & 57 deletions storeapi/agent/ServerStoreService.cpp

This file was deleted.

41 changes: 26 additions & 15 deletions storeapi/agent/ServerStoreService.hpp
Original file line number Diff line number Diff line change
@@ -1,24 +1,20 @@
#pragma once
#include <pplawait.h>

#include "base/DefaultContext.hpp"
#include "base/Exception.hpp"
#include "base/StoreService.hpp"

#include <base/DefaultContext.hpp>
#include <base/Exception.hpp>
#include <base/StoreService.hpp>
#include <chrono>
#include <cstdint>
#include <format>
#include <limits>
#include <string>

namespace StoreApi {

// Models the interesting user information the application can correlate
// when talking to external business servers about the subscription.
struct UserInfo {
// The user ID that should be tracked in the Contract Server.
winrt::hstring id;

// An asynchronous factory returning [UserInfo] of the current user.
static concurrency::task<UserInfo> Current();
std::string id;
};

// Adds functionality on top of the [StoreService] interesting to background
Expand All @@ -28,20 +24,18 @@ class ServerStoreService : public StoreService<ContextType> {
public:
// Generates the user ID key (a.k.a the JWT) provided the server AAD [token]
// and the [user] info whose ID the caller wants to have encoded in the JWT.
concurrency::task<std::string> GenerateUserJwt(std::string token,
UserInfo user) {
std::string GenerateUserJwt(std::string token, UserInfo user) const {
if (user.id.empty()) {
throw Exception(StoreApi::ErrorCode::NoLocalUser);
}

auto hToken = winrt::to_hstring(token);
auto jwt = co_await this->context.GenerateUserJwt(hToken, user.id);
auto jwt = this->context.GenerateUserJwt(token, user.id);
if (jwt.empty()) {
throw Exception(ErrorCode::EmptyJwt,
std::format("access token: {}", token));
}

co_return winrt::to_string(jwt);
return jwt;
}

// Returns the expiration time as the number of seconds since Unix epoch of
Expand All @@ -60,6 +54,23 @@ class ServerStoreService : public StoreService<ContextType> {
// just need to convert the duration to seconds.
return duration_cast<std::chrono::seconds>(dur).count();
}

// A factory returning the current user's [UserInfo].
UserInfo CurrentUserInfo() const {
auto hashes = this->context.AllLocallyAuthenticatedUserHashes();

auto howManyUsers = hashes.size();
if (howManyUsers < 1) {
throw Exception(ErrorCode::NoLocalUser);
}

if (howManyUsers > 1) {
throw Exception(ErrorCode::TooManyLocalUsers,
std::format("Expected one but found {}", howManyUsers));
}

return UserInfo{.id = hashes[0]};
}
};

} // namespace StoreApi
62 changes: 61 additions & 1 deletion storeapi/base/impl/StoreContext.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,12 @@
#include "StoreContext.hpp"

#include <winrt/Windows.Foundation.Collections.h>
#include <winrt/Windows.Foundation.h>
#include <winrt/Windows.Security.Cryptography.core.h>
#include <winrt/Windows.Security.Cryptography.h>
#include <winrt/Windows.Services.Store.h>
#include <winrt/Windows.System.h>
#include <winrt/base.h>

#include <algorithm>
#include <format>
Expand All @@ -29,6 +35,9 @@ std::vector<winrt::hstring> to_hstrings(std::span<const std::string> input);

// Translates a [StorePurchaseStatus] into the [PurchaseStatus] enum.
PurchaseStatus translate(StorePurchaseStatus purchaseStatus) noexcept;

// Returns a hstring representation of a SHA256 sum of the input hstring.
winrt::hstring sha256(winrt::hstring input);
} // namespace

std::chrono::system_clock::time_point
Expand Down Expand Up @@ -85,6 +94,15 @@ std::vector<StoreContext::Product> StoreContext::GetProducts(
return products;
}

std::string StoreContext::GenerateUserJwt(std::string token,
std::string userId) const {
assert(!token.empty() && "Azure AD token is required");
auto hJwt = self.GetCustomerPurchaseIdAsync(winrt::to_hstring(token),
winrt::to_hstring(userId))
.get();
return winrt::to_string(hJwt);
}

void StoreContext::InitDialogs(Window parentWindow) {
// Apps that do not feature a [CoreWindow] must inform the runtime the parent
// window handle in order to render runtime provided UI elements, such as
Expand All @@ -93,6 +111,34 @@ void StoreContext::InitDialogs(Window parentWindow) {
iiw->Initialize(parentWindow);
}

std::vector<std::string> StoreContext::AllLocallyAuthenticatedUserHashes() {
using winrt::Windows::Foundation::IInspectable;
using winrt::Windows::System::KnownUserProperties;
using winrt::Windows::System::User;
using winrt::Windows::System::UserAuthenticationStatus;
using winrt::Windows::System::UserType;

// This should really return a single user, but the API is specified in terms
// of a collection, so let's not assume too much.
auto users =
User::FindAllAsync(UserType::LocalUser,
UserAuthenticationStatus::LocallyAuthenticated)
.get();

std::vector<std::string> allHashes;
allHashes.reserve(users.Size());
for (auto user : users) {
IInspectable accountName =
user.GetPropertyAsync(KnownUserProperties::AccountName()).get();
auto name = winrt::unbox_value<winrt::hstring>(accountName);
if (!name.empty()) {
allHashes.push_back(winrt::to_string(sha256(name)));
}
}

return allHashes;
}

namespace {
std::vector<winrt::hstring> to_hstrings(std::span<const std::string> input) {
std::vector<winrt::hstring> hStrs;
Expand All @@ -119,8 +165,22 @@ PurchaseStatus translate(StorePurchaseStatus purchaseStatus) noexcept {
assert(false && "Missing enum elements to translate StorePurchaseStatus.");
return StoreApi::PurchaseStatus::Unknown; // To be future proof.
}

winrt::hstring sha256(winrt::hstring input) {
using winrt::Windows::Security::Cryptography::BinaryStringEncoding;
using winrt::Windows::Security::Cryptography::CryptographicBuffer;
using winrt::Windows::Security::Cryptography::Core::HashAlgorithmNames;
using winrt::Windows::Security::Cryptography::Core::HashAlgorithmProvider;

auto inputUtf8 = CryptographicBuffer::ConvertStringToBinary(
winrt::to_hstring(input), BinaryStringEncoding::Utf8);
auto hasher =
HashAlgorithmProvider::OpenAlgorithm(HashAlgorithmNames::Sha256());
return CryptographicBuffer::EncodeToHexString(hasher.HashData(inputUtf8));
}

} // namespace

} // namespace StoreApi::impl

#endif // UP4W_TEST_WITH_MS_STORE_MOCK
#endif // UP4W_TEST_WITH_MS_STORE_MOCK
16 changes: 7 additions & 9 deletions storeapi/base/impl/StoreContext.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,6 @@
// For the underlying Store API
#include <winrt/windows.services.store.h>

// To provide the WinRT coroutine types.
#include <winrt/windows.foundation.h>

// For HWND and GUI-related Windows types.
#include <ShObjIdl.h>

Expand Down Expand Up @@ -70,17 +67,18 @@ class StoreContext {
std::vector<Product> GetProducts(std::span<const std::string> kinds,
std::span<const std::string> ids) const;

// Generates the user ID key (a.k.a the JWT) provided the server AAD [hToken]
// and the [hUserId] the caller wants to have encoded in the JWT.
winrt::Windows::Foundation::IAsyncOperation<winrt::hstring> GenerateUserJwt(
winrt::hstring hToken, winrt::hstring hUserId) {
return self.GetCustomerPurchaseIdAsync(hToken, hUserId);
}
// Generates the user ID key (a.k.a the JWT) provided the server AAD [token]
// and the [userId] the caller wants to have encoded in the JWT.
std::string GenerateUserJwt(std::string token, std::string userId) const;

// Initializes the GUI "subsystem" with the [parentWindow] handle so we can
// render native dialogs, such as when purchase or other kinds of
// authorization are required.
void InitDialogs(Window parentWindow);

// Returns a collection of hashes of all locally authenticated users running
// in this session. Most likely the collection will contain a single element.
static std::vector<std::string> AllLocallyAuthenticatedUserHashes();
};

} // namespace StoreApi::impl
Expand Down
2 changes: 1 addition & 1 deletion storeapi/test/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ set(StoreApi_SRCS
"../base/Exception.hpp"
"../base/Purchase.hpp"
"../base/StoreService.hpp"
"../agent/ServerStoreService.cpp"
"../agent/ServerStoreService.hpp"
"../gui/ClientStoreService.hpp"
)

Expand Down
Loading

0 comments on commit 9f60462

Please sign in to comment.