From 61c501805e63bfe108be8fc1b09eba1c73e28e55 Mon Sep 17 00:00:00 2001 From: Duncan Horn Date: Tue, 4 Oct 2022 12:55:02 -0700 Subject: [PATCH 1/9] Copy code and get it compiling --- WindowsAppRuntime.sln | 7 + dev/OAuth/AuthFailure.cpp | 72 +++ dev/OAuth/AuthFailure.h | 23 + dev/OAuth/AuthManager.cpp | 282 +++++++++++ dev/OAuth/AuthManager.h | 72 +++ dev/OAuth/AuthRequestAsyncOperation.cpp | 464 ++++++++++++++++++ dev/OAuth/AuthRequestAsyncOperation.h | 65 +++ dev/OAuth/AuthRequestParams.cpp | 263 ++++++++++ dev/OAuth/AuthRequestParams.h | 67 +++ dev/OAuth/AuthRequestResult.cpp | 67 +++ dev/OAuth/AuthRequestResult.h | 21 + dev/OAuth/AuthResponse.cpp | 91 ++++ dev/OAuth/AuthResponse.h | 37 ++ dev/OAuth/ClientAuthentication.cpp | 65 +++ dev/OAuth/ClientAuthentication.h | 36 ++ dev/OAuth/Crypto.h | 87 ++++ ...icrosoft.Security.Authentication.OAuth.def | 4 + dev/OAuth/OAuth.idl | 462 +++++++++++++++++ dev/OAuth/OAuth.vcxitems | 85 ++++ dev/OAuth/TokenFailure.cpp | 79 +++ dev/OAuth/TokenFailure.h | 26 + dev/OAuth/TokenRequestParams.cpp | 243 +++++++++ dev/OAuth/TokenRequestParams.h | 77 +++ dev/OAuth/TokenRequestResult.cpp | 47 ++ dev/OAuth/TokenRequestResult.h | 23 + dev/OAuth/TokenResponse.cpp | 81 +++ dev/OAuth/TokenResponse.h | 25 + dev/OAuth/common.h | 20 + .../WindowsAppRuntime_DLL.vcxproj | 1 + 29 files changed, 2892 insertions(+) create mode 100644 dev/OAuth/AuthFailure.cpp create mode 100644 dev/OAuth/AuthFailure.h create mode 100644 dev/OAuth/AuthManager.cpp create mode 100644 dev/OAuth/AuthManager.h create mode 100644 dev/OAuth/AuthRequestAsyncOperation.cpp create mode 100644 dev/OAuth/AuthRequestAsyncOperation.h create mode 100644 dev/OAuth/AuthRequestParams.cpp create mode 100644 dev/OAuth/AuthRequestParams.h create mode 100644 dev/OAuth/AuthRequestResult.cpp create mode 100644 dev/OAuth/AuthRequestResult.h create mode 100644 dev/OAuth/AuthResponse.cpp create mode 100644 dev/OAuth/AuthResponse.h create mode 100644 dev/OAuth/ClientAuthentication.cpp create mode 100644 dev/OAuth/ClientAuthentication.h create mode 100644 dev/OAuth/Crypto.h create mode 100644 dev/OAuth/Microsoft.Security.Authentication.OAuth.def create mode 100644 dev/OAuth/OAuth.idl create mode 100644 dev/OAuth/OAuth.vcxitems create mode 100644 dev/OAuth/TokenFailure.cpp create mode 100644 dev/OAuth/TokenFailure.h create mode 100644 dev/OAuth/TokenRequestParams.cpp create mode 100644 dev/OAuth/TokenRequestParams.h create mode 100644 dev/OAuth/TokenRequestResult.cpp create mode 100644 dev/OAuth/TokenRequestResult.h create mode 100644 dev/OAuth/TokenResponse.cpp create mode 100644 dev/OAuth/TokenResponse.h create mode 100644 dev/OAuth/common.h diff --git a/WindowsAppRuntime.sln b/WindowsAppRuntime.sln index 7cc5c0da98..2f5cf8f25a 100644 --- a/WindowsAppRuntime.sln +++ b/WindowsAppRuntime.sln @@ -408,6 +408,10 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Test_DeploymentManagerAutoI EndProjectSection Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Windows.AppNotifications.Builder.Projection", "dev\Projections\CS\Microsoft.Windows.AppNotifications.Builder.Projection\Microsoft.Windows.AppNotifications.Builder.Projection.csproj", "{50BF3E96-3050-4053-B012-BF6993483DA5}" EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "OAuth", "OAuth", "{4A3ACD67-5C57-474D-87BF-675676D7451A}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "OAuth", "dev\OAuth\OAuth.vcxitems", "{3E7FD510-8B66-40E7-A80B-780CB8972F83}" +EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "VersionInfo", "VersionInfo", "{2A2D1131-273C-4E17-BCD3-8812170A4B95}" EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "VersionInfo", "dev\VersionInfo\VersionInfo.vcxitems", "{E3EDEC7F-A24E-4766-BB1D-6BDFBA157C51}" @@ -452,6 +456,7 @@ Global dev\EnvironmentManager\ChangeTracker\ChangeTracker.vcxitems*{e15c3465-9d45-495d-92ce-b91ef45e8623}*SharedItemsImports = 9 dev\AppLifecycle\AppLifecycle.vcxitems*{e3a522a3-6635-4a42-bded-1af46a15f63c}*SharedItemsImports = 9 dev\AppNotifications\AppNotificationBuilder\AppNotificationBuilder.vcxitems*{e49329f3-5196-4bba-b5c4-e11ce7efb07a}*SharedItemsImports = 9 + dev\OAuth\OAuth.vcxitems*{3E7FD510-8B66-40E7-A80B-780CB8972F83}*SharedItemsImports = 9 dev\VersionInfo\VersionInfo.vcxitems*{e3edec7f-a24e-4766-bb1d-6bdfba157c51}*SharedItemsImports = 9 test\inc\inc.vcxitems*{e5659a29-fe68-417b-9bc5-613073dd54df}*SharedItemsImports = 4 test\inc\inc.vcxitems*{e977b1bd-00dc-4085-a105-e0a18e0183d7}*SharedItemsImports = 4 @@ -1749,6 +1754,8 @@ Global {676BA502-4220-465A-A9ED-ED22CDE4A24B} = {3A37083C-AA67-461E-BA78-0E0A65FE0C22} {5A4FBF6D-04A2-4061-B11F-1A0E64129610} = {3A37083C-AA67-461E-BA78-0E0A65FE0C22} {50BF3E96-3050-4053-B012-BF6993483DA5} = {716C26A0-E6B0-4981-8412-D14A4D410531} + {4A3ACD67-5C57-474D-87BF-675676D7451A} = {448ED2E5-0B37-4D97-9E6B-8C10A507976A} + {3E7FD510-8B66-40E7-A80B-780CB8972F83} = {4A3ACD67-5C57-474D-87BF-675676D7451A} {2A2D1131-273C-4E17-BCD3-8812170A4B95} = {448ED2E5-0B37-4D97-9E6B-8C10A507976A} {E3EDEC7F-A24E-4766-BB1D-6BDFBA157C51} = {2A2D1131-273C-4E17-BCD3-8812170A4B95} {442FB943-1197-48FE-B3B6-8C1BCA1E81E4} = {8630F7AA-2969-4DC9-8700-9B468C1DC21D} diff --git a/dev/OAuth/AuthFailure.cpp b/dev/OAuth/AuthFailure.cpp new file mode 100644 index 0000000000..d0d6ce30cb --- /dev/null +++ b/dev/OAuth/AuthFailure.cpp @@ -0,0 +1,72 @@ +#include +#include "common.h" + +#include "AuthFailure.h" +#include + +using namespace std::literals; +using namespace winrt::Microsoft::Security::Authentication::OAuth; +using namespace winrt::Windows::Foundation; +using namespace winrt::Windows::Foundation::Collections; + +namespace winrt::Microsoft::Security::Authentication::OAuth::implementation +{ + AuthFailure::AuthFailure(const Uri& responseUri) + { + std::map additionalParams; + + for (auto&& entry : responseUri.QueryParsed()) + { + auto name = entry.Name(); + if (name == L"error"sv) + { + m_error = entry.Value(); + } + else if (name == L"error_description"sv) + { + m_errorDescription = entry.Value(); + } + else if (name == L"error_uri"sv) + { + m_errorUri = Uri(entry.Value()); + } + else if (name == L"state"sv) + { + m_state = entry.Value(); + } + else + { + additionalParams.emplace(std::move(name), entry.Value()); + } + } + + // TODO: Look in the fragment part as well + + m_additionalParams = winrt::single_threaded_map(std::move(additionalParams)).GetView(); + } + + winrt::hstring AuthFailure::Error() + { + return m_error; + } + + winrt::hstring AuthFailure::ErrorDescription() + { + return m_errorDescription; + } + + Uri AuthFailure::ErrorUri() + { + return m_errorUri; + } + + winrt::hstring AuthFailure::State() + { + return m_state; + } + + IMapView AuthFailure::AdditionalParams() + { + return m_additionalParams; + } +} diff --git a/dev/OAuth/AuthFailure.h b/dev/OAuth/AuthFailure.h new file mode 100644 index 0000000000..37c80761ff --- /dev/null +++ b/dev/OAuth/AuthFailure.h @@ -0,0 +1,23 @@ +#pragma once +#include + +namespace winrt::Microsoft::Security::Authentication::OAuth::implementation +{ + struct AuthFailure : AuthFailureT + { + AuthFailure(const foundation::Uri& responseUri); + + winrt::hstring Error(); + winrt::hstring ErrorDescription(); + foundation::Uri ErrorUri(); + winrt::hstring State(); + collections::IMapView AdditionalParams(); + + private: + winrt::hstring m_error; + winrt::hstring m_errorDescription; + foundation::Uri m_errorUri{ nullptr }; + winrt::hstring m_state; + collections::IMapView m_additionalParams; + }; +} diff --git a/dev/OAuth/AuthManager.cpp b/dev/OAuth/AuthManager.cpp new file mode 100644 index 0000000000..0653d3e9e2 --- /dev/null +++ b/dev/OAuth/AuthManager.cpp @@ -0,0 +1,282 @@ +#include +#include "common.h" + +#include "AuthManager.h" +#include + +#include "AuthRequestParams.h" +#include "TokenFailure.h" +#include "TokenRequestParams.h" +#include "TokenRequestResult.h" +#include "TokenResponse.h" + +using namespace winrt::Microsoft::Security::Authentication::OAuth; +using namespace winrt::Windows::Data::Json; +using namespace winrt::Windows::Foundation; +using namespace winrt::Windows::Foundation::Collections; +using namespace winrt::Windows::Web::Http; + +namespace winrt::Microsoft::Security::Authentication::OAuth::factory_implementation +{ + IAsyncOperation AuthManager::InitiateAuthRequestAsync(const Uri& authEndpoint, + const oauth::AuthRequestParams& params) + { + auto asyncOp = winrt::make_self(authEndpoint, + winrt::get_self(params)); + + { + std::lock_guard guard{ m_mutex }; + m_pendingAuthRequests.push_back(AuthRequestState{ params.State(), asyncOp }); + } + + return *asyncOp; + } + + bool AuthManager::CompleteAuthRequest(const Uri& responseUri) + { + // We need to extract the state in order to find the original request + winrt::hstring state; + for (auto&& entry : responseUri.QueryParsed()) + { + if (entry.Name() == L"state") + { + state = entry.Value(); + break; + } + } + + // TODO: If we could not find the state, we need to check the fragment + + // Don't throw an error. It could be the case that the application just blindly calls this function first + if (state.empty()) + { + return false; + } + + // First check in our local pending list + if (try_complete_local(state, responseUri)) + { + return true; + } + + // Not found locally; we need to check to see if the request originated in another process + auto pipeName = request_pipe_name(state); + + // We encrypt the URI using the state as the key. This accomplishes a couple things: (1) it helps protect the + // server from another process attaching and sending bogus data, and (2) it helps protect against sending the + // authorization grant information to the wrong client. Both of these points of course become moot if the bad + // party intercepts the state value, and because the state value is somewhat exposed through the browser launch/ + // URL, these steps are intended more as a defense in depth. Other features such as PKCE should be used to + // ensure that codes/tokens are safe in the event that the state is compromised. + auto encryptedUri = encrypt(responseUri.RawUri(), state); + + // When we create the named pipe, we only allow a single pipe instance. This should be fine under normal + // circumstances, however it might be the case that another process attaches to the pipe. This may be + // innocuous - e.g. the browser did multiple redirects - or it could be a bad actor - e.g. a process sending + // random garbage to any pipe it can open or another process specifically targeting oauth. Therefore we make + // multiple attempts to connect to the pipe + HANDLE pipe = INVALID_HANDLE_VALUE; + while (true) // TODO: Bound this? Need to remember to return false if we do + { + pipe = ::CreateFileW(pipeName.c_str(), GENERIC_WRITE, 0, nullptr, OPEN_EXISTING, 0, nullptr); + if (pipe != INVALID_HANDLE_VALUE) break; + + if (auto err = ::GetLastError(); err != ERROR_PIPE_BUSY) + { + // The pipe no longer exist; e.g. flow already completed, client cancelled, etc. + return false; + } + + if (!::WaitNamedPipeW(pipeName.c_str(), 100)) + { + // 100ms should be enough time to wrap up any business. So either the system is bogged down (perhaps too + // many requests to open the pipe), the pipe was closed, or the pipe was closed and opened by another + // process who isn't being responsive. + return false; + } + } + + ULONG serverPid = 0; + if (::GetNamedPipeServerProcessId(pipe, &serverPid)) + { + ::AllowSetForegroundWindow(serverPid); + + // TODO: We can also possibly verify other things about the server process (exe path, etc.) + } + + DWORD bytesToWrite = encryptedUri.Length(); + DWORD bytesWritten = 0; + if (!::WriteFile(pipe, encryptedUri.data(), bytesToWrite, &bytesWritten, nullptr) || + (bytesWritten != bytesToWrite)) + { + // TODO: Actual error? This could be because the server timed us out... + ::CloseHandle(pipe); + return false; + } + + // The client should have the URI and the operation should be considered handled + ::CloseHandle(pipe); + return true; + } + + IAsyncOperation AuthManager::RequestTokenAsync(Uri tokenEndpoint, + oauth::TokenRequestParams params) + { + return RequestTokenAsync(std::move(tokenEndpoint), std::move(params), nullptr); + } + + IAsyncOperation AuthManager::RequestTokenAsync(Uri tokenEndpoint, + oauth::TokenRequestParams params, oauth::ClientAuthentication clientAuth) + { + auto paramsImpl = winrt::get_self(params); + paramsImpl->finalize(); + + HttpResponseMessage response{ nullptr }; + winrt::hstring responseString; + try + { + HttpClient httpClient; + HttpFormUrlEncodedContent content(winrt::single_threaded_map(paramsImpl->params())); + HttpRequestMessage request(HttpMethod::Post(), tokenEndpoint); + request.Content(HttpFormUrlEncodedContent(winrt::single_threaded_map(paramsImpl->params()))); + + auto headers = request.Headers(); + headers.Accept().ParseAdd(L"application/json"); + + if (auto auth = clientAuth.Authorization()) + { + headers.Authorization(auth); + } + + if (auto proxyAuth = clientAuth.ProxyAuthorization()) + { + headers.ProxyAuthorization(proxyAuth); + } + + if (auto map = clientAuth.AdditionalHeaders()) + { + for (auto&& pair : map) + { + if (!headers.TryAppendWithoutValidation(pair.Key(), pair.Value())) + { + // TODO? Why might this fail? Throw? + } + } + } + + response = co_await httpClient.SendRequestAsync(request); + // TODO: Check status code? + if (!response.IsSuccessStatusCode()) + { + __debugbreak(); // TODO + response.EnsureSuccessStatusCode(); // TODO: Could just use this? + } + + auto responseContentType = response.Content().Headers().ContentType().MediaType(); + if (responseContentType != L"application/json") + { + co_return implementation::TokenRequestResult::MakeFailure(std::move(response), + TokenFailureKind::InvalidResponse, WEB_E_UNSUPPORTED_FORMAT); + } + + responseString = co_await response.Content().ReadAsStringAsync(); + } + catch (...) + { + co_return implementation::TokenRequestResult::MakeFailure(std::move(response), + TokenFailureKind::HttpFailure, winrt::to_hresult()); + } + + JsonObject jsonObject{ nullptr }; + if (!JsonObject::TryParse(responseString, jsonObject)) + { + co_return implementation::TokenRequestResult::MakeFailure(std::move(response), + TokenFailureKind::InvalidResponse, WEB_E_INVALID_JSON_STRING); + } + else + { + try + { + // Determine if it's a success or error response based on the presence of 'error' + if (jsonObject.HasKey(L"error")) + { + auto failure = winrt::make(jsonObject); + co_return winrt::make(std::move(response), nullptr, + std::move(failure)); + } + else + { + auto success = winrt::make(jsonObject); + co_return winrt::make(std::move(response), std::move(success), + nullptr); + } + } + catch (...) + { + co_return implementation::TokenRequestResult::MakeFailure(std::move(response), + TokenFailureKind::InvalidResponse, winrt::to_hresult()); + } + } + } + + bool AuthManager::try_complete_local(const winrt::hstring& state, const foundation::Uri& responseUri) + { + AuthRequestState requestState; + { + std::lock_guard guard{ m_mutex }; + auto itr = std::find_if(m_pendingAuthRequests.begin(), m_pendingAuthRequests.end(), + [&](auto&& entry) { return entry.state == state; }); + + if (itr != m_pendingAuthRequests.end()) + { + requestState = std::move(*itr); + *itr = std::move(m_pendingAuthRequests.back()); + m_pendingAuthRequests.pop_back(); + } + } + + if (requestState.async_op) + { + // Found locally + requestState.async_op->complete(responseUri); + return true; + } + + return false; + } + + void AuthManager::cancel(AuthRequestAsyncOperation* op) + { + auto requestState = try_remove(op); + if (requestState.async_op) + { + requestState.async_op->cancel(); + } + } + + void AuthManager::error(AuthRequestAsyncOperation* op, winrt::hresult hr) + { + auto requestState = try_remove(op); + if (requestState.async_op) + { + requestState.async_op->error(hr); + } + } + + AuthRequestState AuthManager::try_remove(AuthRequestAsyncOperation* op) + { + std::lock_guard guard{ m_mutex }; + auto itr = std::find_if(m_pendingAuthRequests.begin(), m_pendingAuthRequests.end(), + [&](auto&& entry) { return entry.async_op.get() == op; }); + + AuthRequestState result; + if (itr != m_pendingAuthRequests.end()) + { + result = std::move(*itr); + *itr = std::move(m_pendingAuthRequests.back()); + m_pendingAuthRequests.pop_back(); + } + + return result; + } +} diff --git a/dev/OAuth/AuthManager.h b/dev/OAuth/AuthManager.h new file mode 100644 index 0000000000..abe70a11f3 --- /dev/null +++ b/dev/OAuth/AuthManager.h @@ -0,0 +1,72 @@ +#pragma once +#include + +#include "AuthRequestAsyncOperation.h" + +namespace winrt::Microsoft::Security::Authentication::OAuth::implementation +{ + struct AuthManager; +} + +namespace winrt::Microsoft::Security::Authentication::OAuth::factory_implementation +{ + struct AuthRequestState + { + winrt::hstring state; + winrt::com_ptr async_op; + }; + + struct AuthManager : AuthManagerT + { + foundation::IAsyncOperation InitiateAuthRequestAsync( + const foundation::Uri& authEndpoint, const oauth::AuthRequestParams& params); + bool CompleteAuthRequest(const foundation::Uri& responseUri); + foundation::IAsyncOperation RequestTokenAsync(foundation::Uri tokenEndpoint, + oauth::TokenRequestParams params); + foundation::IAsyncOperation RequestTokenAsync(foundation::Uri tokenEndpoint, + oauth::TokenRequestParams params, oauth::ClientAuthentication clientAuth); + + // Implementation functions + bool try_complete_local(const winrt::hstring& state, const foundation::Uri& responseUri); + void cancel(AuthRequestAsyncOperation* op); + void error(AuthRequestAsyncOperation* op, winrt::hresult hr); + + private: + AuthRequestState try_remove(AuthRequestAsyncOperation* op); + + std::shared_mutex m_mutex; + std::vector m_pendingAuthRequests; + }; +} + +namespace winrt::Microsoft::Security::Authentication::OAuth::implementation +{ + struct AuthManager + { + static foundation::IAsyncOperation InitiateAuthRequestAsync( + foundation::Uri authEndpoint, oauth::AuthRequestParams params) + { + return winrt::make_self()->InitiateAuthRequestAsync(authEndpoint, + params); + } + + static bool CompleteAuthRequest(const foundation::Uri& responseUri) + { + return winrt::make_self()->CompleteAuthRequest(responseUri); + } + + static foundation::IAsyncOperation RequestTokenAsync(foundation::Uri tokenEndpoint, + oauth::TokenRequestParams params) + { + return winrt::make_self()->RequestTokenAsync(std::move(tokenEndpoint), + std::move(params)); + } + + static foundation::IAsyncOperation RequestTokenAsync(foundation::Uri tokenEndpoint, + oauth::TokenRequestParams params, oauth::ClientAuthentication clientAuth) + { + return winrt::make_self()->RequestTokenAsync(std::move(tokenEndpoint), + std::move(params), std::move(clientAuth)); + } + }; +} diff --git a/dev/OAuth/AuthRequestAsyncOperation.cpp b/dev/OAuth/AuthRequestAsyncOperation.cpp new file mode 100644 index 0000000000..8466909718 --- /dev/null +++ b/dev/OAuth/AuthRequestAsyncOperation.cpp @@ -0,0 +1,464 @@ +#include +#include "common.h" + +#include "AuthManager.h" +#include "AuthRequestAsyncOperation.h" +#include "AuthRequestResult.h" + +#include + +using namespace std::literals; +using namespace winrt::Microsoft::Security::Authentication::OAuth; +using namespace winrt::Windows::Foundation; +using namespace winrt::Windows::Foundation::Collections; +using namespace winrt::Windows::Security::Cryptography; + +AuthRequestAsyncOperation::AuthRequestAsyncOperation(const Uri& authEndpoint, + implementation::AuthRequestParams* params) : + m_params(params->get_strong()) +{ + try + { + // Calling 'finalize' will (1) prevent subsequent changes from being made to the params, (2) validate + // consistency in the parameters that are set, and (3) throw an exception if 'finalize' was previously called by + // someone else. If no exception is thrown, it signals that this object effectively owns the request parameters + // and is able to read and set necessary properties without fear of them being modified by another call + m_params->finalize(); + + if ((m_params->CodeChallengeMethod() != CodeChallengeMethodKind::None) && m_params->CodeVerifier().empty()) + { + m_params->set_code_verifier(winrt::hstring{ random_base64urlencoded_string(32) }); + } + + if (m_params->State().empty()) + { + while (true) + { + winrt::hstring state{ random_base64urlencoded_string(32) }; + if (try_create_pipe(state)) + { + m_params->set_state(state); + break; + } + + // 'FILE_FLAG_FIRST_PIPE_INSTANCE' is documented as failing with 'ERROR_ACCESS_DENIED' if a pipe + // with the same name has already been created. + if (auto err = ::GetLastError(); err != ERROR_ACCESS_DENIED) + { + throw winrt::hresult_error(HRESULT_FROM_WIN32(err), + L"Generation of a unique state value unexpectedly failed"); + } + } + } + else if (!try_create_pipe(m_params->State())) + { + auto err = ::GetLastError(); + auto msg = + (err == ERROR_ACCESS_DENIED) ? L"Provided state value is not unique" : L"Failed to create named pipe"; + throw winrt::hresult_error(HRESULT_FROM_WIN32(err), msg); + } + + m_overlapped.hEvent = ::CreateEventW(nullptr, true, false, nullptr); + if (!m_overlapped.hEvent) + { + throw winrt::hresult_error(HRESULT_FROM_WIN32(::GetLastError()), L"Failed to create an event"); + } + + m_ptp = ::CreateThreadpoolWait(async_callback, this, nullptr); + connect_to_new_client(); + + // Pipe server has been successfully set up. Initiate the launch + auto url = m_params->create_url(authEndpoint); + + auto launchResult = ::ShellExecuteW(nullptr, L"open", url.c_str(), nullptr, nullptr, SW_SHOWDEFAULT); + if (auto code = reinterpret_cast(launchResult); code < 32) + { + throw winrt::hresult_error(static_cast(code), L"Failed to launch browser"); + } + } + catch (...) + { + // Throwing in a constructor will cause the destructor not to run... + close_pipe(); + throw; + } +} + +AuthRequestAsyncOperation::~AuthRequestAsyncOperation() +{ + close_pipe(); +} + +void AuthRequestAsyncOperation::close_pipe() +{ + if (m_state == state::reading) + { + // TODO: Might this trigger a callback? If so, we may end up trying to perform more operations... Might need + // some synchronization and another state value here. + ::CancelIoEx(m_pipe, &m_overlapped); + } + + if (m_ptp) + { + if (!::SetThreadpoolWaitEx(m_ptp, nullptr, nullptr, nullptr)) + { + // False here means that there's a callback in progress. This would realistically only happen if there was + // a race between the client calling 'Cancel' and someone connecting to the pipe + ::WaitForThreadpoolWaitCallbacks(m_ptp, true); + } + + ::CloseThreadpoolWait(m_ptp); + m_ptp = nullptr; + } + + if (m_overlapped.hEvent) + { + ::CloseHandle(m_overlapped.hEvent); + m_overlapped.hEvent = nullptr; + } + + if (m_pipe != INVALID_HANDLE_VALUE) + { + ::CloseHandle(m_pipe); + m_pipe = INVALID_HANDLE_VALUE; + } +} + +winrt::hresult AuthRequestAsyncOperation::ErrorCode() +{ + std::shared_lock guard{ m_mutex }; + return m_error; +} + +uint32_t AuthRequestAsyncOperation::Id() +{ + return 1; // NOTE: This is copying the C++/WinRT implementation +} + +winrt::Windows::Foundation::AsyncStatus AuthRequestAsyncOperation::Status() +{ + std::shared_lock guard{ m_mutex }; + return m_status; +} + +void AuthRequestAsyncOperation::Cancel() +{ + winrt::make_self()->cancel(this); +} + +void AuthRequestAsyncOperation::Close() +{ + // TODO? C++/WinRT does a noop here +} + +AsyncOperationCompletedHandler AuthRequestAsyncOperation::Completed() +{ + std::shared_lock guard{ m_mutex }; + return m_handler; +} + +void AuthRequestAsyncOperation::Completed(const AsyncOperationCompletedHandler& handler) +{ + bool shouldInvoke = false; + { + std::lock_guard guard{ m_mutex }; + if (m_handlerSet) + { + throw winrt::hresult_illegal_delegate_assignment(); + } + + m_handlerSet = true; + if (!handler) + { + WINRT_ASSERT(!m_handler); + return; + } + + if (m_status != AsyncStatus::Started) + { + shouldInvoke = true; + } + else if (handler.try_as<::IAgileObject>()) + { + m_handler = handler; + } + else + { + try + { + auto ref = winrt::make_agile(handler); + m_handler = [ref = std::move(ref)](const IAsyncOperation& op, AsyncStatus status) { + ref.get()(op, status); + }; + } + catch (...) + { + m_handler = handler; + } + } + } + + if (shouldInvoke) + { + invoke_handler(handler); + } +} + +AuthRequestResult AuthRequestAsyncOperation::GetResults() +{ + std::shared_lock guard{ m_mutex }; + if (m_status == AsyncStatus::Completed) + { + return m_result; + } + else if (m_error < 0) + { + throw winrt::hresult_error(m_error); + } + + WINRT_ASSERT(m_status == AsyncStatus::Started); + throw winrt::hresult_illegal_method_call(); +} + +void AuthRequestAsyncOperation::complete(const Uri& responseUri) +{ + transition_state(AsyncStatus::Completed, responseUri); +} + +void AuthRequestAsyncOperation::cancel() +{ + transition_state(AsyncStatus::Canceled, nullptr, HRESULT_FROM_WIN32(ERROR_CANCELLED)); +} + +void AuthRequestAsyncOperation::error(winrt::hresult hr) +{ + transition_state(AsyncStatus::Error, nullptr, hr); +} + +void AuthRequestAsyncOperation::transition_state(AsyncStatus status, const Uri& responseUri, winrt::hresult hr) +{ + AsyncOperationCompletedHandler handler; + { + std::lock_guard guard{ m_mutex }; + // TODO: Should probably close pipe, however we might be in a callback which would deadlock... + + // State change is initiated by AuthManager and should never happen twice + WINRT_ASSERT(m_status == AsyncStatus::Started); + m_status = status; + m_error = hr; + + if (responseUri) + { + WINRT_ASSERT(hr >= 0); + m_result = winrt::make(m_params.get(), responseUri); + } + else + { + WINRT_ASSERT(hr < 0); + } + + handler = m_handler; + } + + if (handler) + { + invoke_handler(handler); + } +} + +void CALLBACK AuthRequestAsyncOperation::async_callback(PTP_CALLBACK_INSTANCE, PVOID context, PTP_WAIT, + TP_WAIT_RESULT waitResult) +{ + auto pThis = static_cast(context); + + try + { + DWORD bytes = 0; + DWORD overlappedError = ERROR_SUCCESS; + if (waitResult == WAIT_OBJECT_0) + { + if (!::GetOverlappedResult(pThis->m_pipe, &pThis->m_overlapped, &bytes, false)) + { + overlappedError = ::GetLastError(); + } + } + + switch (pThis->m_state) + { + case state::connecting: { + WINRT_ASSERT(waitResult == WAIT_OBJECT_0); // TODO: Is this valid? Maybe when we cancelled? Error? + if (waitResult != WAIT_OBJECT_0) + { + WINRT_ASSERT(waitResult == WAIT_TIMEOUT); + throw winrt::hresult_error(HRESULT_FROM_WIN32(ERROR_TIMEOUT), + L"Timed out waiting for a client to connect to the pipe"); + } + else if (overlappedError != ERROR_SUCCESS) + { + // If ConnectNamedClient failed, assume we hit an unrecoverable failure + throw winrt::hresult_error(HRESULT_FROM_WIN32(overlappedError), + L"Failed waiting for a client to connect to the pipe"); + } + + pThis->initiate_read(); + } + break; + + case state::reading: { + if (overlappedError == ERROR_MORE_DATA) + { + pThis->m_pipeReadData.insert(pThis->m_pipeReadData.end(), pThis->m_pipeReadBuffer, + pThis->m_pipeReadBuffer + pThis->m_overlapped.InternalHigh); + pThis->initiate_read(); // Need more data before we can complete + } + else if ((waitResult != WAIT_OBJECT_0) || (overlappedError != ERROR_SUCCESS)) + { + // Ideally we could assume that read timeouts/failures are fatal, however we don't know if the client is + // trustworthy and we don't want some arbitrary process to bait us into terminating the request + [[maybe_unused]] auto disconnectResult = ::DisconnectNamedPipe(pThis->m_pipe); + WINRT_ASSERT(disconnectResult); // TODO: What if the client disconnected from us? + pThis->connect_to_new_client(); + } + else + { + pThis->on_read_complete(); + } + } + break; + + default: + WINRT_ASSERT(false); + throw winrt::hresult_error(E_UNEXPECTED, L"Unexpected failure waiting for AuthRequest result"); + break; + } + } + catch (...) + { + winrt::make_self()->error(pThis, winrt::to_hresult()); + } +} + +bool AuthRequestAsyncOperation::try_create_pipe(const winrt::hstring& state) +{ + auto name = request_pipe_name(state); + m_pipe = + ::CreateNamedPipeW(name.c_str(), PIPE_ACCESS_INBOUND | FILE_FLAG_FIRST_PIPE_INSTANCE | FILE_FLAG_OVERLAPPED, + PIPE_TYPE_MESSAGE | PIPE_READMODE_MESSAGE | PIPE_REJECT_REMOTE_CLIENTS, 1, 1024, 1024, 0, nullptr); + + if (m_pipe != INVALID_HANDLE_VALUE) + { + m_pipeName = std::move(name); + return true; + } + + return false; +} + +void AuthRequestAsyncOperation::connect_to_new_client() +{ + m_pipeReadData.clear(); + + [[maybe_unused]] auto connectResult = ::ConnectNamedPipe(m_pipe, &m_overlapped); + WINRT_ASSERT(!connectResult); // Only non-zero in synchronous mode, even if already connected + if (auto err = ::GetLastError(); err == ERROR_PIPE_CONNECTED) + { + // Client already connected + initiate_read(); + } + else if (err != ERROR_IO_PENDING) + { + throw winrt::hresult_error(HRESULT_FROM_WIN32(err), L"Failed to listen for clients on the pipe"); + } + else + { + m_state = state::connecting; + ::SetThreadpoolWait(m_ptp, m_overlapped.hEvent, nullptr); + } +} + +void AuthRequestAsyncOperation::initiate_read() +{ + while (true) + { + if (::ReadFile(m_pipe, m_pipeReadBuffer, sizeof(m_pipeReadBuffer), nullptr, &m_overlapped)) + { + // Immediate success. No need to wait + on_read_complete(); + break; + } + + auto err = ::GetLastError(); + if (err == ERROR_MORE_DATA) + { + // Partial read successful; save data and continue loop to try and read more data + m_pipeReadData.insert(m_pipeReadData.end(), m_pipeReadBuffer, m_pipeReadBuffer + m_overlapped.InternalHigh); + } + else if (err == ERROR_IO_PENDING) + { + // Reading asynchronously + m_state = state::reading; + std::int64_t timeout = std::chrono::duration_cast(-50ms).count(); // 50ms timeout + ::SetThreadpoolWait(m_ptp, m_overlapped.hEvent, reinterpret_cast(&timeout)); + break; + } + else + { + // Ideally we could assume that read timeouts/failures are fatal, however we don't know if the client is + // trustworthy and we don't want some arbitrary process to bait us into terminating the request + [[maybe_unused]] auto disconnectResult = ::DisconnectNamedPipe(m_pipe); + WINRT_ASSERT(disconnectResult); // TODO: What if the client disconnected from us? + connect_to_new_client(); + break; + } + } +} + +void AuthRequestAsyncOperation::on_read_complete() +{ + m_pipeReadData.insert(m_pipeReadData.end(), m_pipeReadBuffer, m_pipeReadBuffer + m_overlapped.InternalHigh); + + bool shouldReconnect = true; + try + { + auto expectedState = m_params->State(); + auto encryptedBuffer = CryptographicBuffer::CreateFromByteArray(m_pipeReadData); + auto uriString = decrypt(encryptedBuffer, expectedState); + Uri responseUri(uriString); + + // An exception is unlikely (we needed the state from the URI to open the pipe in the first place), but could + // happen if someone is connecting and sending garbage data. We'll catch below, so all is okay + auto state = responseUri.QueryParsed().GetFirstValueByName(L"state"); + if (state == expectedState) + { + if (winrt::make_self()->try_complete_local(state, responseUri)) + { + shouldReconnect = false; + } + } + } + catch (...) + { + // Likely handed bad data; just disconnect and attempt a reconnect + } + + // The client will only ever send a single message, so disconnect and form a new connection + [[maybe_unused]] auto disconnectResult = ::DisconnectNamedPipe(m_pipe); + WINRT_ASSERT(disconnectResult); // TODO + + if (shouldReconnect) + { + connect_to_new_client(); + } +} + +void AuthRequestAsyncOperation::invoke_handler(const AsyncOperationCompletedHandler& handler) +{ + try + { + handler(*this, m_status); + } + catch (...) + { + // Just eat exceptions as they're not relevant to the caller at all + } +} diff --git a/dev/OAuth/AuthRequestAsyncOperation.h b/dev/OAuth/AuthRequestAsyncOperation.h new file mode 100644 index 0000000000..a1ece2c894 --- /dev/null +++ b/dev/OAuth/AuthRequestAsyncOperation.h @@ -0,0 +1,65 @@ +#pragma once + +#include "AuthRequestParams.h" + +struct AuthRequestAsyncOperation : + winrt::implements, + foundation::IAsyncInfo> +{ + AuthRequestAsyncOperation(const foundation::Uri& authEndpoint, oauth::implementation::AuthRequestParams* params); + ~AuthRequestAsyncOperation(); + + // IAsyncInfo + winrt::hresult ErrorCode(); + uint32_t Id(); + foundation::AsyncStatus Status(); + void Cancel(); + void Close(); + + // IAsyncOperation + foundation::AsyncOperationCompletedHandler Completed(); + void Completed(const foundation::AsyncOperationCompletedHandler& handler); + oauth::AuthRequestResult GetResults(); + + // Internal functions + void complete(const foundation::Uri& responseUri); + void cancel(); + void error(winrt::hresult hr); + +private: + enum class state + { + connecting, + reading, + }; + + static void CALLBACK async_callback(PTP_CALLBACK_INSTANCE, PVOID context, PTP_WAIT, TP_WAIT_RESULT waitResult); + + bool try_create_pipe(const winrt::hstring& state); + void close_pipe(); + void connect_to_new_client(); + void initiate_read(); + void on_read_complete(); + + void transition_state(foundation::AsyncStatus status, const foundation::Uri& responseUri = nullptr, + winrt::hresult hr = {}); + void invoke_handler(const foundation::AsyncOperationCompletedHandler& handler); + + std::shared_mutex m_mutex; + + winrt::com_ptr m_params; + std::wstring m_pipeName; + HANDLE m_pipe = INVALID_HANDLE_VALUE; + state m_state = state::connecting; + OVERLAPPED m_overlapped = {}; + PTP_WAIT m_ptp = nullptr; + std::vector m_pipeReadData; + std::uint8_t m_pipeReadBuffer[128]; + + // IAsyncOperation state + oauth::AuthRequestResult m_result{ nullptr }; + bool m_handlerSet = false; + foundation::AsyncOperationCompletedHandler m_handler; + foundation::AsyncStatus m_status = foundation::AsyncStatus::Started; + winrt::hresult m_error = {}; +}; diff --git a/dev/OAuth/AuthRequestParams.cpp b/dev/OAuth/AuthRequestParams.cpp new file mode 100644 index 0000000000..c60e4adf3e --- /dev/null +++ b/dev/OAuth/AuthRequestParams.cpp @@ -0,0 +1,263 @@ +#include +#include "common.h" + +#include "AuthRequestParams.h" +#include + +using namespace winrt::Microsoft::Security::Authentication::OAuth; +using namespace winrt::Windows::Foundation; +using namespace winrt::Windows::Foundation::Collections; +using namespace winrt::Windows::Security::Cryptography; +using namespace winrt::Windows::Security::Cryptography::Core; + +namespace winrt::Microsoft::Security::Authentication::OAuth::implementation +{ + AuthRequestParams::AuthRequestParams(const winrt::hstring& responseType, const winrt::hstring& clientId) : + m_responseType(responseType), + m_clientId(clientId) + { + } + + AuthRequestParams::AuthRequestParams(const winrt::hstring& responseType, const winrt::hstring& clientId, + const Uri& redirectUri) : + m_responseType(responseType), + m_clientId(clientId), + m_redirectUri(redirectUri) + { + } + + oauth::AuthRequestParams AuthRequestParams::CreateForAuthorizationCodeRequest(const winrt::hstring& clientId) + { + return winrt::make(L"code", clientId); + } + + oauth::AuthRequestParams AuthRequestParams::CreateForAuthorizationCodeRequest(const winrt::hstring& clientId, + const Uri& redirectUri) + { + return winrt::make(L"code", clientId, redirectUri); + } + + oauth::AuthRequestParams AuthRequestParams::CreateForImplicitRequest(const winrt::hstring& clientId) + { + return winrt::make(L"token", clientId); + } + + oauth::AuthRequestParams AuthRequestParams::CreateForImplicitRequest(const winrt::hstring& clientId, + const Uri& redirectUri) + { + return winrt::make(L"token", clientId, redirectUri); + } + + winrt::hstring AuthRequestParams::ResponseType() + { + std::shared_lock guard{ m_mutex }; + return m_responseType; + } + + void AuthRequestParams::ResponseType(const winrt::hstring& value) + { + std::lock_guard guard{ m_mutex }; + check_not_finalized(); + m_responseType = value; + } + + winrt::hstring AuthRequestParams::ClientId() + { + std::shared_lock guard{ m_mutex }; + return m_clientId; + } + + void AuthRequestParams::ClientId(const winrt::hstring& value) + { + std::lock_guard guard{ m_mutex }; + check_not_finalized(); + m_clientId = value; + } + + Uri AuthRequestParams::RedirectUri() + { + std::shared_lock guard{ m_mutex }; + return m_redirectUri; + } + + void AuthRequestParams::RedirectUri(const Uri& value) + { + std::lock_guard guard{ m_mutex }; + check_not_finalized(); + m_redirectUri = value; + } + + winrt::hstring AuthRequestParams::State() + { + std::shared_lock guard{ m_mutex }; + return m_state; + } + + void AuthRequestParams::State(const winrt::hstring& value) + { + std::lock_guard guard{ m_mutex }; + check_not_finalized(); + m_state = value; + } + + winrt::hstring AuthRequestParams::Scope() + { + std::shared_lock guard{ m_mutex }; + return m_scope; + } + + void AuthRequestParams::Scope(const winrt::hstring& value) + { + std::lock_guard guard{ m_mutex }; + check_not_finalized(); + m_scope = value; + } + + winrt::hstring AuthRequestParams::CodeVerifier() + { + std::shared_lock guard{ m_mutex }; + return m_codeVerifier; + } + + void AuthRequestParams::CodeVerifier(const winrt::hstring& value) + { + std::lock_guard guard{ m_mutex }; + check_not_finalized(); + m_codeVerifier = value; + } + + CodeChallengeMethodKind AuthRequestParams::CodeChallengeMethod() + { + std::shared_lock guard{ m_mutex }; + return m_codeChallengeMethod; + } + + void AuthRequestParams::CodeChallengeMethod(CodeChallengeMethodKind value) + { + std::lock_guard guard{ m_mutex }; + check_not_finalized(); + m_codeChallengeMethod = value; + } + + IMap AuthRequestParams::AdditionalParams() + { + std::shared_lock guard{ m_mutex }; + return m_additionalParams; + } + + void AuthRequestParams::AdditionalParams(const IMap& value) + { + std::lock_guard guard{ m_mutex }; + check_not_finalized(); + m_additionalParams = value; + } + + void AuthRequestParams::finalize() + { + std::lock_guard guard{ m_mutex }; + if (m_finalized) + { + throw winrt::hresult_illegal_method_call(L"AuthRequestParams can only be used for a single request call"); + } + + m_finalized = true; + + if (!m_codeVerifier.empty() && (m_codeChallengeMethod == CodeChallengeMethodKind::None)) + { + throw winrt::hresult_illegal_method_call( + L"'CodeVerifier' cannot be set when 'CodeChallengeMethod' is set to 'None'"); + } + } + + void AuthRequestParams::set_state(winrt::hstring value) + { + std::lock_guard guard{ m_mutex }; + WINRT_ASSERT(m_state.empty()); + m_state = std::move(value); + } + + void AuthRequestParams::set_code_verifier(winrt::hstring value) + { + std::lock_guard guard{ m_mutex }; + WINRT_ASSERT(m_codeVerifier.empty()); + WINRT_ASSERT(m_codeChallengeMethod != CodeChallengeMethodKind::None); + m_codeVerifier = std::move(value); + } + + std::wstring AuthRequestParams::create_url(const Uri& authEndpoint) + { + std::shared_lock guard{ m_mutex }; + WINRT_ASSERT(m_finalized); + + // Per RFC 6749 section 3.1, the auth endpoint URI *MAY* contain a query string, which must be retained + std::wstring result{ authEndpoint.RawUri() }; + if (authEndpoint.Query().empty()) + { + result += L"?state="; + } + else + { + result += L"&state="; + } + + result += Uri::EscapeComponent(m_state); + + if (!m_responseType.empty()) + { + result += L"&response_type="; + result += Uri::EscapeComponent(m_responseType); + } + + if (!m_clientId.empty()) + { + result += L"&client_id="; + result += Uri::EscapeComponent(m_clientId); + } + + if (m_redirectUri) + { + result += L"&redirect_uri="; + result += Uri::EscapeComponent(m_redirectUri.RawUri()); + } + + if (!m_scope.empty()) + { + result += L"&scope="; + result += Uri::EscapeComponent(m_scope); + } + + if (m_codeChallengeMethod == CodeChallengeMethodKind::S256) + { + result += L"&code_challenge_method=S256&code_challenge="; + result += base64urlencode(sha256(m_codeVerifier)); + } + else if (m_codeChallengeMethod == CodeChallengeMethodKind::Plain) + { + result += L"&code_challenge_method=plain&code_challenge="; + result += Uri::EscapeComponent(m_codeVerifier); + } + + if (m_additionalParams) + { + for (auto&& pair : m_additionalParams) + { + result += L"&"; + result += Uri::EscapeComponent(pair.Key()); + result += L"="; + result += Uri::EscapeComponent(pair.Value()); + } + } + + return result; + } + + void AuthRequestParams::check_not_finalized() + { + // NOTE: Lock should be held when calling + if (m_finalized) + { + throw winrt::hresult_illegal_method_call( + L"AuthRequestParams object cannot be modified after being used to initiate a request"); + } + } +} diff --git a/dev/OAuth/AuthRequestParams.h b/dev/OAuth/AuthRequestParams.h new file mode 100644 index 0000000000..31b2fc72ae --- /dev/null +++ b/dev/OAuth/AuthRequestParams.h @@ -0,0 +1,67 @@ +#pragma once +#include + +#include + +namespace winrt::Microsoft::Security::Authentication::OAuth::implementation +{ + struct AuthRequestParams : AuthRequestParamsT + { + AuthRequestParams(const winrt::hstring& responseType, const winrt::hstring& clientId); + AuthRequestParams(const winrt::hstring& responseType, const winrt::hstring& clientId, + const foundation::Uri& redirectUri); + + static oauth::AuthRequestParams CreateForAuthorizationCodeRequest(const winrt::hstring& clientId); + static oauth::AuthRequestParams CreateForAuthorizationCodeRequest(const winrt::hstring& clientId, + const foundation::Uri& redirectUri); + static oauth::AuthRequestParams CreateForImplicitRequest(const winrt::hstring& clientId); + static oauth::AuthRequestParams CreateForImplicitRequest(const winrt::hstring& clientId, + const foundation::Uri& redirectUri); + + // Interface functions + winrt::hstring ResponseType(); + void ResponseType(const winrt::hstring& value); + winrt::hstring ClientId(); + void ClientId(const winrt::hstring& value); + foundation::Uri RedirectUri(); + void RedirectUri(const foundation::Uri& value); + winrt::hstring State(); + void State(const winrt::hstring& value); + winrt::hstring Scope(); + void Scope(const winrt::hstring& value); + winrt::hstring CodeVerifier(); + void CodeVerifier(const winrt::hstring& value); + oauth::CodeChallengeMethodKind CodeChallengeMethod(); + void CodeChallengeMethod(oauth::CodeChallengeMethodKind value); + collections::IMap AdditionalParams(); + void AdditionalParams(const collections::IMap& value); + + // Implementation functions + void finalize(); + void set_state(winrt::hstring value); + void set_code_verifier(winrt::hstring value); + std::wstring create_url(const foundation::Uri& authEndpoint); + + private: + void check_not_finalized(); + + std::shared_mutex m_mutex; + bool m_finalized = false; + winrt::hstring m_responseType; + winrt::hstring m_clientId; + foundation::Uri m_redirectUri{ nullptr }; + winrt::hstring m_state; + winrt::hstring m_scope; + winrt::hstring m_codeVerifier; + oauth::CodeChallengeMethodKind m_codeChallengeMethod; + collections::IMap m_additionalParams = + winrt::multi_threaded_map(); + }; +} + +namespace winrt::Microsoft::Security::Authentication::OAuth::factory_implementation +{ + struct AuthRequestParams : AuthRequestParamsT + { + }; +} diff --git a/dev/OAuth/AuthRequestResult.cpp b/dev/OAuth/AuthRequestResult.cpp new file mode 100644 index 0000000000..eb6960abf0 --- /dev/null +++ b/dev/OAuth/AuthRequestResult.cpp @@ -0,0 +1,67 @@ +#include +#include "common.h" + +#include "AuthRequestResult.h" +#include + +#include "AuthFailure.h" +#include "AuthResponse.h" + +using namespace winrt::Microsoft::Security::Authentication::OAuth; +using namespace winrt::Windows::Foundation; +using namespace winrt::Windows::Foundation::Collections; + +namespace winrt::Microsoft::Security::Authentication::OAuth::implementation +{ + AuthRequestResult::AuthRequestResult(AuthRequestParams* params, const Uri& responseUri) : m_responseUri(responseUri) + { + // We first need to figure out if this is a success or failure response + bool isError = false; + bool isSuccess = false; + for (auto&& entry : m_responseUri.QueryParsed()) + { + auto name = entry.Name(); + if ((name == L"code") || (name == L"access_token")) + { + isSuccess = true; + break; + } + else if (name == L"error") + { + isError = true; + break; + } + } + + if (!isError && !isSuccess) + { + // TODO: May also need to check the fragment + } + + // If we don't recognize the response as an error, interpret it as success. The application may be using an + // extension that we don't recognize + if (isError) + { + m_failure = winrt::make(m_responseUri); + } + else + { + m_response = winrt::make(params, m_responseUri); + } + } + + Uri AuthRequestResult::ResponseUri() + { + return m_responseUri; + } + + oauth::AuthResponse AuthRequestResult::Response() + { + return m_response; + } + + oauth::AuthFailure AuthRequestResult::Failure() + { + return m_failure; + } +} diff --git a/dev/OAuth/AuthRequestResult.h b/dev/OAuth/AuthRequestResult.h new file mode 100644 index 0000000000..62d4470790 --- /dev/null +++ b/dev/OAuth/AuthRequestResult.h @@ -0,0 +1,21 @@ +#pragma once +#include + +#include "AuthRequestParams.h" + +namespace winrt::Microsoft::Security::Authentication::OAuth::implementation +{ + struct AuthRequestResult : AuthRequestResultT + { + AuthRequestResult(AuthRequestParams* params, const foundation::Uri& responseUri); + + foundation::Uri ResponseUri(); + oauth::AuthResponse Response(); + oauth::AuthFailure Failure(); + + private: + foundation::Uri m_responseUri; + oauth::AuthResponse m_response{ nullptr }; + oauth::AuthFailure m_failure{ nullptr }; + }; +} diff --git a/dev/OAuth/AuthResponse.cpp b/dev/OAuth/AuthResponse.cpp new file mode 100644 index 0000000000..880fe9f488 --- /dev/null +++ b/dev/OAuth/AuthResponse.cpp @@ -0,0 +1,91 @@ +#include +#include "common.h" + +#include "AuthResponse.h" +#include + +using namespace std::literals; +using namespace winrt::Microsoft::Security::Authentication::OAuth; +using namespace winrt::Windows::Foundation; +using namespace winrt::Windows::Foundation::Collections; + +namespace winrt::Microsoft::Security::Authentication::OAuth::implementation +{ + AuthResponse::AuthResponse(AuthRequestParams* requestParams, const Uri& responseUri) : + m_requestParams(requestParams->get_strong()) + { + std::map additionalParams; + + for (auto&& entry : responseUri.QueryParsed()) + { + auto name = entry.Name(); + if (name == L"state"sv) + { + m_state = entry.Value(); + } + else if (name == L"code"sv) + { + m_code = entry.Value(); + } + else if (name == L"access_token"sv) + { + m_accessToken = entry.Value(); + } + else if (name == L"token_type"sv) + { + m_tokenType = entry.Value(); + } + else if (name == L"expires_in"sv) + { + m_expiresIn = entry.Value(); + } + else if (name == L"scope"sv) + { + m_scope = entry.Value(); + } + else + { + additionalParams.emplace(std::move(name), entry.Value()); + } + } + + // TODO: Look in the fragment part as well + + m_additionalParams = winrt::single_threaded_map(std::move(additionalParams)).GetView(); + } + + winrt::hstring AuthResponse::State() + { + return m_state; + } + + winrt::hstring AuthResponse::Code() + { + return m_code; + } + + winrt::hstring AuthResponse::AccessToken() + { + return m_accessToken; + } + + winrt::hstring AuthResponse::TokenType() + { + return m_tokenType; + } + + winrt::hstring AuthResponse::ExpiresIn() + { + return m_expiresIn; + } + + winrt::hstring AuthResponse::Scope() + { + return m_scope; + } + + IMapView AuthResponse::AdditionalParams() + { + return m_additionalParams; + } +} diff --git a/dev/OAuth/AuthResponse.h b/dev/OAuth/AuthResponse.h new file mode 100644 index 0000000000..1623c4476a --- /dev/null +++ b/dev/OAuth/AuthResponse.h @@ -0,0 +1,37 @@ +#pragma once +#include + +#include "AuthRequestParams.h" + +namespace winrt::Microsoft::Security::Authentication::OAuth::implementation +{ + struct AuthResponse : AuthResponseT + { + AuthResponse(AuthRequestParams* params, const foundation::Uri& responseUri); + + winrt::hstring State(); + winrt::hstring Code(); + winrt::hstring AccessToken(); + winrt::hstring TokenType(); + winrt::hstring ExpiresIn(); + winrt::hstring Scope(); + collections::IMapView AdditionalParams(); + + // Implementation functions + const winrt::com_ptr& request_params() const noexcept + { + return m_requestParams; + } + + private: + winrt::com_ptr m_requestParams; + + winrt::hstring m_state; + winrt::hstring m_code; + winrt::hstring m_accessToken; + winrt::hstring m_tokenType; + winrt::hstring m_expiresIn; + winrt::hstring m_scope; + collections::IMapView m_additionalParams; + }; +} diff --git a/dev/OAuth/ClientAuthentication.cpp b/dev/OAuth/ClientAuthentication.cpp new file mode 100644 index 0000000000..bbbdec1a76 --- /dev/null +++ b/dev/OAuth/ClientAuthentication.cpp @@ -0,0 +1,65 @@ +#include +#include "common.h" + +#include "ClientAuthentication.h" +#include + +using namespace winrt::Microsoft::Security::Authentication::OAuth; +using namespace winrt::Windows::Foundation; +using namespace winrt::Windows::Foundation::Collections; +using namespace winrt::Windows::Security::Cryptography; +using namespace winrt::Windows::Web::Http::Headers; + +namespace winrt::Microsoft::Security::Authentication::OAuth::implementation +{ + ClientAuthentication::ClientAuthentication(const HttpCredentialsHeaderValue& authorization) : + m_authorization(authorization) + { + } + + oauth::ClientAuthentication ClientAuthentication::CreateForBasicAuthorization(const winrt::hstring& clientId, + const winrt::hstring& clientSecret) + { + auto authString = clientId + L":" + clientSecret; + auto buffer = CryptographicBuffer::ConvertStringToBinary(authString, BinaryStringEncoding::Utf8); + auto base64Token = CryptographicBuffer::EncodeToBase64String(buffer); + HttpCredentialsHeaderValue header(L"Basic", base64Token); + return winrt::make(header); + } + + HttpCredentialsHeaderValue ClientAuthentication::Authorization() + { + std::shared_lock guard{ m_mutex }; + return m_authorization; + } + + void ClientAuthentication::Authorization(const HttpCredentialsHeaderValue& value) + { + std::lock_guard guard{ m_mutex }; + m_authorization = value; + } + + HttpCredentialsHeaderValue ClientAuthentication::ProxyAuthorization() + { + std::shared_lock guard{ m_mutex }; + return m_proxyAuthorization; + } + + void ClientAuthentication::ProxyAuthorization(const HttpCredentialsHeaderValue& value) + { + std::lock_guard guard{ m_mutex }; + m_proxyAuthorization = value; + } + + winrt::Windows::Foundation::Collections::IMap ClientAuthentication::AdditionalHeaders() + { + std::shared_lock guard{ m_mutex }; + return m_additionalHeaders; + } + + void ClientAuthentication::AdditionalHeaders(winrt::Windows::Foundation::Collections::IMap const& value) + { + std::lock_guard guard{ m_mutex }; + m_additionalHeaders = value; + } +} diff --git a/dev/OAuth/ClientAuthentication.h b/dev/OAuth/ClientAuthentication.h new file mode 100644 index 0000000000..d736a0fb4a --- /dev/null +++ b/dev/OAuth/ClientAuthentication.h @@ -0,0 +1,36 @@ +#pragma once +#include + +#include + +namespace winrt::Microsoft::Security::Authentication::OAuth::implementation +{ + struct ClientAuthentication : ClientAuthenticationT + { + ClientAuthentication() = default; + ClientAuthentication(http::Headers::HttpCredentialsHeaderValue const& authorization); + + static oauth::ClientAuthentication CreateForBasicAuthorization(const winrt::hstring& clientId, + const winrt::hstring& clientSecret); + + http::Headers::HttpCredentialsHeaderValue Authorization(); + void Authorization(http::Headers::HttpCredentialsHeaderValue const& value); + http::Headers::HttpCredentialsHeaderValue ProxyAuthorization(); + void ProxyAuthorization(http::Headers::HttpCredentialsHeaderValue const& value); + collections::IMap AdditionalHeaders(); + void AdditionalHeaders(collections::IMap const& value); + + private: + std::shared_mutex m_mutex; + http::Headers::HttpCredentialsHeaderValue m_authorization{ nullptr }; + http::Headers::HttpCredentialsHeaderValue m_proxyAuthorization{ nullptr }; + collections::IMap m_additionalHeaders = + winrt::multi_threaded_map(); + }; +} +namespace winrt::Microsoft::Security::Authentication::OAuth::factory_implementation +{ + struct ClientAuthentication : ClientAuthenticationT + { + }; +} diff --git a/dev/OAuth/Crypto.h b/dev/OAuth/Crypto.h new file mode 100644 index 0000000000..a6d8ed488c --- /dev/null +++ b/dev/OAuth/Crypto.h @@ -0,0 +1,87 @@ +// Helpers using the cryptographic APIs +#pragma once + +#include + +inline std::wstring base64urlencode(const streams::IBuffer& buffer) +{ + using namespace winrt::Windows::Security::Cryptography; + + std::wstring result; + auto base64 = CryptographicBuffer::EncodeToBase64String(buffer); + result.reserve(base64.size()); + + for (auto ch : base64) + { + switch (ch) + { + case '+': result.push_back('-'); break; + case '/': result.push_back('_'); break; + case '=': break; // No padding + default: result.push_back(ch); break; + } + } + + return result; +} + +inline std::wstring random_base64urlencoded_string(std::uint32_t octets) +{ + using namespace winrt::Windows::Security::Cryptography; + auto buffer = CryptographicBuffer::GenerateRandom(octets); + return base64urlencode(buffer); +} + +inline streams::IBuffer sha256(const winrt::hstring& text, + crypto::BinaryStringEncoding encoding = crypto::BinaryStringEncoding::Utf8) +{ + using namespace winrt::Windows::Security::Cryptography; + using namespace winrt::Windows::Security::Cryptography::Core; + + auto algo = HashAlgorithmProvider::OpenAlgorithm(HashAlgorithmNames::Sha256()); + return CryptographicBuffer::ConvertStringToBinary(text, encoding); +} + +inline winrt::hstring sha256_base64encoded(const winrt::hstring& text) +{ + auto buffer = sha256(text); + return crypto::CryptographicBuffer::EncodeToBase64String(buffer); +} + +inline std::wstring request_pipe_name(const winrt::hstring& state) +{ + // In order to try and protect the state and auth code, we use a hash of the state value for the pipe name + std::wstring result = LR"^-^(\\.\pipe\oauth\)^-^"; + result += sha256_base64encoded(state); + return result; +} + +inline crypto::Core::CryptographicKey create_key(const winrt::hstring& keyString) +{ + using namespace winrt::Windows::Security::Cryptography; + using namespace winrt::Windows::Security::Cryptography::Core; + + auto keyBuffer = CryptographicBuffer::ConvertStringToBinary(keyString, BinaryStringEncoding::Utf8); + auto algo = SymmetricKeyAlgorithmProvider::OpenAlgorithm(SymmetricAlgorithmNames::AesEcbPkcs7()); + return algo.CreateSymmetricKey(keyBuffer); +} + +inline streams::IBuffer encrypt(const winrt::hstring& message, const winrt::hstring& keyString) +{ + using namespace winrt::Windows::Security::Cryptography; + using namespace winrt::Windows::Security::Cryptography::Core; + + auto msgBuffer = CryptographicBuffer::ConvertStringToBinary(message, BinaryStringEncoding::Utf8); + auto key = create_key(keyString); + return CryptographicEngine::Encrypt(key, msgBuffer, nullptr); +} + +inline winrt::hstring decrypt(const streams::IBuffer& encryptedBuffer, const winrt::hstring& keyString) +{ + using namespace winrt::Windows::Security::Cryptography; + using namespace winrt::Windows::Security::Cryptography::Core; + + auto key = create_key(keyString); + auto decryptedBuffer = CryptographicEngine::Decrypt(key, encryptedBuffer, nullptr); + return CryptographicBuffer::ConvertBinaryToString(BinaryStringEncoding::Utf8, decryptedBuffer); +} diff --git a/dev/OAuth/Microsoft.Security.Authentication.OAuth.def b/dev/OAuth/Microsoft.Security.Authentication.OAuth.def new file mode 100644 index 0000000000..7c97f8b141 --- /dev/null +++ b/dev/OAuth/Microsoft.Security.Authentication.OAuth.def @@ -0,0 +1,4 @@ +LIBRARY Microsoft.Security.Authentication.OAuth +EXPORTS + DllCanUnloadNow PRIVATE + DllGetActivationFactory PRIVATE diff --git a/dev/OAuth/OAuth.idl b/dev/OAuth/OAuth.idl new file mode 100644 index 0000000000..7455770a52 --- /dev/null +++ b/dev/OAuth/OAuth.idl @@ -0,0 +1,462 @@ +// Copyright (c) Microsoft Corporation and Contributors. +// Licensed under the MIT License. + +namespace Microsoft.Security.Authentication.OAuth +{ + [contractversion(1)] + apicontract OAuthContract {}; + + [contract(OAuthContract, 1)] + runtimeclass ClientAuthentication + { + ClientAuthentication(); + ClientAuthentication(Windows.Web.Http.Headers.HttpCredentialsHeaderValue authorization); + + static ClientAuthentication CreateForBasicAuthorization(String clientId, String clientSecret); + + // Specifies the 'Authorization' header of the HTTP POST request when requesting a token + Windows.Web.Http.Headers.HttpCredentialsHeaderValue Authorization { get; set; }; + + // Specifies the 'Proxy-Authorization' header of the HTTP POST request when requesting a token + Windows.Web.Http.Headers.HttpCredentialsHeaderValue ProxyAuthorization { get; set; }; + + // Specifies additional header values of the HTTP POST request when requesting a token + Windows.Foundation.Collections.IMap AdditionalHeaders { get; set; }; + } + + [contract(OAuthContract, 1)] + static runtimeclass AuthManager + { + // Initiates an authorization request in the user's default browser as described by RFC 6749 section 3.1. The + // returned 'IAsyncOperation' will remain in the 'Started' state until it is either cancelled or completed by a + // call to 'CompleteAuthRequest'. + static Windows.Foundation.IAsyncOperation InitiateAuthRequestAsync( + Windows.Foundation.Uri authEndpoint, + AuthRequestParams params); + + // Called by the application when the user agent completes an auth request via a redirect Uri. Return value is + // true if an appropriate request could be found and completed. Otherwise returns false indicating that the + // response went unhandled and the application may respond as appropriate. + static Boolean CompleteAuthRequest(Windows.Foundation.Uri responseUri); + + // Initiates an access token request as described by RFC 6749 section 3.2. + static Windows.Foundation.IAsyncOperation RequestTokenAsync( + Windows.Foundation.Uri tokenEndpoint, + TokenRequestParams params); + + // Initiates an access token request as described by RFC 6749 section 3.2. + static Windows.Foundation.IAsyncOperation RequestTokenAsync( + Windows.Foundation.Uri tokenEndpoint, + TokenRequestParams params, + ClientAuthentication clientAuth); + } + + // Correlates to the 'code_challenge_method' as described by section 4.3 of RFC 7636: Proof Key for Code Exchange by + // OAuth Public Clients (https://www.rfc-editor.org/rfc/rfc7636.html#section-4.3) + [contract(OAuthContract, 1)] + enum CodeChallengeMethodKind + { + // Suppresses the use of a code verifier. An error will be thrown if a code challenge string is set when this + // option is used + None = 0, + // Challenge method of "S256" (i.e. SHA256). This is the default unless explicitly set + S256 = 1, + // Challenge method of "plain" (i.e. send as plain text) + Plain = 2, + }; + + [contract(OAuthContract, 1)] + runtimeclass AuthRequestParams + { + // Construct with required parameters + AuthRequestParams(String responseType, String clientId); + // Construct with required parameters as well as a redirect URI, which is frequently specified + AuthRequestParams(String responseType, String clientId, Windows.Foundation.Uri redirectUri); + + // Helper method to create for an authorization code grant request ("code" response type) with required + // parameters, per RFC 6749 section 4.1.1. + static AuthRequestParams CreateForAuthorizationCodeRequest(String clientId); + // Helper method to create for an authorization code grant request ("code" response type) with required + // parameters as well as a redirect URI, which is frequently specified. + static AuthRequestParams CreateForAuthorizationCodeRequest(String clientId, Windows.Foundation.Uri redirectUri); + + // Helper method to create for an implicit grant request ("token" response type) with required parameters, per + // RFC 6749 section 4.2.1. + static AuthRequestParams CreateForImplicitRequest(String clientId); + // Helper method to create for an implicit grant request ("token" response type) with required parameters as + // well as a redirect URI, which is frequently specified. + static AuthRequestParams CreateForImplicitRequest(String clientId, Windows.Foundation.Uri redirectUri); + + // Specifies the required "response_type" parameter of the authorization request. This property is initialized + // by the creation function used ("code" for 'CreateForAuthorizationCodeRequest' and "token" for + // 'CreateForImplicitRequest'). + // + // Defined by RFC 6749: The OAuth 2.0 Authorization Framework, sections 4.1.1 and 4.2.1 + // https://www.rfc-editor.org/rfc/rfc6749#section-4.1.1 + // https://www.rfc-editor.org/rfc/rfc6749#section-4.2.1 + String ResponseType { get; set; }; + + // Specifies the required "client_id" parameter of the authorization request. This property is initialized by + // the value provided in the creation function call. + // + // Defined by RFC 6749: The OAuth 2.0 Authorization Framework, sections 4.1.1 and 4.2.1 + // https://www.rfc-editor.org/rfc/rfc6749#section-4.1.1 + // https://www.rfc-editor.org/rfc/rfc6749#section-4.2.1 + String ClientId { get; set; }; + + // Specifies the optional "redirect_uri" parameter of the authorization request. + // + // Defined by RFC 6749: The OAuth 2.0 Authorization Framework, sections 4.1.1 and 4.2.1 + // https://www.rfc-editor.org/rfc/rfc6749#section-4.1.1 + // https://www.rfc-editor.org/rfc/rfc6749#section-4.2.1 + Windows.Foundation.Uri RedirectUri { get; set; }; + + // Specifies the recommended "state" parameter of the authorization request. Note that although this is not + // required by the OAuth standard, a state value will always be set to correlate requests and responses. This + // parameter can be manually specified, in which case it must be globally unique across the entire system, + // otherwise an error will be thrown. It is therefore recommended to let the API select a value for you as it + // will guarantee that a unique value will be used. + // + // Defined by RFC 6749: The OAuth 2.0 Authorization Framework, sections 4.1.1 and 4.2.1 + // https://www.rfc-editor.org/rfc/rfc6749#section-4.1.1 + // https://www.rfc-editor.org/rfc/rfc6749#section-4.2.1 + String State { get; set; }; + + // Specifies the optional "scope" parameter of the authorization request. + // + // Defined by RFC 6749: The OAuth 2.0 Authorization Framework, sections 4.1.1 and 4.2.1 + // https://www.rfc-editor.org/rfc/rfc6749#section-4.1.1 + // https://www.rfc-editor.org/rfc/rfc6749#section-4.2.1 + String Scope { get; set; }; + + // Used as the PKCE code verifier. Either this value or a hash of this value will be used to specify the + // "code_challenge" parameter of the authorization request, depending on the value of 'CodeChallengeMethod'. If + // this value is not specified and 'CodeChallengeMethod' is not 'None', a random value will be generated for + // this property. The code verifier will persist all the way through to the token request. + // + // Defined by RFC 7636: Proof Key for Code Exchange by OAuth Public Clients, section 4.1 + // https://www.rfc-editor.org/rfc/rfc7636#section-4.1 + String CodeVerifier { get; set; }; + + // Specifies the optional "code_challenge_method" parameter of the authorization request. For authorization code + // requests, this value defaults to 'S256'. For implicit requests, this value defaults to 'None' and cannot be + // changed. + // + // Defined by RFC 7636: Proof Key for Code Exchange by OAuth Public Clients, section 4.3 + // https://www.rfc-editor.org/rfc/rfc7636#section-4.3 + CodeChallengeMethodKind CodeChallengeMethod { get; set; }; + + // Additional parameters passed along in the query string of the request URL. + Windows.Foundation.Collections.IMap AdditionalParams { get; set; }; + } + + [contract(OAuthContract, 1)] + runtimeclass AuthResponse + { + // From the "state" parameter of the authorization response. This property will always be set because a state + // value is always sent with the request. + // + // Defined by RFC 6749: The OAuth 2.0 Authorization Framework, sections 4.1.2 and 4.2.2 + // https://www.rfc-editor.org/rfc/rfc6749#section-4.1.2 + // https://www.rfc-editor.org/rfc/rfc6749#section-4.2.2 + String State { get; }; + + // From the "code" parameter of the authorization response. Set only if the request was an authorization code + // request. + // + // Defined by RFC 6749: The OAuth 2.0 Authorization Framework, section 4.1.2 + // https://www.rfc-editor.org/rfc/rfc6749#section-4.1.2 + String Code { get; }; + + // From the "access_token" parameter of the authorization response. Set only if the request was an implicit + // request. + // + // Defined by RFC 6749: The OAuth 2.0 Authorization Framework, section 4.2.2 + // https://www.rfc-editor.org/rfc/rfc6749#section-4.2.2 + String AccessToken { get; }; + + // From the "token_type" parameter of the authorization response. Set only if the request was an implicit + // request. + // + // Defined by RFC 6749: The OAuth 2.0 Authorization Framework, section 4.2.2 + // https://www.rfc-editor.org/rfc/rfc6749#section-4.2.2 + String TokenType { get; }; + + // From the "expires_in" parameter of the authorization response. An optional parameter that may be set only if + // the request was an implicit request. + // + // Defined by RFC 6749: The OAuth 2.0 Authorization Framework, section 4.2.2 + // https://www.rfc-editor.org/rfc/rfc6749#section-4.2.2 + String ExpiresIn { get; }; // TODO: DateTime? + + // From the "scope" parameter of the authorization response. An optional parameter that may be set only if the + // request was an implicit request. + // + // Defined by RFC 6749: The OAuth 2.0 Authorization Framework, section 4.2.2 + // https://www.rfc-editor.org/rfc/rfc6749#section-4.2.2 + String Scope { get; }; + + // Additional parameters set by the authorization server in the response URI. + Windows.Foundation.Collections.IMapView AdditionalParams { get; }; + } + + [contract(OAuthContract, 1)] + runtimeclass AuthFailure + { + // From the "error" parameter of the error response. The value of this property will map to a well known string + // specified in RFC 6749 sections 4.1.2.1 and 4.2.2.1, or approved extensions. + // + // Defined by RFC 6749: The OAuth 2.0 Authorization Framework, sections 4.1.2.1 and 4.2.2.1 + // https://www.rfc-editor.org/rfc/rfc6749#section-4.1.2.1 + // https://www.rfc-editor.org/rfc/rfc6749#section-4.2.2.1 + String Error { get; }; + + // From the "error_description" parameter of the error response. An optional parameter that, when set, provides + // additional human-readable information intended to assist the developer in understanding the error. + // + // Defined by RFC 6749: The OAuth 2.0 Authorization Framework, sections 4.1.2.1 and 4.2.2.1 + // https://www.rfc-editor.org/rfc/rfc6749#section-4.1.2.1 + // https://www.rfc-editor.org/rfc/rfc6749#section-4.2.2.1 + String ErrorDescription { get; }; + + // From the "error_uri" parameter of the error response. An optional parameter that, when set, specifies a URI + // identifying a human-readable webpage intended to assist the developer in understanding the error. + // + // Defined by RFC 6749: The OAuth 2.0 Authorization Framework, sections 4.1.2.1 and 4.2.2.1 + // https://www.rfc-editor.org/rfc/rfc6749#section-4.1.2.1 + // https://www.rfc-editor.org/rfc/rfc6749#section-4.2.2.1 + Windows.Foundation.Uri ErrorUri { get; }; + + // From the "state" parameter of the error response. This property will always be set because a state value is + // always sent with the request. + // + // Defined by RFC 6749: The OAuth 2.0 Authorization Framework, sections 4.1.2.1 and 4.2.2.1 + // https://www.rfc-editor.org/rfc/rfc6749#section-4.1.2.1 + // https://www.rfc-editor.org/rfc/rfc6749#section-4.2.2.1 + String State { get; }; + + // Additional parameters set by the authorization server in the response URI. + Windows.Foundation.Collections.IMapView AdditionalParams { get; }; + } + + [contract(OAuthContract, 1)] + runtimeclass AuthRequestResult + { + // The raw URI that was used to complete the request. + Windows.Foundation.Uri ResponseUri { get; }; + + // Non-null if the server's response indicates success, otherwise null + AuthResponse Response { get; }; + + // Non-null if the server's response indicates failure, otherwise null + AuthFailure Failure { get; }; + } + + [contract(OAuthContract, 1)] + runtimeclass TokenRequestParams + { + // Construct with required parameters + TokenRequestParams(String grantType); + + // Helper method to create for an authorization code grant request ("authorization_code" grant type), + // initialized with the required parameters extracted from the authorization response, per RFC 6749 section + // 4.1.3. + static TokenRequestParams CreateForAuthorizationCodeRequest(AuthResponse authResponse); + + // Helper method to create for a resource owner password credentials grant request ("password" grant type), + // initialized with the required parameters, per RFC 6749 section 4.3.2. + static TokenRequestParams CreateForResourceOwnerPasswordCredentials(String username, String password); + + // Helper method to create for a client credentials grant request ("client_credentials" grant type), initialized + // with the required parameters, per RFC 6749 section 4.4.2. + static TokenRequestParams CreateForClientCredentials(); + + // Helper method to create for an extension grant request, using the provided URI for the grant type, per RFC + // 6749 section 4.5. + static TokenRequestParams CreateForExtension(Windows.Foundation.Uri extensionUri); + + // Helper method to create for an access token refresh request ("refresh_token" grant type), initialized with + // the required parameters, per RFC 6749 section 6. + static TokenRequestParams CreateForRefreshToken(String refreshToken); + + // Specifies the required "grant_type" parameter of the token request. This property is initialized by the + // creation function used ("authorization_code" for 'CreateForAuthorizationCodeRequest', "password" for + // 'CreateForResourceOwnerPasswordCredentials', "client_credentials" for 'CreateForClientCredentials', + // "refresh_token" for 'CreateForRefreshToken', or the specified URI for 'CreateForExtension'). + // + // Defined by RFC 6749: The OAuth 2.0 Authorization Framework, sections 4.1.3, 4.3.2, 4.4.2, 4.5, and 6 + // https://www.rfc-editor.org/rfc/rfc6749#section-4.1.3 + // https://www.rfc-editor.org/rfc/rfc6749#section-4.3.2 + // https://www.rfc-editor.org/rfc/rfc6749#section-4.4.2 + // https://www.rfc-editor.org/rfc/rfc6749#section-4.5 + // https://www.rfc-editor.org/rfc/rfc6749#section-6 + String GrantType { get; set; }; + + // Specifies the "code" parameter of the token request. This property is required when the grant type is + // "authorization_code" and is initialized by 'CreateForAuthorizationCodeRequest'. + // + // Defined by RFC 6749: The OAuth 2.0 Authorization Framework, section 4.1.3 + // https://www.rfc-editor.org/rfc/rfc6749#section-4.1.3 + String Code { get; set; }; + + // Specifies the "redirect_uri" parameter of the token request. This property is required when the grant type is + // "authorization_code" and a redirect URI was included in the authorization request. This property is + // initialized by 'CreateForAuthorizationCodeRequest'. + // + // Defined by RFC 6749: The OAuth 2.0 Authorization Framework, section 4.1.3 + // https://www.rfc-editor.org/rfc/rfc6749#section-4.1.3 + Windows.Foundation.Uri RedirectUri { get; set; }; + + // Specifies the "code_verifier" parameter of the token request. This property is required when the grant type + // is "authorization_code" and a code challenge was included in the authorization request. This property is + // initialized by 'CreateForAuthorizationCodeRequest'. + // + // Defined by RFC 7636: Proof Key for Code Exchange by OAuth Public Clients, section 4.5 + // https://www.rfc-editor.org/rfc/rfc7636#section-4.5 + String CodeVerifier { get; set; }; + + // Specifies the "client_id" parameter of the token request. This property is required when the grant type is + // "authorization_code" and no alternative client authentication is specified. This property is initiated by + // 'CreateForAuthorizationCodeRequest'. + // + // Defined by RFC 6749: The OAuth 2.0 Authorization Framework, section 4.1.3 + // https://www.rfc-editor.org/rfc/rfc6749#section-4.1.3 + String ClientId { get; set; }; + + // Specifies the "username" parameter of the token request. This property is required when the grant type is + // "password" and is initialized by 'CreateForResourceOwnerPasswordCredentials'. + // + // Defined by RFC 6749: The OAuth 2.0 Authorization Framework, section 4.3.2 + // https://www.rfc-editor.org/rfc/rfc6749#section-4.3.2 + String Username { get; set; }; + + // Specifies the "password" parameter of the token request. This property is required when the grant type is + // "password" and is initialized by 'CreateForResourceOwnerPasswordCredentials'. + // + // Defined by RFC 6749: The OAuth 2.0 Authorization Framework, section 4.3.2 + // https://www.rfc-editor.org/rfc/rfc6749#section-4.3.2 + String Password { get; set; }; + + // Specifies the "scope" parameter of the token request. This property is valid only when the grant type is + // "password", "client_credentials", or "refresh_token". + // + // Defined by RFC 6749: The OAuth 2.0 Authorization Framework, sections 4.3.2, 4.4.2, and 6 + // https://www.rfc-editor.org/rfc/rfc6749#section-4.3.2 + // https://www.rfc-editor.org/rfc/rfc6749#section-4.4.2 + // https://www.rfc-editor.org/rfc/rfc6749#section-6 + String Scope { get; set; }; + + // Specifies the "refresh_token" parameter of the token request. This property is required when the grant type + // is "refresh_token" and is initialized by 'CreateForRefreshToken'. + // + // Defined by RFC 6749: The OAuth 2.0 Authorization Framework, section 6 + // https://www.rfc-editor.org/rfc/rfc6749#section-6 + String RefreshToken { get; set; }; + + // Additional parameters passed along in the HTTP request entity-body. + Windows.Foundation.Collections.IMap AdditionalParams { get; set; }; + } + + [contract(OAuthContract, 1)] + runtimeclass TokenResponse + { + // From the "access_token" parameter of the token response. A required property that should always be set. + // + // Defined by RFC 6749: The OAuth 2.0 Authorization Framework, section 5.1 + // https://www.rfc-editor.org/rfc/rfc6749#section-5.1 + String AccessToken { get; }; + + // From the "token_type" parameter of the token response. A required property that should always be set. + // + // Defined by RFC 6749: The OAuth 2.0 Authorization Framework, section 5.1 + // https://www.rfc-editor.org/rfc/rfc6749#section-5.1 + String TokenType { get; }; + + // From the "expires_in" parameter of the token response. An optional property that, when set, specifies the + // lifetime of the access token in seconds. + // + // Defined by RFC 6749: The OAuth 2.0 Authorization Framework, section 5.1 + // https://www.rfc-editor.org/rfc/rfc6749#section-5.1 + Double ExpiresIn { get; }; // TODO: DateTime? + + // From the "refresh_token" parameter of the token response. An optional property that, when set, can be used to + // obtain new access tokens using the same authorization grant provided during the request. + // + // Defined by RFC 6749: The OAuth 2.0 Authorization Framework, section 5.1 + // https://www.rfc-editor.org/rfc/rfc6749#section-5.1 + String RefreshToken { get; }; + + // From the "scope" parameter of the token response. An optional property that, when set, describes the scope of + // the access token issued by the authorization server. + // + // Defined by RFC 6749: The OAuth 2.0 Authorization Framework, section 5.1 + // https://www.rfc-editor.org/rfc/rfc6749#section-5.1 + String Scope { get; }; + + // Additional parameters set by the authorization server in the token response. + Windows.Foundation.Collections.IMapView AdditionalParams { get; }; + } + + [contract(OAuthContract, 1)] + enum TokenFailureKind + { + // The server responded with an error response as described by RFC 6749 section 5.2. This means that the failure + // object has an 'Error' string and possibly other specified properties. + ErrorResponse = 0, + + // The HTTP POST request failed. See the 'ErrorCode' property for more details as to why. + HttpFailure = 1, + + // The server responded, but its response was improperly formatted. This could be that the server did not send + // the response as JSON, the response JSON string was improperly formatted, or the response JSON contained + // unexpected object types (e.g. a number when a string is expected, etc.). + InvalidResponse = 2, + }; + + [contract(OAuthContract, 1)] + runtimeclass TokenFailure + { + // Indicates the type of failure that this object describes, which will indicate which properties might be set. + TokenFailureKind Kind { get; }; + + // If 'Kind' was anything other than 'ErrorResponse', + HRESULT ErrorCode { get; }; + + // From the "error" parameter of the error response. The value of this property will map to a well known string + // specified in RFC 6749 section 5.2, or approved extensions. + // + // Defined by RFC 6749: The OAuth 2.0 Authorization Framework, section 5.2 + // https://www.rfc-editor.org/rfc/rfc6749#section-5.2 + String Error { get; }; + + // From the "error_description" parameter of the error response. An optional parameter that, when set, provides + // additional human-readable information intended to assist the developer in understanding the error. + // + // Defined by RFC 6749: The OAuth 2.0 Authorization Framework, section 5.2 + // https://www.rfc-editor.org/rfc/rfc6749#section-5.2 + String ErrorDescription { get; }; + + // From the "error_uri" parameter of the error response. An optional parameter that, when set, specifies a URI + // identifying a human-readable webpage intended to assist the developer in understanding the error. + // + // Defined by RFC 6749: The OAuth 2.0 Authorization Framework, section 5.2 + // https://www.rfc-editor.org/rfc/rfc6749#section-5.2 + Windows.Foundation.Uri ErrorUri { get; }; + + // Additional parameters set by the authorization server in the token response. + Windows.Foundation.Collections.IMapView AdditionalParams { get; }; + } + + [contract(OAuthContract, 1)] + runtimeclass TokenRequestResult + { + // The raw HTTP response that was used to complete the request + Windows.Web.Http.HttpResponseMessage ResponseMessage { get; }; + + // Non-null if the server's response indicates success, otherwise null + TokenResponse Response { get; }; + + // Non-null if the server's response indicates failure, otherwise null + TokenFailure Failure { get; }; + } +} diff --git a/dev/OAuth/OAuth.vcxitems b/dev/OAuth/OAuth.vcxitems new file mode 100644 index 0000000000..1a6c96beae --- /dev/null +++ b/dev/OAuth/OAuth.vcxitems @@ -0,0 +1,85 @@ + + + + $(MSBuildAllProjects);$(MSBuildThisFileFullPath) + true + {3E7FD510-8B66-40E7-A80B-780CB8972F83} + + + + %(AdditionalIncludeDirectories);$(MSBuildThisFileDirectory) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/dev/OAuth/TokenFailure.cpp b/dev/OAuth/TokenFailure.cpp new file mode 100644 index 0000000000..d03de7df3d --- /dev/null +++ b/dev/OAuth/TokenFailure.cpp @@ -0,0 +1,79 @@ +#include +#include "common.h" + +#include "TokenFailure.h" +#include + +using namespace std::literals; +using namespace winrt::Microsoft::Security::Authentication::OAuth; +using namespace winrt::Windows::Data::Json; +using namespace winrt::Windows::Foundation; +using namespace winrt::Windows::Foundation::Collections; + +namespace winrt::Microsoft::Security::Authentication::OAuth::implementation +{ + TokenFailure::TokenFailure(TokenFailureKind kind, winrt::hresult code) : m_kind(kind), m_errorCode(code) {} + + TokenFailure::TokenFailure(const JsonObject& jsonObject) : + m_kind(TokenFailureKind::ErrorResponse), + m_errorCode(E_FAIL) + { + std::map additionalParams; + + // NOTE: Functions like 'GetString' will throw if the value is not the requested type, so the calling code must + // be ready to handle such failures + for (auto&& pair : jsonObject) + { + auto name = pair.Key(); + if (name == L"error"sv) + { + m_error = pair.Value().GetString(); + // TODO: Use the error string to set a more accurate HRESULT? + } + else if (name == L"error_description"sv) + { + m_errorDescription = pair.Value().GetString(); + } + else if (name == L"error_uri"sv) + { + m_errorUri = Uri(pair.Value().GetString()); + } + else + { + additionalParams.emplace(std::move(name), pair.Value()); + } + } + + m_additionalParams = winrt::single_threaded_map(std::move(additionalParams)).GetView(); + } + + TokenFailureKind TokenFailure::Kind() + { + return m_kind; + } + + winrt::hresult TokenFailure::ErrorCode() + { + return m_errorCode; + } + + winrt::hstring TokenFailure::Error() + { + return m_error; + } + + winrt::hstring TokenFailure::ErrorDescription() + { + return m_errorDescription; + } + + Uri TokenFailure::ErrorUri() + { + return m_errorUri; + } + + IMapView TokenFailure::AdditionalParams() + { + return m_additionalParams; + } +} diff --git a/dev/OAuth/TokenFailure.h b/dev/OAuth/TokenFailure.h new file mode 100644 index 0000000000..7b9e5b3d76 --- /dev/null +++ b/dev/OAuth/TokenFailure.h @@ -0,0 +1,26 @@ +#pragma once +#include + +namespace winrt::Microsoft::Security::Authentication::OAuth::implementation +{ + struct TokenFailure : TokenFailureT + { + TokenFailure(TokenFailureKind kind, winrt::hresult code); + TokenFailure(const json::JsonObject& jsonObject); + + TokenFailureKind Kind(); + winrt::hresult ErrorCode(); + winrt::hstring Error(); + winrt::hstring ErrorDescription(); + foundation::Uri ErrorUri(); + collections::IMapView AdditionalParams(); + + private: + TokenFailureKind m_kind; + winrt::hresult m_errorCode; + winrt::hstring m_error; + winrt::hstring m_errorDescription; + foundation::Uri m_errorUri{ nullptr }; + collections::IMapView m_additionalParams; + }; +} diff --git a/dev/OAuth/TokenRequestParams.cpp b/dev/OAuth/TokenRequestParams.cpp new file mode 100644 index 0000000000..99c26463e3 --- /dev/null +++ b/dev/OAuth/TokenRequestParams.cpp @@ -0,0 +1,243 @@ +#include +#include "common.h" + +#include "TokenRequestParams.h" +#include + +#include "AuthResponse.h" + +using namespace winrt::Microsoft::Security::Authentication::OAuth; +using namespace winrt::Windows::Foundation; +using namespace Collections; + +namespace winrt::Microsoft::Security::Authentication::OAuth::implementation +{ + TokenRequestParams::TokenRequestParams(const winrt::hstring& grantType) : m_grantType(grantType) {} + + oauth::TokenRequestParams TokenRequestParams::CreateForAuthorizationCodeRequest( + const oauth::AuthResponse& authResponse) + { + auto result = winrt::make_self(L"authorization_code"); + result->m_code = authResponse.Code(); + + auto implResponse = winrt::get_self(authResponse); + if (auto redirectUri = implResponse->request_params()->RedirectUri()) + { + result->m_redirectUri = std::move(redirectUri); + } + + if (auto clientId = implResponse->request_params()->ClientId(); !clientId.empty()) + { + result->m_clientId = std::move(clientId); + } + + if (auto codeVerifier = implResponse->request_params()->CodeVerifier(); !codeVerifier.empty()) + { + result->m_codeVerifier = std::move(codeVerifier); + } + + return *result; + } + + oauth::TokenRequestParams TokenRequestParams::CreateForResourceOwnerPasswordCredentials( + const winrt::hstring& username, const winrt::hstring& password) + { + auto result = winrt::make_self(L"password"); + result->m_username = username; + result->m_password = password; + + return *result; + } + + oauth::TokenRequestParams TokenRequestParams::CreateForClientCredentials() + { + return winrt::make(L"client_credentials"); + } + + oauth::TokenRequestParams TokenRequestParams::CreateForExtension(const Uri& extensionUri) + { + return winrt::make(extensionUri.RawUri()); + } + + oauth::TokenRequestParams TokenRequestParams::CreateForRefreshToken(const winrt::hstring& refreshToken) + { + auto result = winrt::make_self(L"refresh_token"); + result->m_refreshToken = refreshToken; + + return *result; + } + + winrt::hstring TokenRequestParams::GrantType() + { + std::shared_lock guard{ m_mutex }; + return m_grantType; + } + + void TokenRequestParams::GrantType(const winrt::hstring& value) + { + std::lock_guard guard{ m_mutex }; + check_not_finalized(); + m_grantType = value; + } + + winrt::hstring TokenRequestParams::Code() + { + std::shared_lock guard{ m_mutex }; + return m_code; + } + + void TokenRequestParams::Code(const winrt::hstring& value) + { + std::lock_guard guard{ m_mutex }; + check_not_finalized(); + m_code = value; + } + + Uri TokenRequestParams::RedirectUri() + { + std::shared_lock guard{ m_mutex }; + return m_redirectUri; + } + + void TokenRequestParams::RedirectUri(const Uri& value) + { + std::lock_guard guard{ m_mutex }; + check_not_finalized(); + m_redirectUri = value; + } + + winrt::hstring TokenRequestParams::CodeVerifier() + { + std::shared_lock guard{ m_mutex }; + return m_codeVerifier; + } + + void TokenRequestParams::CodeVerifier(const winrt::hstring& value) + { + std::lock_guard guard{ m_mutex }; + check_not_finalized(); + m_codeVerifier = value; + } + + winrt::hstring TokenRequestParams::ClientId() + { + std::shared_lock guard{ m_mutex }; + return m_clientId; + } + + void TokenRequestParams::ClientId(const winrt::hstring& value) + { + std::lock_guard guard{ m_mutex }; + check_not_finalized(); + m_clientId = value; + } + + winrt::hstring TokenRequestParams::Username() + { + std::shared_lock guard{ m_mutex }; + return m_username; + } + + void TokenRequestParams::Username(const winrt::hstring& value) + { + std::lock_guard guard{ m_mutex }; + check_not_finalized(); + m_username = value; + } + + winrt::hstring TokenRequestParams::Password() + { + std::shared_lock guard{ m_mutex }; + return m_password; + } + + void TokenRequestParams::Password(const winrt::hstring& value) + { + std::lock_guard guard{ m_mutex }; + check_not_finalized(); + m_password = value; + } + + winrt::hstring TokenRequestParams::Scope() + { + std::shared_lock guard{ m_mutex }; + return m_scope; + } + + void TokenRequestParams::Scope(const winrt::hstring& value) + { + std::lock_guard guard{ m_mutex }; + check_not_finalized(); + m_scope = value; + } + + winrt::hstring TokenRequestParams::RefreshToken() + { + std::shared_lock guard{ m_mutex }; + return m_refreshToken; + } + + void TokenRequestParams::RefreshToken(const winrt::hstring& value) + { + std::lock_guard guard{ m_mutex }; + check_not_finalized(); + m_refreshToken = value; + } + + IMap TokenRequestParams::AdditionalParams() + { + std::shared_lock guard{ m_mutex }; + return m_additionalParams; + } + + void TokenRequestParams::AdditionalParams(const IMap& value) + { + std::lock_guard guard{ m_mutex }; + check_not_finalized(); + m_additionalParams = value; + } + + void TokenRequestParams::finalize() + { + std::lock_guard guard{ m_mutex }; + if (m_finalized) + { + throw winrt::hresult_illegal_method_call(L"TokenRequestParams can only be used for a single request call"); + } + + m_finalized = true; + } + + std::map TokenRequestParams::params() + { + // HttpFormUrlEncodedContent requires an IIterable> as input. In theory we can + // make the TokenRequestParams implement this type to save on some work, however this may be a little tricky + std::map result; + auto addIfSet = [&](std::wstring_view key, const winrt::hstring& value) { + if (!value.empty()) + { + result.emplace(key, value); + } + }; + + std::shared_lock guard{ m_mutex }; + addIfSet(L"grant_type", m_grantType); + addIfSet(L"code", m_code); + if (m_redirectUri) result.emplace(L"redirect_uri", m_redirectUri.RawUri()); + addIfSet(L"code_verifier", m_codeVerifier); + addIfSet(L"client_id", m_clientId); + addIfSet(L"username", m_username); + addIfSet(L"password", m_password); + addIfSet(L"scope", m_scope); + addIfSet(L"refresh_token", m_refreshToken); + if (m_additionalParams) + { + for (auto&& pair : m_additionalParams) + { + result.emplace(pair.Key(), pair.Value()); + } + } + + return result; + } +} diff --git a/dev/OAuth/TokenRequestParams.h b/dev/OAuth/TokenRequestParams.h new file mode 100644 index 0000000000..2829e6e4d2 --- /dev/null +++ b/dev/OAuth/TokenRequestParams.h @@ -0,0 +1,77 @@ +#pragma once +#include + +#include + +namespace winrt::Microsoft::Security::Authentication::OAuth::implementation +{ + struct TokenRequestParams : TokenRequestParamsT + { + TokenRequestParams() = default; + TokenRequestParams(const winrt::hstring& grantType); + + static oauth::TokenRequestParams CreateForAuthorizationCodeRequest(const oauth::AuthResponse& authResponse); + static oauth::TokenRequestParams CreateForResourceOwnerPasswordCredentials(const winrt::hstring& username, + const winrt::hstring& password); + static oauth::TokenRequestParams CreateForClientCredentials(); + static oauth::TokenRequestParams CreateForExtension(const foundation::Uri& extensionUri); + static oauth::TokenRequestParams CreateForRefreshToken(const winrt::hstring& refreshToken); + + winrt::hstring GrantType(); + void GrantType(const winrt::hstring& value); + winrt::hstring Code(); + void Code(const winrt::hstring& value); + foundation::Uri RedirectUri(); + void RedirectUri(const foundation::Uri& value); + winrt::hstring CodeVerifier(); + void CodeVerifier(const winrt::hstring& value); + winrt::hstring ClientId(); + void ClientId(const winrt::hstring& value); + winrt::hstring Username(); + void Username(const winrt::hstring& value); + winrt::hstring Password(); + void Password(const winrt::hstring& value); + winrt::hstring Scope(); + void Scope(const winrt::hstring& value); + winrt::hstring RefreshToken(); + void RefreshToken(const winrt::hstring& value); + collections::IMap AdditionalParams(); + void AdditionalParams(const collections::IMap& value); + + // Implementation functions + void finalize(); + std::map params(); + + private: + void check_not_finalized() + { + // NOTE: Lock should be held when calling + if (m_finalized) + { + throw winrt::hresult_illegal_method_call( + L"TokenRequestParams object cannot be modified after being used to initiate a request"); + } + } + + std::shared_mutex m_mutex; + bool m_finalized = false; + winrt::hstring m_grantType; + winrt::hstring m_code; + foundation::Uri m_redirectUri{ nullptr }; + winrt::hstring m_codeVerifier; + winrt::hstring m_clientId; + winrt::hstring m_username; + winrt::hstring m_password; + winrt::hstring m_scope; + winrt::hstring m_refreshToken; + collections::IMap m_additionalParams = + winrt::multi_threaded_map(); + }; +} + +namespace winrt::Microsoft::Security::Authentication::OAuth::factory_implementation +{ + struct TokenRequestParams : TokenRequestParamsT + { + }; +} diff --git a/dev/OAuth/TokenRequestResult.cpp b/dev/OAuth/TokenRequestResult.cpp new file mode 100644 index 0000000000..bc1304968e --- /dev/null +++ b/dev/OAuth/TokenRequestResult.cpp @@ -0,0 +1,47 @@ +#include +#include "common.h" + +#include "TokenRequestResult.h" +#include + +#include "TokenFailure.h" +#include "TokenResponse.h" + +using namespace winrt::Microsoft::Security::Authentication::OAuth; +using namespace winrt::Windows::Data::Json; +using namespace winrt::Windows::Foundation; +using namespace winrt::Windows::Foundation::Collections; +using namespace winrt::Windows::Web::Http; + +namespace winrt::Microsoft::Security::Authentication::OAuth::implementation +{ + TokenRequestResult::TokenRequestResult(HttpResponseMessage responseMessage, oauth::TokenResponse response, + oauth::TokenFailure failure) : + m_responseMessage(std::move(responseMessage)), + m_response(std::move(response)), + m_failure(std::move(failure)) + { + } + + oauth::TokenRequestResult TokenRequestResult::MakeFailure(HttpResponseMessage response, + TokenFailureKind failureKind, winrt::hresult failureCode) + { + return winrt::make(std::move(response), nullptr, + winrt::make(failureKind, failureCode)); + } + + HttpResponseMessage TokenRequestResult::ResponseMessage() + { + return m_responseMessage; + } + + oauth::TokenResponse TokenRequestResult::Response() + { + return m_response; + } + + oauth::TokenFailure TokenRequestResult::Failure() + { + return m_failure; + } +} diff --git a/dev/OAuth/TokenRequestResult.h b/dev/OAuth/TokenRequestResult.h new file mode 100644 index 0000000000..98889adea5 --- /dev/null +++ b/dev/OAuth/TokenRequestResult.h @@ -0,0 +1,23 @@ +#pragma once +#include + +namespace winrt::Microsoft::Security::Authentication::OAuth::implementation +{ + struct TokenRequestResult : TokenRequestResultT + { + TokenRequestResult(http::HttpResponseMessage responseMessage, oauth::TokenResponse resposne, + oauth::TokenFailure failure); + + static oauth::TokenRequestResult MakeFailure(http::HttpResponseMessage response, TokenFailureKind failureKind, + winrt::hresult failureCode); + + http::HttpResponseMessage ResponseMessage(); + oauth::TokenResponse Response(); + oauth::TokenFailure Failure(); + + private: + http::HttpResponseMessage m_responseMessage; + oauth::TokenResponse m_response{ nullptr }; + oauth::TokenFailure m_failure{ nullptr }; + }; +} diff --git a/dev/OAuth/TokenResponse.cpp b/dev/OAuth/TokenResponse.cpp new file mode 100644 index 0000000000..0ce6bab974 --- /dev/null +++ b/dev/OAuth/TokenResponse.cpp @@ -0,0 +1,81 @@ +#include +#include "common.h" + +#include "TokenResponse.h" +#include + +using namespace winrt::Microsoft::Security::Authentication::OAuth; +using namespace winrt::Windows::Data::Json; +using namespace winrt::Windows::Foundation; +using namespace winrt::Windows::Foundation::Collections; + +namespace winrt::Microsoft::Security::Authentication::OAuth::implementation +{ + TokenResponse::TokenResponse(const json::JsonObject& jsonObject) + { + std::map additionalParams; + + // NOTE: Functions like 'GetString' will throw if the value is not the requested type. It might be worth + // revisiting this in the future + for (auto&& pair : jsonObject) + { + auto name = pair.Key(); + if (name == L"access_token") + { + m_accessToken = pair.Value().GetString(); + } + else if (name == L"token_type") + { + m_tokenType = pair.Value().GetString(); + } + else if (name == L"expires_in") + { + m_expiresIn = pair.Value().GetNumber(); + } + else if (name == L"refresh_token") + { + m_refreshToken = pair.Value().GetString(); + } + else if (name == L"scope") + { + m_scope = pair.Value().GetString(); + } + else + { + additionalParams.emplace(std::move(name), pair.Value()); + } + } + + m_additionalParams = winrt::single_threaded_map(std::move(additionalParams)).GetView(); + } + + winrt::hstring TokenResponse::AccessToken() + { + return m_accessToken; + } + + winrt::hstring TokenResponse::TokenType() + { + return m_tokenType; + } + + double TokenResponse::ExpiresIn() + { + return m_expiresIn; + } + + winrt::hstring TokenResponse::RefreshToken() + { + return m_refreshToken; + } + + winrt::hstring TokenResponse::Scope() + { + return m_scope; + } + + IMapView TokenResponse::AdditionalParams() + { + return m_additionalParams; + } +} diff --git a/dev/OAuth/TokenResponse.h b/dev/OAuth/TokenResponse.h new file mode 100644 index 0000000000..4becf5f712 --- /dev/null +++ b/dev/OAuth/TokenResponse.h @@ -0,0 +1,25 @@ +#pragma once +#include + +namespace winrt::Microsoft::Security::Authentication::OAuth::implementation +{ + struct TokenResponse : TokenResponseT + { + TokenResponse(const json::JsonObject& jsonObject); + + winrt::hstring AccessToken(); + winrt::hstring TokenType(); + double ExpiresIn(); + winrt::hstring RefreshToken(); + winrt::hstring Scope(); + collections::IMapView AdditionalParams(); + + private: + winrt::hstring m_accessToken; + winrt::hstring m_tokenType; + double m_expiresIn; + winrt::hstring m_refreshToken; + winrt::hstring m_scope; + collections::IMapView m_additionalParams; + }; +} diff --git a/dev/OAuth/common.h b/dev/OAuth/common.h new file mode 100644 index 0000000000..6f0bb54bed --- /dev/null +++ b/dev/OAuth/common.h @@ -0,0 +1,20 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace collections = winrt::Windows::Foundation::Collections; +namespace crypto = winrt::Windows::Security::Cryptography; +namespace foundation = winrt::Windows::Foundation; +namespace http = winrt::Windows::Web::Http; +namespace json = winrt::Windows::Data::Json; +namespace oauth = winrt::Microsoft::Security::Authentication::OAuth; +namespace streams = winrt::Windows::Storage::Streams; + +#include "Crypto.h" diff --git a/dev/WindowsAppRuntime_DLL/WindowsAppRuntime_DLL.vcxproj b/dev/WindowsAppRuntime_DLL/WindowsAppRuntime_DLL.vcxproj index 15d6e35833..21fcc5b33a 100644 --- a/dev/WindowsAppRuntime_DLL/WindowsAppRuntime_DLL.vcxproj +++ b/dev/WindowsAppRuntime_DLL/WindowsAppRuntime_DLL.vcxproj @@ -96,6 +96,7 @@ + From e8322ceaadbc0582fab9496800705b4c01ef96bc Mon Sep 17 00:00:00 2001 From: Duncan Horn Date: Wed, 5 Oct 2022 11:22:27 -0700 Subject: [PATCH 2/9] No setters for collections types --- dev/OAuth/AuthRequestParams.cpp | 24 +-- dev/OAuth/AuthRequestParams.h | 17 +- dev/OAuth/ClientAuthentication.cpp | 6 - dev/OAuth/ClientAuthentication.h | 1 - dev/OAuth/LockableMap.h | 301 +++++++++++++++++++++++++++++ dev/OAuth/OAuth.idl | 6 +- dev/OAuth/TokenRequestParams.cpp | 12 +- dev/OAuth/TokenRequestParams.h | 7 +- 8 files changed, 328 insertions(+), 46 deletions(-) create mode 100644 dev/OAuth/LockableMap.h diff --git a/dev/OAuth/AuthRequestParams.cpp b/dev/OAuth/AuthRequestParams.cpp index c60e4adf3e..cfd455cb01 100644 --- a/dev/OAuth/AuthRequestParams.cpp +++ b/dev/OAuth/AuthRequestParams.cpp @@ -1,4 +1,4 @@ -#include +#include #include "common.h" #include "AuthRequestParams.h" @@ -142,14 +142,7 @@ namespace winrt::Microsoft::Security::Authentication::OAuth::implementation IMap AuthRequestParams::AdditionalParams() { std::shared_lock guard{ m_mutex }; - return m_additionalParams; - } - - void AuthRequestParams::AdditionalParams(const IMap& value) - { - std::lock_guard guard{ m_mutex }; - check_not_finalized(); - m_additionalParams = value; + return *m_additionalParams; } void AuthRequestParams::finalize() @@ -161,6 +154,7 @@ namespace winrt::Microsoft::Security::Authentication::OAuth::implementation } m_finalized = true; + m_additionalParams->lock(); if (!m_codeVerifier.empty() && (m_codeChallengeMethod == CodeChallengeMethodKind::None)) { @@ -239,7 +233,7 @@ namespace winrt::Microsoft::Security::Authentication::OAuth::implementation if (m_additionalParams) { - for (auto&& pair : m_additionalParams) + for (auto&& pair : IMap{ *m_additionalParams }) { result += L"&"; result += Uri::EscapeComponent(pair.Key()); @@ -250,14 +244,4 @@ namespace winrt::Microsoft::Security::Authentication::OAuth::implementation return result; } - - void AuthRequestParams::check_not_finalized() - { - // NOTE: Lock should be held when calling - if (m_finalized) - { - throw winrt::hresult_illegal_method_call( - L"AuthRequestParams object cannot be modified after being used to initiate a request"); - } - } } diff --git a/dev/OAuth/AuthRequestParams.h b/dev/OAuth/AuthRequestParams.h index 31b2fc72ae..f47de5deb3 100644 --- a/dev/OAuth/AuthRequestParams.h +++ b/dev/OAuth/AuthRequestParams.h @@ -3,6 +3,8 @@ #include +#include "LockableMap.h" + namespace winrt::Microsoft::Security::Authentication::OAuth::implementation { struct AuthRequestParams : AuthRequestParamsT @@ -34,7 +36,6 @@ namespace winrt::Microsoft::Security::Authentication::OAuth::implementation oauth::CodeChallengeMethodKind CodeChallengeMethod(); void CodeChallengeMethod(oauth::CodeChallengeMethodKind value); collections::IMap AdditionalParams(); - void AdditionalParams(const collections::IMap& value); // Implementation functions void finalize(); @@ -43,7 +44,15 @@ namespace winrt::Microsoft::Security::Authentication::OAuth::implementation std::wstring create_url(const foundation::Uri& authEndpoint); private: - void check_not_finalized(); + void check_not_finalized() + { + // NOTE: Lock should be held when calling + if (m_finalized) + { + throw winrt::hresult_illegal_method_call( + L"AuthRequestParams object cannot be modified after being used to initiate a request"); + } + } std::shared_mutex m_mutex; bool m_finalized = false; @@ -54,8 +63,8 @@ namespace winrt::Microsoft::Security::Authentication::OAuth::implementation winrt::hstring m_scope; winrt::hstring m_codeVerifier; oauth::CodeChallengeMethodKind m_codeChallengeMethod; - collections::IMap m_additionalParams = - winrt::multi_threaded_map(); + winrt::com_ptr> m_additionalParams = + winrt::make_self>(); }; } diff --git a/dev/OAuth/ClientAuthentication.cpp b/dev/OAuth/ClientAuthentication.cpp index bbbdec1a76..9a2cd00cc2 100644 --- a/dev/OAuth/ClientAuthentication.cpp +++ b/dev/OAuth/ClientAuthentication.cpp @@ -56,10 +56,4 @@ namespace winrt::Microsoft::Security::Authentication::OAuth::implementation std::shared_lock guard{ m_mutex }; return m_additionalHeaders; } - - void ClientAuthentication::AdditionalHeaders(winrt::Windows::Foundation::Collections::IMap const& value) - { - std::lock_guard guard{ m_mutex }; - m_additionalHeaders = value; - } } diff --git a/dev/OAuth/ClientAuthentication.h b/dev/OAuth/ClientAuthentication.h index d736a0fb4a..5fb6aee201 100644 --- a/dev/OAuth/ClientAuthentication.h +++ b/dev/OAuth/ClientAuthentication.h @@ -18,7 +18,6 @@ namespace winrt::Microsoft::Security::Authentication::OAuth::implementation http::Headers::HttpCredentialsHeaderValue ProxyAuthorization(); void ProxyAuthorization(http::Headers::HttpCredentialsHeaderValue const& value); collections::IMap AdditionalHeaders(); - void AdditionalHeaders(collections::IMap const& value); private: std::shared_mutex m_mutex; diff --git a/dev/OAuth/LockableMap.h b/dev/OAuth/LockableMap.h new file mode 100644 index 0000000000..879ea93af6 --- /dev/null +++ b/dev/OAuth/LockableMap.h @@ -0,0 +1,301 @@ +#pragma once + +#include +#include + +namespace impl +{ + template + inline T default_value() + { + if constexpr (std::is_constructible_v) + { + // Handles classes where we'd otherwise get activation + return T{ nullptr }; + } + else + { + return T{}; + } + } + + template + struct KeyValuePair : winrt::implements, collections::IKeyValuePair> + { + KeyValuePair(KeyT key, ValueT value) : + m_key(std::move(key)), + m_value(std::move(value)) + { + } + + KeyT Key() + { + return m_key; + } + + ValueT Value() + { + return m_value; + } + + private: + + KeyT m_key; + ValueT m_value; + }; + + template + struct LockableMapIterator : winrt::implements, + collections::IIterator>> + { + LockableMapIterator(winrt::com_ptr map, std::size_t version) : m_map(std::move(map)), m_version(version) + { + m_itr = m_map->m_map.begin(); + } + + // IIterator + collections::IKeyValuePair Current() + { + std::shared_lock guard{ m_map->m_mutex }; + check_version(); + if (m_itr == m_map->m_map.end()) + { + throw winrt::hresult_out_of_bounds(); + } + + return winrt::make>(m_itr->first, m_itr->second); + } + + bool HasCurrent() + { + std::shared_lock guard{ m_map->m_mutex }; + check_version(); + return m_itr != m_map->m_map.end(); + } + + std::uint32_t GetMany(winrt::array_view> items) + { + std::shared_lock guard{ m_map->m_mutex }; + check_version(); + + auto end = m_map->m_map.end(); + std::uint32_t result = 0; + for (; (m_itr != end) && (result < items.size()); ++m_itr) + { + items[result++] = winrt::make>(m_itr->first, m_itr->second); + } + + return result; + } + + bool MoveNext() + { + std::shared_lock guard{ m_map->m_mutex }; + check_version(); + + auto end = m_map->m_map.end(); + if (m_itr != end) + { + ++m_itr; + } + + return m_itr != end; + } + + private: + void check_version() + { + if (m_version != m_map->m_version) + { + throw winrt::hresult_changed_state(); + } + } + + winrt::com_ptr m_map; + std::size_t m_version; + typename std::map::const_iterator m_itr; + }; + + template + struct LockableMapView : winrt::implements, + collections::IMapView, + collections::IIterable>> + { + LockableMapView(winrt::com_ptr map, std::size_t version) : m_map(std::move(map)), m_version(version) + { + } + + // IMapView + std::uint32_t Size() + { + std::shared_lock guard{ m_map->m_mutex }; + check_version(); + return static_cast(m_map->m_map.size()); + } + + bool HasKey(const KeyT& key) + { + std::shared_lock guard{ m_map->m_mutex }; + check_version(); + return m_map->m_map.find(key) != m_map->m_map.end(); + } + + ValueT Lookup(const KeyT& key) + { + std::shared_lock guard{ m_map->m_mutex }; + check_version(); + auto itr = m_map->m_map.find(key); + if (itr == m_map->m_map.end()) + { + throw winrt::hresult_out_of_bounds(); + } + + return itr->second; + } + + void Split(collections::IMapView& lhs, collections::IMapView& rhs) + { + // NOTE: Follows C++/WinRT implementation + lhs = nullptr; + rhs = nullptr; + } + + // IIterable + collections::IIterator> First() + { + std::shared_lock guard{ m_map->m_mutex }; + return winrt::make>(m_map, m_version); + } + + private: + void check_version() + { + if (m_version != m_map->m_version) + { + throw winrt::hresult_changed_state(); + } + } + + winrt::com_ptr m_map; + std::size_t m_version; + }; +} + +// Here, "lock" means "prevent further modification." Of course, the objects contained within the map can modified, but +// nothing can be added/removed from the map +template +struct LockableMap : winrt::implements, + collections::IMap, + collections::IIterable>> +{ + friend struct impl::LockableMapIterator; + friend struct impl::LockableMapView; + + // IMap + std::uint32_t Size() + { + std::shared_lock guard{ m_mutex }; + return static_cast(m_map.size()); + } + + void Clear() + { + std::map oldValues; // Release outside of lock + + std::lock_guard guard{ m_mutex }; + check_not_locked(); + m_map.swap(oldValues); + ++m_version; + } + + collections::IMapView GetView() + { + std::shared_lock guard{ m_mutex }; + return winrt::make>(this->get_strong(), m_version); + } + + bool HasKey(const KeyT& value) + { + std::shared_lock guard{ m_mutex }; + return m_map.find(value) != m_map.end(); + } + + bool Insert(const KeyT& key, const ValueT& value) + { + auto removedValue = impl::default_value(); + + std::lock_guard guard{ m_mutex }; + check_not_locked(); + auto [itr, added] = m_map.emplace(key, value); + if (!added) + { + std::swap(removedValue, itr->second); + itr->second = value; + } + ++m_version; + + return !added; + } + + ValueT Lookup(const KeyT& key) + { + std::shared_lock guard{ m_mutex }; + auto itr = m_map.find(key); + if (itr == m_map.end()) + { + throw winrt::hresult_out_of_bounds(); + } + return itr->second; + } + + void Remove(const KeyT& key) + { + typename std::map::node_type node; // Destroy outside of lock + + { + std::lock_guard guard{ m_mutex }; + check_not_locked(); + node = m_map.extract(key); + ++m_version; + } + + if (!node) + { + throw winrt::hresult_out_of_bounds(); + } + } + + // IIterable + collections::IIterator> First() + { + std::shared_lock guard{ m_mutex }; + return winrt::make>(this->get_strong(), m_version); + } + + // Implementation Functions + void lock() + { + std::lock_guard guard{ m_mutex }; + if (m_locked) + { + throw winrt::hresult_illegal_method_call(L"Map has already been locked from modification"); + } + + m_locked = true; + } + +private: + void check_not_locked() + { + // NOTE: Lock should be held when calling + if (m_locked) + { + throw winrt::hresult_illegal_method_call(L"Map has been locked from modification"); + } + } + + std::shared_mutex m_mutex; + std::size_t m_version = 0; + bool m_locked = false; + std::map m_map; +}; diff --git a/dev/OAuth/OAuth.idl b/dev/OAuth/OAuth.idl index 7455770a52..c8d34fdb80 100644 --- a/dev/OAuth/OAuth.idl +++ b/dev/OAuth/OAuth.idl @@ -21,7 +21,7 @@ namespace Microsoft.Security.Authentication.OAuth Windows.Web.Http.Headers.HttpCredentialsHeaderValue ProxyAuthorization { get; set; }; // Specifies additional header values of the HTTP POST request when requesting a token - Windows.Foundation.Collections.IMap AdditionalHeaders { get; set; }; + Windows.Foundation.Collections.IMap AdditionalHeaders { get; }; } [contract(OAuthContract, 1)] @@ -147,7 +147,7 @@ namespace Microsoft.Security.Authentication.OAuth CodeChallengeMethodKind CodeChallengeMethod { get; set; }; // Additional parameters passed along in the query string of the request URL. - Windows.Foundation.Collections.IMap AdditionalParams { get; set; }; + Windows.Foundation.Collections.IMap AdditionalParams { get; }; } [contract(OAuthContract, 1)] @@ -354,7 +354,7 @@ namespace Microsoft.Security.Authentication.OAuth String RefreshToken { get; set; }; // Additional parameters passed along in the HTTP request entity-body. - Windows.Foundation.Collections.IMap AdditionalParams { get; set; }; + Windows.Foundation.Collections.IMap AdditionalParams { get; }; } [contract(OAuthContract, 1)] diff --git a/dev/OAuth/TokenRequestParams.cpp b/dev/OAuth/TokenRequestParams.cpp index 99c26463e3..edb6235259 100644 --- a/dev/OAuth/TokenRequestParams.cpp +++ b/dev/OAuth/TokenRequestParams.cpp @@ -187,14 +187,7 @@ namespace winrt::Microsoft::Security::Authentication::OAuth::implementation IMap TokenRequestParams::AdditionalParams() { std::shared_lock guard{ m_mutex }; - return m_additionalParams; - } - - void TokenRequestParams::AdditionalParams(const IMap& value) - { - std::lock_guard guard{ m_mutex }; - check_not_finalized(); - m_additionalParams = value; + return *m_additionalParams; } void TokenRequestParams::finalize() @@ -206,6 +199,7 @@ namespace winrt::Microsoft::Security::Authentication::OAuth::implementation } m_finalized = true; + m_additionalParams->lock(); } std::map TokenRequestParams::params() @@ -232,7 +226,7 @@ namespace winrt::Microsoft::Security::Authentication::OAuth::implementation addIfSet(L"refresh_token", m_refreshToken); if (m_additionalParams) { - for (auto&& pair : m_additionalParams) + for (auto&& pair : IMap{ *m_additionalParams }) { result.emplace(pair.Key(), pair.Value()); } diff --git a/dev/OAuth/TokenRequestParams.h b/dev/OAuth/TokenRequestParams.h index 2829e6e4d2..a77038dde9 100644 --- a/dev/OAuth/TokenRequestParams.h +++ b/dev/OAuth/TokenRequestParams.h @@ -3,6 +3,8 @@ #include +#include "LockableMap.h" + namespace winrt::Microsoft::Security::Authentication::OAuth::implementation { struct TokenRequestParams : TokenRequestParamsT @@ -36,7 +38,6 @@ namespace winrt::Microsoft::Security::Authentication::OAuth::implementation winrt::hstring RefreshToken(); void RefreshToken(const winrt::hstring& value); collections::IMap AdditionalParams(); - void AdditionalParams(const collections::IMap& value); // Implementation functions void finalize(); @@ -64,8 +65,8 @@ namespace winrt::Microsoft::Security::Authentication::OAuth::implementation winrt::hstring m_password; winrt::hstring m_scope; winrt::hstring m_refreshToken; - collections::IMap m_additionalParams = - winrt::multi_threaded_map(); + winrt::com_ptr> m_additionalParams = + winrt::make_self>(); }; } From 8b745af75247fa93b890520dfece8f631aae1152 Mon Sep 17 00:00:00 2001 From: Duncan Horn Date: Mon, 17 Oct 2022 13:02:45 -0700 Subject: [PATCH 3/9] Initial commit of tests --- WindowsAppRuntime.sln | 69 ++- build/NuSpecs/AppxManifest.xml | 6 + .../WindowsAppSDK-Nuget-Native.WinRt.props | 8 +- .../WindowsAppSDK-Nuget-Native.targets | 8 + dev/OAuth/AuthFailure.cpp | 6 +- dev/OAuth/AuthFailure.h | 4 +- dev/OAuth/AuthManager.cpp | 27 +- dev/OAuth/AuthManager.h | 8 +- dev/OAuth/AuthRequestAsyncOperation.cpp | 11 +- dev/OAuth/AuthRequestParams.cpp | 6 +- dev/OAuth/AuthRequestParams.h | 6 +- dev/OAuth/AuthRequestResult.cpp | 6 +- dev/OAuth/AuthRequestResult.h | 4 +- dev/OAuth/AuthResponse.cpp | 6 +- dev/OAuth/AuthResponse.h | 4 +- dev/OAuth/ClientAuthentication.cpp | 6 +- dev/OAuth/ClientAuthentication.h | 6 +- ...icrosoft.Security.Authentication.OAuth.def | 4 - dev/OAuth/OAuth.idl | 2 +- dev/OAuth/TokenFailure.cpp | 6 +- dev/OAuth/TokenFailure.h | 4 +- dev/OAuth/TokenRequestParams.cpp | 6 +- dev/OAuth/TokenRequestParams.h | 6 +- dev/OAuth/TokenRequestResult.cpp | 6 +- dev/OAuth/TokenRequestResult.h | 4 +- dev/OAuth/TokenResponse.cpp | 6 +- dev/OAuth/TokenResponse.h | 4 +- dev/OAuth/common.h | 6 +- specs/WinRT/WinRTAPIContracts.md | 1 + .../appxmanifest.xml | 9 + .../appxmanifest.xml | 9 + .../OAuthTestCommon/OAuthTestCommon.vcxitems | 19 + test/OAuth/OAuthTestCommon/OAuthTestValues.h | 107 ++++ test/OAuth/OAuthTests/OAuthTests.cpp | 462 ++++++++++++++++++ test/OAuth/OAuthTests/OAuthTests.vcxproj | 148 ++++++ .../OAuthTests/OAuthTests.vcxproj.filters | 25 + test/OAuth/OAuthTests/packages.config | 6 + 37 files changed, 918 insertions(+), 113 deletions(-) delete mode 100644 dev/OAuth/Microsoft.Security.Authentication.OAuth.def create mode 100644 test/OAuth/OAuthTestCommon/OAuthTestCommon.vcxitems create mode 100644 test/OAuth/OAuthTestCommon/OAuthTestValues.h create mode 100644 test/OAuth/OAuthTests/OAuthTests.cpp create mode 100644 test/OAuth/OAuthTests/OAuthTests.vcxproj create mode 100644 test/OAuth/OAuthTests/OAuthTests.vcxproj.filters create mode 100644 test/OAuth/OAuthTests/packages.config diff --git a/WindowsAppRuntime.sln b/WindowsAppRuntime.sln index 2f5cf8f25a..5583acec69 100644 --- a/WindowsAppRuntime.sln +++ b/WindowsAppRuntime.sln @@ -1,4 +1,3 @@ - Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 17 VisualStudioVersion = 17.2.32616.157 @@ -406,6 +405,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Test_DeploymentManagerAutoI ProjectSection(ProjectDependencies) = postProject {B73AD907-6164-4294-88FB-F3C9C10DA1F1} = {B73AD907-6164-4294-88FB-F3C9C10DA1F1} EndProjectSection +EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Windows.AppNotifications.Builder.Projection", "dev\Projections\CS\Microsoft.Windows.AppNotifications.Builder.Projection\Microsoft.Windows.AppNotifications.Builder.Projection.csproj", "{50BF3E96-3050-4053-B012-BF6993483DA5}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "OAuth", "OAuth", "{4A3ACD67-5C57-474D-87BF-675676D7451A}" @@ -424,43 +424,13 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "VersionInfoTests", "test\Ve {5E2CC9D5-7C05-41D9-9DB5-EC5DF64BA1DC} = {5E2CC9D5-7C05-41D9-9DB5-EC5DF64BA1DC} EndProjectSection EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "OAuth", "OAuth", "{95AAEE9C-7D53-4B7B-B2C9-DB388BEF6A14}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "OAuthTestCommon", "test\OAuth\OAuthTestCommon\OAuthTestCommon.vcxitems", "{5CFEB8C6-9242-40AC-9CF9-2F5C18001E3C}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "OAuthTests", "test\OAuth\OAuthTests\OAuthTests.vcxproj", "{21651459-648E-475C-91DB-2BDE359C75A4}" +EndProject Global - GlobalSection(SharedMSBuildProjectFiles) = preSolution - test\inc\inc.vcxitems*{08bc78e0-63c6-49a7-81b3-6afc3deac4de}*SharedItemsImports = 4 - dev\PushNotifications\PushNotifications.vcxitems*{103c0c23-7ba8-4d44-a63c-83488e2e3a81}*SharedItemsImports = 9 - test\inc\inc.vcxitems*{2cd5cd9b-cf45-4fa7-9769-ee4e02426bf0}*SharedItemsImports = 4 - dev\EnvironmentManager\API\Microsoft.Process.Environment.vcxitems*{2f3fad1b-d3df-4866-a3a3-c2c777d55638}*SharedItemsImports = 9 - test\inc\inc.vcxitems*{412d023e-8635-4ad2-a0ea-e19e08d36915}*SharedItemsImports = 4 - test\inc\inc.vcxitems*{4b30c685-8490-440f-9879-a75d45daa361}*SharedItemsImports = 4 - dev\UndockedRegFreeWinRT\UndockedRegFreeWinRT.vcxitems*{56371ca6-144b-4989-a4e9-391ad4fa7651}*SharedItemsImports = 9 - test\inc\inc.vcxitems*{56a1d696-feda-4333-bf37-772ebececb10}*SharedItemsImports = 4 - test\inc\inc.vcxitems*{5b2d17fe-c371-417f-860c-3d32397c2404}*SharedItemsImports = 4 - test\inc\inc.vcxitems*{7c502995-59c3-483b-86ba-815985353633}*SharedItemsImports = 4 - dev\Common\Common.vcxitems*{8828053c-d6ec-4744-8624-f8c676c2d4df}*SharedItemsImports = 9 - dev\Licensing\Licensing.vcxitems*{885a43fa-052d-4b0d-a2dc-13ee15796435}*SharedItemsImports = 9 - test\inc\inc.vcxitems*{8e52d7ea-a200-4a6b-ba74-8efb49468caf}*SharedItemsImports = 4 - dev\AppNotifications\AppNotifications.vcxitems*{b4824897-88e0-4927-8fb9-e60106f01ed9}*SharedItemsImports = 9 - test\inc\inc.vcxitems*{b567fe2e-3a03-48d0-b2b5-760cdec35891}*SharedItemsImports = 9 - dev\Common\Common.vcxitems*{b73ad907-6164-4294-88fb-f3c9c10da1f1}*SharedItemsImports = 4 - dev\DynamicDependency\API\DynamicDependency.vcxitems*{b73ad907-6164-4294-88fb-f3c9c10da1f1}*SharedItemsImports = 4 - dev\Licensing\Licensing.vcxitems*{b73ad907-6164-4294-88fb-f3c9c10da1f1}*SharedItemsImports = 4 - dev\PowerNotifications\PowerNotifications.vcxitems*{b73ad907-6164-4294-88fb-f3c9c10da1f1}*SharedItemsImports = 4 - dev\UndockedRegFreeWinRT\UndockedRegFreeWinRT.vcxitems*{b73ad907-6164-4294-88fb-f3c9c10da1f1}*SharedItemsImports = 4 - dev\PowerNotifications\PowerNotifications.vcxitems*{b75c1b22-553c-40e4-b38e-6ab4d01fdb9d}*SharedItemsImports = 9 - dev\DynamicDependency\API\DynamicDependency.vcxitems*{bf055a59-0919-4e34-9b76-dd055495cc5a}*SharedItemsImports = 9 - test\inc\inc.vcxitems*{c62688a1-16a0-4729-b6ed-842f4faa29f3}*SharedItemsImports = 4 - dev\AccessControl\AccessControl.vcxitems*{c91bcb93-9ed1-4acd-85f3-26f9f6ac52e3}*SharedItemsImports = 9 - dev\Common\Common.vcxitems*{d45d4170-e055-4926-8b03-04daa5f02c6c}*SharedItemsImports = 4 - test\inc\inc.vcxitems*{d5667df6-a151-4081-abc7-b93e8e5604ce}*SharedItemsImports = 4 - dev\Deployment\Deployment.vcxitems*{db38fb4d-d04f-4c1d-93e0-f8ae259c5fd6}*SharedItemsImports = 9 - dev\EnvironmentManager\ChangeTracker\ChangeTracker.vcxitems*{e15c3465-9d45-495d-92ce-b91ef45e8623}*SharedItemsImports = 9 - dev\AppLifecycle\AppLifecycle.vcxitems*{e3a522a3-6635-4a42-bded-1af46a15f63c}*SharedItemsImports = 9 - dev\AppNotifications\AppNotificationBuilder\AppNotificationBuilder.vcxitems*{e49329f3-5196-4bba-b5c4-e11ce7efb07a}*SharedItemsImports = 9 - dev\OAuth\OAuth.vcxitems*{3E7FD510-8B66-40E7-A80B-780CB8972F83}*SharedItemsImports = 9 - dev\VersionInfo\VersionInfo.vcxitems*{e3edec7f-a24e-4766-bb1d-6bdfba157c51}*SharedItemsImports = 9 - test\inc\inc.vcxitems*{e5659a29-fe68-417b-9bc5-613073dd54df}*SharedItemsImports = 4 - test\inc\inc.vcxitems*{e977b1bd-00dc-4085-a105-e0a18e0183d7}*SharedItemsImports = 4 - EndGlobalSection GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU Debug|ARM64 = Debug|ARM64 @@ -1622,6 +1592,22 @@ Global {442FB943-1197-48FE-B3B6-8C1BCA1E81E4}.Release|x64.Build.0 = Release|x64 {442FB943-1197-48FE-B3B6-8C1BCA1E81E4}.Release|x86.ActiveCfg = Release|Win32 {442FB943-1197-48FE-B3B6-8C1BCA1E81E4}.Release|x86.Build.0 = Release|Win32 + {21651459-648E-475C-91DB-2BDE359C75A4}.Debug|Any CPU.ActiveCfg = Debug|x64 + {21651459-648E-475C-91DB-2BDE359C75A4}.Debug|Any CPU.Build.0 = Debug|x64 + {21651459-648E-475C-91DB-2BDE359C75A4}.Debug|ARM64.ActiveCfg = Debug|x64 + {21651459-648E-475C-91DB-2BDE359C75A4}.Debug|ARM64.Build.0 = Debug|x64 + {21651459-648E-475C-91DB-2BDE359C75A4}.Debug|x64.ActiveCfg = Debug|x64 + {21651459-648E-475C-91DB-2BDE359C75A4}.Debug|x64.Build.0 = Debug|x64 + {21651459-648E-475C-91DB-2BDE359C75A4}.Debug|x86.ActiveCfg = Debug|Win32 + {21651459-648E-475C-91DB-2BDE359C75A4}.Debug|x86.Build.0 = Debug|Win32 + {21651459-648E-475C-91DB-2BDE359C75A4}.Release|Any CPU.ActiveCfg = Release|x64 + {21651459-648E-475C-91DB-2BDE359C75A4}.Release|Any CPU.Build.0 = Release|x64 + {21651459-648E-475C-91DB-2BDE359C75A4}.Release|ARM64.ActiveCfg = Release|x64 + {21651459-648E-475C-91DB-2BDE359C75A4}.Release|ARM64.Build.0 = Release|x64 + {21651459-648E-475C-91DB-2BDE359C75A4}.Release|x64.ActiveCfg = Release|x64 + {21651459-648E-475C-91DB-2BDE359C75A4}.Release|x64.Build.0 = Release|x64 + {21651459-648E-475C-91DB-2BDE359C75A4}.Release|x86.ActiveCfg = Release|Win32 + {21651459-648E-475C-91DB-2BDE359C75A4}.Release|x86.Build.0 = Release|Win32 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -1759,6 +1745,9 @@ Global {2A2D1131-273C-4E17-BCD3-8812170A4B95} = {448ED2E5-0B37-4D97-9E6B-8C10A507976A} {E3EDEC7F-A24E-4766-BB1D-6BDFBA157C51} = {2A2D1131-273C-4E17-BCD3-8812170A4B95} {442FB943-1197-48FE-B3B6-8C1BCA1E81E4} = {8630F7AA-2969-4DC9-8700-9B468C1DC21D} + {95AAEE9C-7D53-4B7B-B2C9-DB388BEF6A14} = {8630F7AA-2969-4DC9-8700-9B468C1DC21D} + {5CFEB8C6-9242-40AC-9CF9-2F5C18001E3C} = {95AAEE9C-7D53-4B7B-B2C9-DB388BEF6A14} + {21651459-648E-475C-91DB-2BDE359C75A4} = {95AAEE9C-7D53-4B7B-B2C9-DB388BEF6A14} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {4B3D7591-CFEC-4762-9A07-ABE99938FB77} @@ -1766,13 +1755,17 @@ Global GlobalSection(SharedMSBuildProjectFiles) = preSolution test\inc\inc.vcxitems*{08bc78e0-63c6-49a7-81b3-6afc3deac4de}*SharedItemsImports = 4 dev\PushNotifications\PushNotifications.vcxitems*{103c0c23-7ba8-4d44-a63c-83488e2e3a81}*SharedItemsImports = 9 + test\inc\inc.vcxitems*{21651459-648e-475c-91db-2bde359c75a4}*SharedItemsImports = 4 + test\OAuth\OAuthTestCommon\OAuthTestCommon.vcxitems*{21651459-648e-475c-91db-2bde359c75a4}*SharedItemsImports = 4 test\inc\inc.vcxitems*{2cd5cd9b-cf45-4fa7-9769-ee4e02426bf0}*SharedItemsImports = 4 dev\EnvironmentManager\API\Microsoft.Process.Environment.vcxitems*{2f3fad1b-d3df-4866-a3a3-c2c777d55638}*SharedItemsImports = 9 + dev\OAuth\OAuth.vcxitems*{3e7fd510-8b66-40e7-a80b-780cb8972f83}*SharedItemsImports = 9 test\inc\inc.vcxitems*{412d023e-8635-4ad2-a0ea-e19e08d36915}*SharedItemsImports = 4 test\inc\inc.vcxitems*{4b30c685-8490-440f-9879-a75d45daa361}*SharedItemsImports = 4 dev\UndockedRegFreeWinRT\UndockedRegFreeWinRT.vcxitems*{56371ca6-144b-4989-a4e9-391ad4fa7651}*SharedItemsImports = 9 test\inc\inc.vcxitems*{56a1d696-feda-4333-bf37-772ebececb10}*SharedItemsImports = 4 test\inc\inc.vcxitems*{5b2d17fe-c371-417f-860c-3d32397c2404}*SharedItemsImports = 4 + test\OAuth\OAuthTestCommon\OAuthTestCommon.vcxitems*{5cfeb8c6-9242-40ac-9cf9-2f5c18001e3c}*SharedItemsImports = 9 test\inc\inc.vcxitems*{7c502995-59c3-483b-86ba-815985353633}*SharedItemsImports = 4 dev\Common\Common.vcxitems*{8828053c-d6ec-4744-8624-f8c676c2d4df}*SharedItemsImports = 9 dev\Licensing\Licensing.vcxitems*{885a43fa-052d-4b0d-a2dc-13ee15796435}*SharedItemsImports = 9 @@ -1793,6 +1786,8 @@ Global dev\Deployment\Deployment.vcxitems*{db38fb4d-d04f-4c1d-93e0-f8ae259c5fd6}*SharedItemsImports = 9 dev\EnvironmentManager\ChangeTracker\ChangeTracker.vcxitems*{e15c3465-9d45-495d-92ce-b91ef45e8623}*SharedItemsImports = 9 dev\AppLifecycle\AppLifecycle.vcxitems*{e3a522a3-6635-4a42-bded-1af46a15f63c}*SharedItemsImports = 9 + dev\VersionInfo\VersionInfo.vcxitems*{e3edec7f-a24e-4766-bb1d-6bdfba157c51}*SharedItemsImports = 9 + dev\AppNotifications\AppNotificationBuilder\AppNotificationBuilder.vcxitems*{e49329f3-5196-4bba-b5c4-e11ce7efb07a}*SharedItemsImports = 9 test\inc\inc.vcxitems*{e5659a29-fe68-417b-9bc5-613073dd54df}*SharedItemsImports = 4 test\inc\inc.vcxitems*{e977b1bd-00dc-4085-a105-e0a18e0183d7}*SharedItemsImports = 4 EndGlobalSection diff --git a/build/NuSpecs/AppxManifest.xml b/build/NuSpecs/AppxManifest.xml index 65e3d8f7e3..10a791889d 100644 --- a/build/NuSpecs/AppxManifest.xml +++ b/build/NuSpecs/AppxManifest.xml @@ -58,6 +58,12 @@ + + + + + + diff --git a/build/NuSpecs/WindowsAppSDK-Nuget-Native.WinRt.props b/build/NuSpecs/WindowsAppSDK-Nuget-Native.WinRt.props index 9b721a7abc..6b55c4af5b 100644 --- a/build/NuSpecs/WindowsAppSDK-Nuget-Native.WinRt.props +++ b/build/NuSpecs/WindowsAppSDK-Nuget-Native.WinRt.props @@ -36,7 +36,7 @@ true - $(MSBuildThisFileDirectory)..\..\lib\uap10.0\Microsoft.Windows.System.winmd $(MSBuildThisFileDirectory)..\..\runtimes\win10-$(_WindowsAppSDKFoundationPlatform)\native\Microsoft.WindowsAppRuntime.dll @@ -48,6 +48,12 @@ $(MSBuildThisFileDirectory)..\..\runtimes\win10-$(_WindowsAppSDKFoundationPlatform)\native\Microsoft.WindowsAppRuntime.dll true + + $(MSBuildThisFileDirectory)..\..\lib\uap10.0\Microsoft.Windows.Security.Authentication.OAuth.winmd + $(MSBuildThisFileDirectory)..\..\runtimes\win10-$(_WindowsAppSDKFoundationPlatform)\native\Microsoft.WindowsAppRuntime.dll + true + diff --git a/build/NuSpecs/WindowsAppSDK-Nuget-Native.targets b/build/NuSpecs/WindowsAppSDK-Nuget-Native.targets index cc8c36d467..e963e26a04 100644 --- a/build/NuSpecs/WindowsAppSDK-Nuget-Native.targets +++ b/build/NuSpecs/WindowsAppSDK-Nuget-Native.targets @@ -68,6 +68,14 @@ + + + false + Microsoft.WindowsAppRuntime.dll + + + diff --git a/dev/OAuth/AuthFailure.cpp b/dev/OAuth/AuthFailure.cpp index d0d6ce30cb..b1367b0f44 100644 --- a/dev/OAuth/AuthFailure.cpp +++ b/dev/OAuth/AuthFailure.cpp @@ -2,14 +2,14 @@ #include "common.h" #include "AuthFailure.h" -#include +#include using namespace std::literals; -using namespace winrt::Microsoft::Security::Authentication::OAuth; +using namespace winrt::Microsoft::Windows::Security::Authentication::OAuth; using namespace winrt::Windows::Foundation; using namespace winrt::Windows::Foundation::Collections; -namespace winrt::Microsoft::Security::Authentication::OAuth::implementation +namespace winrt::Microsoft::Windows::Security::Authentication::OAuth::implementation { AuthFailure::AuthFailure(const Uri& responseUri) { diff --git a/dev/OAuth/AuthFailure.h b/dev/OAuth/AuthFailure.h index 37c80761ff..e78fcf0cb1 100644 --- a/dev/OAuth/AuthFailure.h +++ b/dev/OAuth/AuthFailure.h @@ -1,7 +1,7 @@ #pragma once -#include +#include -namespace winrt::Microsoft::Security::Authentication::OAuth::implementation +namespace winrt::Microsoft::Windows::Security::Authentication::OAuth::implementation { struct AuthFailure : AuthFailureT { diff --git a/dev/OAuth/AuthManager.cpp b/dev/OAuth/AuthManager.cpp index 0653d3e9e2..a55c16e32a 100644 --- a/dev/OAuth/AuthManager.cpp +++ b/dev/OAuth/AuthManager.cpp @@ -2,7 +2,7 @@ #include "common.h" #include "AuthManager.h" -#include +#include #include "AuthRequestParams.h" #include "TokenFailure.h" @@ -10,25 +10,42 @@ #include "TokenRequestResult.h" #include "TokenResponse.h" -using namespace winrt::Microsoft::Security::Authentication::OAuth; +using namespace winrt::Microsoft::Windows::Security::Authentication::OAuth; using namespace winrt::Windows::Data::Json; using namespace winrt::Windows::Foundation; using namespace winrt::Windows::Foundation::Collections; using namespace winrt::Windows::Web::Http; -namespace winrt::Microsoft::Security::Authentication::OAuth::factory_implementation +namespace winrt::Microsoft::Windows::Security::Authentication::OAuth::factory_implementation { IAsyncOperation AuthManager::InitiateAuthRequestAsync(const Uri& authEndpoint, const oauth::AuthRequestParams& params) { - auto asyncOp = winrt::make_self(authEndpoint, - winrt::get_self(params)); + auto paramsImpl = winrt::get_self(params); + auto asyncOp = winrt::make_self(authEndpoint, paramsImpl); { std::lock_guard guard{ m_mutex }; m_pendingAuthRequests.push_back(AuthRequestState{ params.State(), asyncOp }); } + try + { + // Pipe server has been successfully set up. Initiate the launch + auto url = paramsImpl->create_url(authEndpoint); + + auto launchResult = ::ShellExecuteW(nullptr, L"open", url.c_str(), nullptr, nullptr, SW_SHOWDEFAULT); + if (auto code = reinterpret_cast(launchResult); code < 32) + { + throw winrt::hresult_error(HRESULT_FROM_WIN32(::GetLastError()), L"Failed to launch browser"); + } + } + catch (...) + { + try_remove(asyncOp.get()); + throw; + } + return *asyncOp; } diff --git a/dev/OAuth/AuthManager.h b/dev/OAuth/AuthManager.h index abe70a11f3..42bbdd2b8e 100644 --- a/dev/OAuth/AuthManager.h +++ b/dev/OAuth/AuthManager.h @@ -1,14 +1,14 @@ #pragma once -#include +#include #include "AuthRequestAsyncOperation.h" -namespace winrt::Microsoft::Security::Authentication::OAuth::implementation +namespace winrt::Microsoft::Windows::Security::Authentication::OAuth::implementation { struct AuthManager; } -namespace winrt::Microsoft::Security::Authentication::OAuth::factory_implementation +namespace winrt::Microsoft::Windows::Security::Authentication::OAuth::factory_implementation { struct AuthRequestState { @@ -39,7 +39,7 @@ namespace winrt::Microsoft::Security::Authentication::OAuth::factory_implementat }; } -namespace winrt::Microsoft::Security::Authentication::OAuth::implementation +namespace winrt::Microsoft::Windows::Security::Authentication::OAuth::implementation { struct AuthManager { diff --git a/dev/OAuth/AuthRequestAsyncOperation.cpp b/dev/OAuth/AuthRequestAsyncOperation.cpp index 8466909718..c0f4c8b4de 100644 --- a/dev/OAuth/AuthRequestAsyncOperation.cpp +++ b/dev/OAuth/AuthRequestAsyncOperation.cpp @@ -8,7 +8,7 @@ #include using namespace std::literals; -using namespace winrt::Microsoft::Security::Authentication::OAuth; +using namespace winrt::Microsoft::Windows::Security::Authentication::OAuth; using namespace winrt::Windows::Foundation; using namespace winrt::Windows::Foundation::Collections; using namespace winrt::Windows::Security::Cryptography; @@ -66,15 +66,6 @@ AuthRequestAsyncOperation::AuthRequestAsyncOperation(const Uri& authEndpoint, m_ptp = ::CreateThreadpoolWait(async_callback, this, nullptr); connect_to_new_client(); - - // Pipe server has been successfully set up. Initiate the launch - auto url = m_params->create_url(authEndpoint); - - auto launchResult = ::ShellExecuteW(nullptr, L"open", url.c_str(), nullptr, nullptr, SW_SHOWDEFAULT); - if (auto code = reinterpret_cast(launchResult); code < 32) - { - throw winrt::hresult_error(static_cast(code), L"Failed to launch browser"); - } } catch (...) { diff --git a/dev/OAuth/AuthRequestParams.cpp b/dev/OAuth/AuthRequestParams.cpp index cfd455cb01..9d2865af7a 100644 --- a/dev/OAuth/AuthRequestParams.cpp +++ b/dev/OAuth/AuthRequestParams.cpp @@ -2,15 +2,15 @@ #include "common.h" #include "AuthRequestParams.h" -#include +#include -using namespace winrt::Microsoft::Security::Authentication::OAuth; +using namespace winrt::Microsoft::Windows::Security::Authentication::OAuth; using namespace winrt::Windows::Foundation; using namespace winrt::Windows::Foundation::Collections; using namespace winrt::Windows::Security::Cryptography; using namespace winrt::Windows::Security::Cryptography::Core; -namespace winrt::Microsoft::Security::Authentication::OAuth::implementation +namespace winrt::Microsoft::Windows::Security::Authentication::OAuth::implementation { AuthRequestParams::AuthRequestParams(const winrt::hstring& responseType, const winrt::hstring& clientId) : m_responseType(responseType), diff --git a/dev/OAuth/AuthRequestParams.h b/dev/OAuth/AuthRequestParams.h index f47de5deb3..abcf636431 100644 --- a/dev/OAuth/AuthRequestParams.h +++ b/dev/OAuth/AuthRequestParams.h @@ -1,11 +1,11 @@ #pragma once -#include +#include #include #include "LockableMap.h" -namespace winrt::Microsoft::Security::Authentication::OAuth::implementation +namespace winrt::Microsoft::Windows::Security::Authentication::OAuth::implementation { struct AuthRequestParams : AuthRequestParamsT { @@ -68,7 +68,7 @@ namespace winrt::Microsoft::Security::Authentication::OAuth::implementation }; } -namespace winrt::Microsoft::Security::Authentication::OAuth::factory_implementation +namespace winrt::Microsoft::Windows::Security::Authentication::OAuth::factory_implementation { struct AuthRequestParams : AuthRequestParamsT { diff --git a/dev/OAuth/AuthRequestResult.cpp b/dev/OAuth/AuthRequestResult.cpp index eb6960abf0..5c3f62618b 100644 --- a/dev/OAuth/AuthRequestResult.cpp +++ b/dev/OAuth/AuthRequestResult.cpp @@ -2,16 +2,16 @@ #include "common.h" #include "AuthRequestResult.h" -#include +#include #include "AuthFailure.h" #include "AuthResponse.h" -using namespace winrt::Microsoft::Security::Authentication::OAuth; +using namespace winrt::Microsoft::Windows::Security::Authentication::OAuth; using namespace winrt::Windows::Foundation; using namespace winrt::Windows::Foundation::Collections; -namespace winrt::Microsoft::Security::Authentication::OAuth::implementation +namespace winrt::Microsoft::Windows::Security::Authentication::OAuth::implementation { AuthRequestResult::AuthRequestResult(AuthRequestParams* params, const Uri& responseUri) : m_responseUri(responseUri) { diff --git a/dev/OAuth/AuthRequestResult.h b/dev/OAuth/AuthRequestResult.h index 62d4470790..3da290919a 100644 --- a/dev/OAuth/AuthRequestResult.h +++ b/dev/OAuth/AuthRequestResult.h @@ -1,9 +1,9 @@ #pragma once -#include +#include #include "AuthRequestParams.h" -namespace winrt::Microsoft::Security::Authentication::OAuth::implementation +namespace winrt::Microsoft::Windows::Security::Authentication::OAuth::implementation { struct AuthRequestResult : AuthRequestResultT { diff --git a/dev/OAuth/AuthResponse.cpp b/dev/OAuth/AuthResponse.cpp index 880fe9f488..a2a8ff345a 100644 --- a/dev/OAuth/AuthResponse.cpp +++ b/dev/OAuth/AuthResponse.cpp @@ -2,14 +2,14 @@ #include "common.h" #include "AuthResponse.h" -#include +#include using namespace std::literals; -using namespace winrt::Microsoft::Security::Authentication::OAuth; +using namespace winrt::Microsoft::Windows::Security::Authentication::OAuth; using namespace winrt::Windows::Foundation; using namespace winrt::Windows::Foundation::Collections; -namespace winrt::Microsoft::Security::Authentication::OAuth::implementation +namespace winrt::Microsoft::Windows::Security::Authentication::OAuth::implementation { AuthResponse::AuthResponse(AuthRequestParams* requestParams, const Uri& responseUri) : m_requestParams(requestParams->get_strong()) diff --git a/dev/OAuth/AuthResponse.h b/dev/OAuth/AuthResponse.h index 1623c4476a..7c1fd95722 100644 --- a/dev/OAuth/AuthResponse.h +++ b/dev/OAuth/AuthResponse.h @@ -1,9 +1,9 @@ #pragma once -#include +#include #include "AuthRequestParams.h" -namespace winrt::Microsoft::Security::Authentication::OAuth::implementation +namespace winrt::Microsoft::Windows::Security::Authentication::OAuth::implementation { struct AuthResponse : AuthResponseT { diff --git a/dev/OAuth/ClientAuthentication.cpp b/dev/OAuth/ClientAuthentication.cpp index 9a2cd00cc2..902e2d2d68 100644 --- a/dev/OAuth/ClientAuthentication.cpp +++ b/dev/OAuth/ClientAuthentication.cpp @@ -2,15 +2,15 @@ #include "common.h" #include "ClientAuthentication.h" -#include +#include -using namespace winrt::Microsoft::Security::Authentication::OAuth; +using namespace winrt::Microsoft::Windows::Security::Authentication::OAuth; using namespace winrt::Windows::Foundation; using namespace winrt::Windows::Foundation::Collections; using namespace winrt::Windows::Security::Cryptography; using namespace winrt::Windows::Web::Http::Headers; -namespace winrt::Microsoft::Security::Authentication::OAuth::implementation +namespace winrt::Microsoft::Windows::Security::Authentication::OAuth::implementation { ClientAuthentication::ClientAuthentication(const HttpCredentialsHeaderValue& authorization) : m_authorization(authorization) diff --git a/dev/OAuth/ClientAuthentication.h b/dev/OAuth/ClientAuthentication.h index 5fb6aee201..cc1fc35670 100644 --- a/dev/OAuth/ClientAuthentication.h +++ b/dev/OAuth/ClientAuthentication.h @@ -1,9 +1,9 @@ #pragma once -#include +#include #include -namespace winrt::Microsoft::Security::Authentication::OAuth::implementation +namespace winrt::Microsoft::Windows::Security::Authentication::OAuth::implementation { struct ClientAuthentication : ClientAuthenticationT { @@ -27,7 +27,7 @@ namespace winrt::Microsoft::Security::Authentication::OAuth::implementation winrt::multi_threaded_map(); }; } -namespace winrt::Microsoft::Security::Authentication::OAuth::factory_implementation +namespace winrt::Microsoft::Windows::Security::Authentication::OAuth::factory_implementation { struct ClientAuthentication : ClientAuthenticationT { diff --git a/dev/OAuth/Microsoft.Security.Authentication.OAuth.def b/dev/OAuth/Microsoft.Security.Authentication.OAuth.def deleted file mode 100644 index 7c97f8b141..0000000000 --- a/dev/OAuth/Microsoft.Security.Authentication.OAuth.def +++ /dev/null @@ -1,4 +0,0 @@ -LIBRARY Microsoft.Security.Authentication.OAuth -EXPORTS - DllCanUnloadNow PRIVATE - DllGetActivationFactory PRIVATE diff --git a/dev/OAuth/OAuth.idl b/dev/OAuth/OAuth.idl index c8d34fdb80..4dea5b56aa 100644 --- a/dev/OAuth/OAuth.idl +++ b/dev/OAuth/OAuth.idl @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation and Contributors. // Licensed under the MIT License. -namespace Microsoft.Security.Authentication.OAuth +namespace Microsoft.Windows.Security.Authentication.OAuth { [contractversion(1)] apicontract OAuthContract {}; diff --git a/dev/OAuth/TokenFailure.cpp b/dev/OAuth/TokenFailure.cpp index d03de7df3d..b4164e2317 100644 --- a/dev/OAuth/TokenFailure.cpp +++ b/dev/OAuth/TokenFailure.cpp @@ -2,15 +2,15 @@ #include "common.h" #include "TokenFailure.h" -#include +#include using namespace std::literals; -using namespace winrt::Microsoft::Security::Authentication::OAuth; +using namespace winrt::Microsoft::Windows::Security::Authentication::OAuth; using namespace winrt::Windows::Data::Json; using namespace winrt::Windows::Foundation; using namespace winrt::Windows::Foundation::Collections; -namespace winrt::Microsoft::Security::Authentication::OAuth::implementation +namespace winrt::Microsoft::Windows::Security::Authentication::OAuth::implementation { TokenFailure::TokenFailure(TokenFailureKind kind, winrt::hresult code) : m_kind(kind), m_errorCode(code) {} diff --git a/dev/OAuth/TokenFailure.h b/dev/OAuth/TokenFailure.h index 7b9e5b3d76..5a33bde7e0 100644 --- a/dev/OAuth/TokenFailure.h +++ b/dev/OAuth/TokenFailure.h @@ -1,7 +1,7 @@ #pragma once -#include +#include -namespace winrt::Microsoft::Security::Authentication::OAuth::implementation +namespace winrt::Microsoft::Windows::Security::Authentication::OAuth::implementation { struct TokenFailure : TokenFailureT { diff --git a/dev/OAuth/TokenRequestParams.cpp b/dev/OAuth/TokenRequestParams.cpp index edb6235259..4dea605f5b 100644 --- a/dev/OAuth/TokenRequestParams.cpp +++ b/dev/OAuth/TokenRequestParams.cpp @@ -2,15 +2,15 @@ #include "common.h" #include "TokenRequestParams.h" -#include +#include #include "AuthResponse.h" -using namespace winrt::Microsoft::Security::Authentication::OAuth; +using namespace winrt::Microsoft::Windows::Security::Authentication::OAuth; using namespace winrt::Windows::Foundation; using namespace Collections; -namespace winrt::Microsoft::Security::Authentication::OAuth::implementation +namespace winrt::Microsoft::Windows::Security::Authentication::OAuth::implementation { TokenRequestParams::TokenRequestParams(const winrt::hstring& grantType) : m_grantType(grantType) {} diff --git a/dev/OAuth/TokenRequestParams.h b/dev/OAuth/TokenRequestParams.h index a77038dde9..b6aa5dade7 100644 --- a/dev/OAuth/TokenRequestParams.h +++ b/dev/OAuth/TokenRequestParams.h @@ -1,11 +1,11 @@ #pragma once -#include +#include #include #include "LockableMap.h" -namespace winrt::Microsoft::Security::Authentication::OAuth::implementation +namespace winrt::Microsoft::Windows::Security::Authentication::OAuth::implementation { struct TokenRequestParams : TokenRequestParamsT { @@ -70,7 +70,7 @@ namespace winrt::Microsoft::Security::Authentication::OAuth::implementation }; } -namespace winrt::Microsoft::Security::Authentication::OAuth::factory_implementation +namespace winrt::Microsoft::Windows::Security::Authentication::OAuth::factory_implementation { struct TokenRequestParams : TokenRequestParamsT { diff --git a/dev/OAuth/TokenRequestResult.cpp b/dev/OAuth/TokenRequestResult.cpp index bc1304968e..670a156ad3 100644 --- a/dev/OAuth/TokenRequestResult.cpp +++ b/dev/OAuth/TokenRequestResult.cpp @@ -2,18 +2,18 @@ #include "common.h" #include "TokenRequestResult.h" -#include +#include #include "TokenFailure.h" #include "TokenResponse.h" -using namespace winrt::Microsoft::Security::Authentication::OAuth; +using namespace winrt::Microsoft::Windows::Security::Authentication::OAuth; using namespace winrt::Windows::Data::Json; using namespace winrt::Windows::Foundation; using namespace winrt::Windows::Foundation::Collections; using namespace winrt::Windows::Web::Http; -namespace winrt::Microsoft::Security::Authentication::OAuth::implementation +namespace winrt::Microsoft::Windows::Security::Authentication::OAuth::implementation { TokenRequestResult::TokenRequestResult(HttpResponseMessage responseMessage, oauth::TokenResponse response, oauth::TokenFailure failure) : diff --git a/dev/OAuth/TokenRequestResult.h b/dev/OAuth/TokenRequestResult.h index 98889adea5..b8df9fb30b 100644 --- a/dev/OAuth/TokenRequestResult.h +++ b/dev/OAuth/TokenRequestResult.h @@ -1,7 +1,7 @@ #pragma once -#include +#include -namespace winrt::Microsoft::Security::Authentication::OAuth::implementation +namespace winrt::Microsoft::Windows::Security::Authentication::OAuth::implementation { struct TokenRequestResult : TokenRequestResultT { diff --git a/dev/OAuth/TokenResponse.cpp b/dev/OAuth/TokenResponse.cpp index 0ce6bab974..9b62e79100 100644 --- a/dev/OAuth/TokenResponse.cpp +++ b/dev/OAuth/TokenResponse.cpp @@ -2,14 +2,14 @@ #include "common.h" #include "TokenResponse.h" -#include +#include -using namespace winrt::Microsoft::Security::Authentication::OAuth; +using namespace winrt::Microsoft::Windows::Security::Authentication::OAuth; using namespace winrt::Windows::Data::Json; using namespace winrt::Windows::Foundation; using namespace winrt::Windows::Foundation::Collections; -namespace winrt::Microsoft::Security::Authentication::OAuth::implementation +namespace winrt::Microsoft::Windows::Security::Authentication::OAuth::implementation { TokenResponse::TokenResponse(const json::JsonObject& jsonObject) { diff --git a/dev/OAuth/TokenResponse.h b/dev/OAuth/TokenResponse.h index 4becf5f712..ccef9e5cce 100644 --- a/dev/OAuth/TokenResponse.h +++ b/dev/OAuth/TokenResponse.h @@ -1,7 +1,7 @@ #pragma once -#include +#include -namespace winrt::Microsoft::Security::Authentication::OAuth::implementation +namespace winrt::Microsoft::Windows::Security::Authentication::OAuth::implementation { struct TokenResponse : TokenResponseT { diff --git a/dev/OAuth/common.h b/dev/OAuth/common.h index 6f0bb54bed..ad167e8980 100644 --- a/dev/OAuth/common.h +++ b/dev/OAuth/common.h @@ -1,6 +1,6 @@ -#pragma once +#pragma once -#include +#include #include #include #include @@ -14,7 +14,7 @@ namespace crypto = winrt::Windows::Security::Cryptography; namespace foundation = winrt::Windows::Foundation; namespace http = winrt::Windows::Web::Http; namespace json = winrt::Windows::Data::Json; -namespace oauth = winrt::Microsoft::Security::Authentication::OAuth; +namespace oauth = winrt::Microsoft::Windows::Security::Authentication::OAuth; namespace streams = winrt::Windows::Storage::Streams; #include "Crypto.h" diff --git a/specs/WinRT/WinRTAPIContracts.md b/specs/WinRT/WinRTAPIContracts.md index a5fc7cd562..639633cf49 100644 --- a/specs/WinRT/WinRTAPIContracts.md +++ b/specs/WinRT/WinRTAPIContracts.md @@ -111,6 +111,7 @@ The list of all contracts defined across Windows App SDK | MRTCore | windowsappsdk | MrtCoreContract | Microsoft.Windows.ApplicationModel.Resources | | | PowerNotifications | windowsappsdk | PowerNotificationsContract | Microsoft.Windows.System.Power | | | PushNotifications | windowsappsdk | PushNotificationsContract | Microsoft.Windows.PushNotifications | | +| OAuth | windowsappsdk | OAuthContract | Microsoft.Windows.Security.Authentication.OAuth | | | WinUI | winui | HostingContract | Microsoft.UI.Xaml.Hosting | | | WinUI | winui | WinUIContract | Microsoft.UI.Xaml | | | WinUI | winui | WinUIControlsContract | Microsoft.UI.Xaml.Controls | | diff --git a/test/Deployment/data/WindowsAppRuntime.Test.Framework/appxmanifest.xml b/test/Deployment/data/WindowsAppRuntime.Test.Framework/appxmanifest.xml index a3c742d4a2..54a6121065 100644 --- a/test/Deployment/data/WindowsAppRuntime.Test.Framework/appxmanifest.xml +++ b/test/Deployment/data/WindowsAppRuntime.Test.Framework/appxmanifest.xml @@ -106,5 +106,14 @@ + + + Microsoft.WindowsAppRuntime.dll + + + + + + diff --git a/test/DynamicDependency/data/Microsoft.WindowsAppRuntime.Framework/appxmanifest.xml b/test/DynamicDependency/data/Microsoft.WindowsAppRuntime.Framework/appxmanifest.xml index 6c640829b6..9c9b2ab1e8 100644 --- a/test/DynamicDependency/data/Microsoft.WindowsAppRuntime.Framework/appxmanifest.xml +++ b/test/DynamicDependency/data/Microsoft.WindowsAppRuntime.Framework/appxmanifest.xml @@ -94,6 +94,15 @@ + + + Microsoft.WindowsAppRuntime.dll + + + + + + Microsoft.WindowsAppRuntime.dll diff --git a/test/OAuth/OAuthTestCommon/OAuthTestCommon.vcxitems b/test/OAuth/OAuthTestCommon/OAuthTestCommon.vcxitems new file mode 100644 index 0000000000..9f6b21550c --- /dev/null +++ b/test/OAuth/OAuthTestCommon/OAuthTestCommon.vcxitems @@ -0,0 +1,19 @@ + + + + $(MSBuildAllProjects);$(MSBuildThisFileFullPath) + true + {5cfeb8c6-9242-40ac-9cf9-2f5c18001e3c} + + + + %(AdditionalIncludeDirectories);$(MSBuildThisFileDirectory) + + + + + + + + + \ No newline at end of file diff --git a/test/OAuth/OAuthTestCommon/OAuthTestValues.h b/test/OAuth/OAuthTestCommon/OAuthTestValues.h new file mode 100644 index 0000000000..c4fd9e3124 --- /dev/null +++ b/test/OAuth/OAuthTestCommon/OAuthTestValues.h @@ -0,0 +1,107 @@ +#pragma once + +#include +#include + +#include +#include + +// Strings associated with errors +namespace strings::error +{ + inline constexpr std::wstring_view invalid_request = L"invalid_request"; + inline constexpr std::wstring_view unauthorized_client = L"unauthorized_client"; + inline constexpr std::wstring_view access_denied = L"access_denied"; + inline constexpr std::wstring_view unsupported_response_type = L"unsupported_response_type"; + inline constexpr std::wstring_view invalid_scope = L"invalid_scope"; + inline constexpr std::wstring_view server_error = L"server_error"; + inline constexpr std::wstring_view temporarily_unavailable = L"temporarily_unavailable"; +} + +// Strings associated with the request +namespace strings::request +{ + inline constexpr std::wstring_view response_type = L"response_type"; + inline constexpr std::wstring_view client_id = L"client_id"; + inline constexpr std::wstring_view redirect_uri = L"redirect_uri"; + inline constexpr std::wstring_view scope = L"scope"; + inline constexpr std::wstring_view state = L"state"; + inline constexpr std::wstring_view code_challenge = L"code_challenge"; + inline constexpr std::wstring_view code_challenge_method = L"code_challenge_method"; +} + +namespace strings::response_type +{ + + inline constexpr std::wstring_view code = L"code"; + inline constexpr std::wstring_view token = L"token"; +} + +// Test parameters that we embed in the client_id +namespace strings::client_id +{ + inline constexpr std::wstring_view scenario = L"scenario"; + //inline constexpr std::wstring_view expect_success = L"expect_success"; + //inline constexpr std::wstring_view expect_additional_params = L"expect_additional_params"; + //inline constexpr std::wstring_view report_expires_in = L"report_expires_in"; + //inline constexpr std::wstring_view limit_scope = L"limit_scope"; +} + +// We're not issuing real tokens, so use a predictable string for validation. This string is specifically formulated to +// ensure that we escape/encode characters properly +inline constexpr std::wstring_view token = L"tacos=yummy&location=\"my tummy\""; + +struct uri_builder +{ + std::wstring uri; + wchar_t prefix; + + uri_builder(const winrt::Windows::Foundation::Uri& uri, bool useQuery = true) : + uri(uri.RawUri()) + { + if (useQuery) + { + prefix = uri.Query().empty() ? L'?' : '&'; + } + else + { + prefix = '#'; + } + } + + void add(std::wstring_view name, std::wstring_view value) + { + assert(!name.empty() && !value.empty()); + + uri.push_back(prefix); + prefix = L'&'; + uri.append(winrt::Windows::Foundation::Uri::EscapeComponent(name)); + uri.push_back(L'='); + uri.append(winrt::Windows::Foundation::Uri::EscapeComponent(value)); + } + + void add_optional(std::wstring_view name, std::wstring_view value) + { + if (!value.empty()) + { + add(name, value); + } + } + + winrt::Windows::Foundation::Uri get() + { + return winrt::Windows::Foundation::Uri{ uri }; + } +}; + +// Each test has its own "client id" so that we can differentiate which test is running. Note that this intentionally +// uses characters that need to be urlencoded +// #define TEST_CLIENT_ID(test) L"client:test=" ##test +// +//#define INCORRECT_STATE_CLIENT_ID TEST_CLIENT_ID(IncorrectState) +//#define COMPLETE_TWICE_CLIENT_ID TEST_CLIENT_ID(CompleteTwice) +// +//// We control the behavior of the fake user agent and authorization server through the client id +//#define TEST_CLIENT_ID(expectSuccess, expectAdditionalParams, reportExpiresIn, limitScope) \ +// L"expect_success=" ##expectSuccess "&expect_additional_params=" ##expectAdditionalParams \ +// "&report_expires_in=" ##reportExpiresIn L"&limit_scope=" ##limitScope diff --git a/test/OAuth/OAuthTests/OAuthTests.cpp b/test/OAuth/OAuthTests/OAuthTests.cpp new file mode 100644 index 0000000000..8efb2a119a --- /dev/null +++ b/test/OAuth/OAuthTests/OAuthTests.cpp @@ -0,0 +1,462 @@ + +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include // Included last to enable the most features + +#define VERIFY_WINSOCK_SUCCEEDED(expr) \ + do { \ + auto wsaResultCode = (expr); \ + if (wsaResultCode == SOCKET_ERROR) { \ + auto wsaLastError = ::WSAGetLastError(); \ + std::wstring msg = L"WSASucceeded(" #expr ") - Error (" + std::to_wstring(wsaLastError) + L")"; \ + WEX::TestExecution::Verify::Fail(msg.c_str(), WEX::TestExecution::ErrorInfo{ __WFILE__, __WFUNCTION__, __LINE__ }); \ + } \ + } while(0, 0) + +using namespace std::literals; +using namespace WEX::Common; +using namespace WEX::Logging; +using namespace WEX::TestExecution; +using namespace winrt::Microsoft::Windows::Security::Authentication::OAuth; +using namespace winrt::Windows::Data::Json; +using namespace winrt::Windows::Foundation; +using namespace winrt::Windows::Foundation::Collections; +using namespace winrt::Windows::Security::Cryptography; +using namespace winrt::Windows::Storage::Streams; + +EXTERN_C IMAGE_DOS_HEADER __ImageBase; + +struct OAuthTests +{ + BEGIN_TEST_CLASS(OAuthTests) + TEST_CLASS_PROPERTY(L"ThreadingModel", L"MTA") + END_TEST_CLASS() + + TEST_CLASS_SETUP(Setup) + { + Test::Bootstrap::Setup(); + + // Detour ShellExecuteW + ::DetourTransactionBegin(); + ::DetourUpdateThread(::GetCurrentThread()); + if (auto err = ::DetourAttach(reinterpret_cast(&RealShellExecuteW), &DetouredShellExecuteW)) + { + Log::Error(WEX::Common::String().Format(L"DetourAttach failed: %d", err)); + ::DetourTransactionAbort(); + return false; + } + ::DetourTransactionCommit(); + + // Initialize the HTTP server we use for token requests + VERIFY_WIN32_SUCCEEDED(::HttpInitialize(HTTPAPI_VERSION_2, HTTP_INITIALIZE_SERVER, nullptr)); + VERIFY_WIN32_SUCCEEDED(::HttpCreateRequestQueue(HTTPAPI_VERSION_2, nullptr, nullptr, 0, &m_requestQueue)); + VERIFY_WIN32_SUCCEEDED(::HttpCreateServerSession(HTTPAPI_VERSION_2, &m_serverSessionId, 0)); + VERIFY_WIN32_SUCCEEDED(::HttpCreateUrlGroup(m_serverSessionId, &m_urlGroup, 0)); + + HTTP_BINDING_INFO bindingInfo = {}; + bindingInfo.Flags.Present = 1; + bindingInfo.RequestQueueHandle = m_requestQueue; + VERIFY_WIN32_SUCCEEDED(::HttpSetUrlGroupProperty(m_urlGroup, HttpServerBindingProperty, &bindingInfo, + static_cast(sizeof(bindingInfo)))); + + // Find an open port; note that ports in the low 50000s are frequently claimed, hence the large iteration bounds + ULONG err = 0; + for (std::uint16_t i = 0; i < 500; ++i) + { + wchar_t buffer[18 + 5 + 1]; + std::swprintf(buffer, std::size(buffer), L"http://127.0.0.1:%d/", m_serverPort); + + err = ::HttpAddUrlToUrlGroup(m_urlGroup, buffer, 0, 0); + if (err == NO_ERROR) + { + m_serverUrlBase = buffer; + break; + } + + ++m_serverPort; + } + + VERIFY_WIN32_SUCCEEDED(err, L"Failed to find an open port"); + m_httpServerThread = std::thread([this] { + RunHttpServer(); + }); + + return true; + } + + TEST_CLASS_CLEANUP(Cleanup) + { + // Tear down the HTTP server + m_serverShutdownEvent.SetEvent(); + m_httpServerThread.join(); + + if (m_urlGroup) + { + ::HttpCloseUrlGroup(m_urlGroup); + m_urlGroup = 0; + } + + if (m_serverSessionId) + { + ::HttpCloseServerSession(m_serverSessionId); + m_serverSessionId = 0; + } + + if (m_requestQueue) + { + ::HttpCloseRequestQueue(m_requestQueue); + m_requestQueue = nullptr; + } + + ::HttpTerminate(HTTP_INITIALIZE_SERVER, nullptr); + + // Clean up our detours + ::DetourTransactionBegin(); + ::DetourUpdateThread(::GetCurrentThread()); + ::DetourDetach(reinterpret_cast(&RealShellExecuteW), &DetouredShellExecuteW); + ::DetourTransactionCommit(); + + Test::Bootstrap::Cleanup(); + + return true; + } + + template + static bool WaitWithTimeout(const IAsyncOperation& op) + { + wil::unique_event event(wil::EventOptions::None); + op.Completed([&](const IAsyncOperation&, AsyncStatus) { + event.SetEvent(); + }); + + // 10 seconds is beyond + if (::WaitForSingleObject(event.get(), 10000) == WAIT_OBJECT_0) + { + return true; + } + + op.Cancel(); + return false; + } + + static inline constexpr std::wstring_view auth_url = L"http://oauthtests.com/oauth"sv; + static inline constexpr std::wstring_view localhost_redirect_url = L"http://127.0.0.1/oauth"sv; + + TEST_METHOD(Localhost_BasicEndToEnd) + { + static constexpr std::wstring_view client_id = L"scenario=Localhost_BasicEndToEnd"sv; + + auto params = AuthRequestParams::CreateForAuthorizationCodeRequest(client_id, Uri{ localhost_redirect_url }); + auto asyncOp = AuthManager::InitiateAuthRequestAsync(Uri{ auth_url }, params); + VERIFY_IS_TRUE(WaitWithTimeout(asyncOp)); + VERIFY_ARE_EQUAL(AsyncStatus::Completed, asyncOp.Status()); + + auto result = asyncOp.GetResults(); + auto response = result.Response(); + VERIFY_IS_NOT_NULL(response); + VERIFY_IS_NULL(result.Failure()); + + VERIFY_ARE_EQUAL(params.State(), response.State()); + } + + // Detoured Functions + static HINSTANCE __stdcall DetouredShellExecuteW(_In_opt_ HWND hwnd, _In_opt_ LPCWSTR operation, _In_ LPCWSTR file, + _In_opt_ LPCWSTR params, _In_opt_ LPCWSTR directory, _In_ INT showCmd) try + { + std::wstring_view fileStr = file; + if (fileStr.substr(0, auth_url.size()) == auth_url) + { + // There's no point in launching the browser and trying to fake an authorization flow as that would do + // nothing to test the API. Instead, perform the logic of the browser and authorization flow here in-proc + winrt::hstring responseType; + winrt::hstring clientId; + Uri redirectUri{ nullptr }; + winrt::hstring scope; + winrt::hstring state; + winrt::hstring codeChallenge; + winrt::hstring codeChallengeMethod; + + winrt::hstring scenario; + + Uri uri{ fileStr }; + winrt::hstring errorString; + winrt::hstring errorMessage; + for (auto&& entry : uri.QueryParsed()) + { + auto name = entry.Name(); + auto value = entry.Value(); + if (name == strings::request::response_type) + { + responseType = value; + } + else if (name == strings::request::client_id) + { + clientId = value; + } + else if (name == strings::request::redirect_uri) + { + redirectUri = Uri{ value }; + } + else if (name == strings::request::scope) + { + scope = value; + } + else if (name == strings::request::state) + { + state = value; + } + else if (name == strings::request::code_challenge) + { + codeChallenge = value; + } + else if (name == strings::request::code_challenge_method) + { + codeChallengeMethod = value; + } + else + { + // TODO: Respond with error? We might not have the redirect URI or state yet... + errorString = strings::error::invalid_request; + errorMessage = L"Unrecognized query parameter '"s + name + L"'"; + } + } + + for (auto&& entry : WwwFormUrlDecoder{ Uri::UnescapeComponent(clientId) }) + { + auto name = entry.Name(); + auto value = entry.Value(); + if (name == strings::client_id::scenario) + { + scenario = value; + } + else + { + errorString = strings::error::invalid_request; + errorMessage = L"Unrecognized client_id parameter '"s + name + L"'"; + } + } + + if (state.empty()) + { + // If no state is provided, we'll be unable to correlate the response to the request. The best we can + // really do here is to fail the launch which will fail the test early and reliably + Log::Error(L"No 'state' value provided in the URI"); + ::SetLastError(ERROR_INVALID_PARAMETER); + return nullptr; + } + else if (responseType.empty()) + { + errorString = strings::error::invalid_request; + errorMessage = L"Missing 'response_type'"; + } + else if (clientId.empty()) + { + errorString = strings::error::invalid_request; + errorMessage = L"Missing 'client_id'"; + } + + if (!redirectUri) + { + // TODO: Some tests will want to test us not providing a URI + // If we aren't given a URI and we didn't expect to not be given a URI, then we can't reliably return + // back an error response + Log::Error(L"No 'redirect_uri' value provided in the URI"); + ::SetLastError(ERROR_INVALID_PARAMETER); + return nullptr; + } + + Uri responseUri{ nullptr }; + if (responseType == strings::response_type::code) + { + std::wstring code = L"client="; + code += Uri::EscapeComponent(clientId); + if (codeChallengeMethod.empty()) + { + code += L"&challenge_method=none"; + } + else + { + code += L"&challenge_method="; + code += codeChallengeMethod; + code += L"&challenge="; + code += codeChallenge; + } + + uri_builder builder{ redirectUri }; + builder.add(L"code", code); + builder.add(L"state", state); + responseUri = builder.get(); + } + else if (responseType == strings::response_type::token) + { + // TODO + } + else + { + errorString = strings::error::unsupported_response_type; + errorMessage = L"Unknown response type '"s + responseType + L"'"; + } + + if (!errorString.empty()) + { + // NOTE: We may have created a response URI already, in which case we want to overwrite it here + uri_builder builder(redirectUri, responseType != strings::response_type::token); + builder.add(L"state", state); + builder.add(L"error", errorString); + builder.add_optional(L"error_description", errorMessage); + // TODO: error_uri + responseUri = builder.get(); + } + + if (responseUri.SchemeName() != L"http") + { + // Protocol activation + return RealShellExecuteW(hwnd, L"open", responseUri.RawUri().c_str(), nullptr, nullptr, SW_SHOWDEFAULT); + } + + // Simulating a localhost server. This would give the response back in-proc so we can just go ahead and + // do that directly. Note that we do this in the same callstack as that will test more interesting code + // paths + if (!AuthManager::CompleteAuthRequest(responseUri)) + { + Log::Warning(L"Failed to complete auth request"); + } + + return reinterpret_cast(42); // Value doesn't really matter; must be greater than 32 + } + + // Not intercepting. Let this "fall through" to the implementation + return RealShellExecuteW(hwnd, operation, file, params, directory, showCmd); + } + catch (...) + { + ::SetLastError(ERROR_FILE_NOT_FOUND); + return reinterpret_cast(ERROR_FILE_NOT_FOUND); + } + + // HTTP Server Thread Callback + void RunHttpServer() + { + wil::unique_event event{ wil::EventOptions::None }; + OVERLAPPED overlapped = {}; + overlapped.hEvent = event.get(); + + ULONG bufferSize = 0x1000; // 4 KB + auto buffer = std::make_unique(bufferSize); + auto request = reinterpret_cast(buffer.get()); + while (true) + { + auto err = ::HttpReceiveHttpRequest(m_requestQueue, HTTP_NULL_ID, HTTP_RECEIVE_REQUEST_FLAG_COPY_BODY, + request, bufferSize, nullptr, &overlapped); + if (err == ERROR_IO_PENDING) + { + // Wait for either shutdown or a request to come in + HANDLE handles[] = { event.get(), m_serverShutdownEvent.get() }; + auto waitResult = ::WaitForMultipleObjects(2, handles, false, INFINITE); + if (waitResult == (WAIT_OBJECT_0 + 1)) + { + // Shutdown + ::CancelIo(m_requestQueue); + break; + } + else if (waitResult != WAIT_OBJECT_0) + { + Log::Warning(WEX::Common::String().Format( + L"WaitForMultipleObjects failed in the HTTP server thread: %d", ::GetLastError())); + ::CancelIo(m_requestQueue); + break; + } + } + + // We have a request; we'll block here until we have all data, if needed + DWORD bytes; + ::GetOverlappedResult(m_requestQueue, &overlapped, &bytes, false); + err = ::GetLastError(); + if (err == ERROR_MORE_DATA) + { + bufferSize = bytes; + buffer = std::make_unique(bufferSize); + request = reinterpret_cast(buffer.get()); + err = ::HttpReceiveHttpRequest(m_requestQueue, request->RequestId, HTTP_RECEIVE_REQUEST_FLAG_COPY_BODY, + request, bufferSize, &bytes, nullptr); + } + + if (err == ERROR_CONNECTION_INVALID) + { + // Connection corrupted by peer + continue; + } + else if (err != ERROR_SUCCESS) + { + Log::Warning(WEX::Common::String().Format(L"HttpReceiveHttpRequest failed: %d", err)); + break; + } + + switch (request->Verb) + { + case HttpVerbGET: + HandleGetRequest(request); + break; + + case HttpVerbPOST: + HandlePostRequest(request); + break; + + default: + Log::Warning(L"Received an HTTP request with an unexpected verb"); + break; + } + } + } + + void HandleGetRequest(HTTP_REQUEST* request) + { + (void)request; // TODO + } + + void HandlePostRequest(HTTP_REQUEST* request) + { + WINRT_ASSERT(request->EntityChunkCount == 1); + WINRT_ASSERT(request->pEntityChunks->DataChunkType == HttpDataChunkFromMemory); + auto& data = request->pEntityChunks->FromMemory; + std::string_view body{ static_cast(data.pBuffer), data.BufferLength }; + + for (auto&& entry : WwwFormUrlDecoder(winrt::to_hstring(body))) + { + (void)entry; // TODO + } + } + + // Detours Information + static inline decltype(&::ShellExecuteW) RealShellExecuteW = &::ShellExecuteW; + + // Local server for performing the token exchange + wil::unique_event m_serverShutdownEvent{ wil::EventOptions::None }; + std::thread m_httpServerThread; + HANDLE m_requestQueue = nullptr; + HTTP_SERVER_SESSION_ID m_serverSessionId = 0; + HTTP_URL_GROUP_ID m_urlGroup = 0; + std::uint16_t m_serverPort = 50001; + std::wstring m_serverUrlBase; +}; diff --git a/test/OAuth/OAuthTests/OAuthTests.vcxproj b/test/OAuth/OAuthTests/OAuthTests.vcxproj new file mode 100644 index 0000000000..5fddba07b3 --- /dev/null +++ b/test/OAuth/OAuthTests/OAuthTests.vcxproj @@ -0,0 +1,148 @@ + + + + + + Debug + ARM64 + + + Debug + Win32 + + + Release + ARM64 + + + Release + Win32 + + + Debug + x64 + + + Release + x64 + + + + 16.0 + Win32Proj + {21651459-648e-475c-91db-2bde359c75a4} + OAuthTests + 10.0 + + + + DynamicLibrary + v143 + Unicode + + + true + + + false + true + + + + + + + + + + + + + + + Level4 + true + true + NOMINMAX;WIN32_LEAN_AND_MEAN;OAUTHTESTS_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) + true + + ..\..\..\dev\Detours; + $(OutDir)\..\WindowsAppRuntime_DLL; + $(OutDir)\..\WindowsAppRuntime_BootstrapDLL; + %(AdditionalIncludeDirectories) + + + + Windows + true + false + httpapi.lib;%(AdditionalDependencies) + Microsoft.WindowsAppRuntime.dll;%(DelayLoadDLLs) + + + + + _DEBUG;%(PreprocessorDefinitions) + + + + + true + true + NDEBUG;%(PreprocessorDefinitions) + + + true + true + + + + + WIN32;%(PreprocessorDefinitions) + + + + + WIN32;%(PreprocessorDefinitions) + + + + + + + + + + + {d6bc25c5-1aa7-4c4a-a02c-b42dedbfea33} + + + {f76b776e-86f5-48c5-8fc7-d2795ecc9746} + + + + + $(OutDir)\..\WindowsAppRuntime_DLL\Microsoft.Windows.Security.Authentication.OAuth.winmd + true + $(OutDir)\..\WindowsAppRuntime_DLL\Microsoft.WindowsAppRuntime.dll + + + + + + + + + + + This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. + + + + + + + + + + \ No newline at end of file diff --git a/test/OAuth/OAuthTests/OAuthTests.vcxproj.filters b/test/OAuth/OAuthTests/OAuthTests.vcxproj.filters new file mode 100644 index 0000000000..675aba6659 --- /dev/null +++ b/test/OAuth/OAuthTests/OAuthTests.vcxproj.filters @@ -0,0 +1,25 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + + + Source Files + + + + + + \ No newline at end of file diff --git a/test/OAuth/OAuthTests/packages.config b/test/OAuth/OAuthTests/packages.config new file mode 100644 index 0000000000..a2dc8394c3 --- /dev/null +++ b/test/OAuth/OAuthTests/packages.config @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file From 4ae3c200e1ce4832c4d0e3bbdc49fa0e8f377ad1 Mon Sep 17 00:00:00 2001 From: Duncan Horn Date: Mon, 14 Nov 2022 12:13:17 -0800 Subject: [PATCH 4/9] Changes I forgot to commit --- WindowsAppRuntime.sln | 137 +- dev/OAuth/AuthFailure.cpp | 50 +- dev/OAuth/AuthManager.cpp | 66 +- dev/OAuth/AuthRequestAsyncOperation.cpp | 180 +- dev/OAuth/AuthRequestAsyncOperation.h | 8 +- dev/OAuth/AuthRequestParams.cpp | 6 +- dev/OAuth/AuthRequestParams.h | 2 +- dev/OAuth/AuthRequestResult.cpp | 30 +- dev/OAuth/AuthResponse.cpp | 67 +- dev/OAuth/Crypto.h | 19 +- dev/OAuth/TokenRequestParams.cpp | 7 +- dev/OAuth/common.h | 13 + .../OAuthTestCommon/OAuthTestCommon.vcxitems | 19 - test/OAuth/OAuthTestCommon/OAuthTestValues.h | 107 -- test/OAuth/OAuthTests/OAuthTests.cpp | 462 ----- test/OAuthTests/OAuthTestValues.h | 114 ++ test/OAuthTests/OAuthTests.cpp | 1484 +++++++++++++++++ test/OAuthTests/OAuthTests.vcxproj | 157 ++ .../OAuthTests/OAuthTests.vcxproj.filters | 0 test/{OAuth => }/OAuthTests/packages.config | 0 .../OAuthTestApp/OAuthTestApp.vcxproj} | 44 +- .../OAuthTestApp/OAuthTestApp.vcxproj.filters | 22 + test/TestApps/OAuthTestApp/main.cpp | 30 + test/TestApps/OAuthTestApp/packages.config | 4 + .../Images/LockScreenLogo.scale-200.png | Bin 0 -> 1430 bytes .../Images/SplashScreen.scale-200.png | Bin 0 -> 7700 bytes .../Images/Square150x150Logo.scale-200.png | Bin 0 -> 2937 bytes .../Images/Square44x44Logo.scale-200.png | Bin 0 -> 1647 bytes ...x44Logo.targetsize-24_altform-unplated.png | Bin 0 -> 1255 bytes .../OAuthTestAppPackage/Images/StoreLogo.png | Bin 0 -> 1451 bytes .../Images/Wide310x150Logo.scale-200.png | Bin 0 -> 3204 bytes .../OAuthTestAppPackage.wapproj | 72 + .../OAuthTestAppPackage/Package.appxmanifest | 58 + 33 files changed, 2348 insertions(+), 810 deletions(-) delete mode 100644 test/OAuth/OAuthTestCommon/OAuthTestCommon.vcxitems delete mode 100644 test/OAuth/OAuthTestCommon/OAuthTestValues.h delete mode 100644 test/OAuth/OAuthTests/OAuthTests.cpp create mode 100644 test/OAuthTests/OAuthTestValues.h create mode 100644 test/OAuthTests/OAuthTests.cpp create mode 100644 test/OAuthTests/OAuthTests.vcxproj rename test/{OAuth => }/OAuthTests/OAuthTests.vcxproj.filters (100%) rename test/{OAuth => }/OAuthTests/packages.config (100%) rename test/{OAuth/OAuthTests/OAuthTests.vcxproj => TestApps/OAuthTestApp/OAuthTestApp.vcxproj} (70%) create mode 100644 test/TestApps/OAuthTestApp/OAuthTestApp.vcxproj.filters create mode 100644 test/TestApps/OAuthTestApp/main.cpp create mode 100644 test/TestApps/OAuthTestApp/packages.config create mode 100644 test/TestApps/OAuthTestAppPackage/Images/LockScreenLogo.scale-200.png create mode 100644 test/TestApps/OAuthTestAppPackage/Images/SplashScreen.scale-200.png create mode 100644 test/TestApps/OAuthTestAppPackage/Images/Square150x150Logo.scale-200.png create mode 100644 test/TestApps/OAuthTestAppPackage/Images/Square44x44Logo.scale-200.png create mode 100644 test/TestApps/OAuthTestAppPackage/Images/Square44x44Logo.targetsize-24_altform-unplated.png create mode 100644 test/TestApps/OAuthTestAppPackage/Images/StoreLogo.png create mode 100644 test/TestApps/OAuthTestAppPackage/Images/Wide310x150Logo.scale-200.png create mode 100644 test/TestApps/OAuthTestAppPackage/OAuthTestAppPackage.wapproj create mode 100644 test/TestApps/OAuthTestAppPackage/Package.appxmanifest diff --git a/WindowsAppRuntime.sln b/WindowsAppRuntime.sln index 5583acec69..7d4e67f9e3 100644 --- a/WindowsAppRuntime.sln +++ b/WindowsAppRuntime.sln @@ -26,8 +26,8 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "inc", "test\inc\inc.vcxitem EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "AppLifecycleTestApp", "test\TestApps\AppLifecycleTestApp\AppLifecycleTestApp.vcxproj", "{7C502995-59C3-483B-86BA-815985353633}" ProjectSection(ProjectDependencies) = postProject - {B73AD907-6164-4294-88FB-F3C9C10DA1F1} = {B73AD907-6164-4294-88FB-F3C9C10DA1F1} {9C1A6C58-52D6-4514-9120-5C339C5DF4BE} = {9C1A6C58-52D6-4514-9120-5C339C5DF4BE} + {B73AD907-6164-4294-88FB-F3C9C10DA1F1} = {B73AD907-6164-4294-88FB-F3C9C10DA1F1} {F76B776E-86F5-48C5-8FC7-D2795ECC9746} = {F76B776E-86F5-48C5-8FC7-D2795ECC9746} EndProjectSection EndProject @@ -45,10 +45,10 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "DynamicDependency", "Dynami EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "DynamicDependency_Test_Win32", "test\DynamicDependency\Test_Win32\DynamicDependency_Test_Win32.vcxproj", "{6BAC3B29-3A6B-4B3F-A9AB-A316AA4E48ED}" ProjectSection(ProjectDependencies) = postProject - {B73AD907-6164-4294-88FB-F3C9C10DA1F1} = {B73AD907-6164-4294-88FB-F3C9C10DA1F1} {58E95711-A12F-4C0E-A978-C6B4A0842AC8} = {58E95711-A12F-4C0E-A978-C6B4A0842AC8} - {F76B776E-86F5-48C5-8FC7-D2795ECC9746} = {F76B776E-86F5-48C5-8FC7-D2795ECC9746} {66D0D8B1-FAF4-4C6A-8303-07F3BA356FE3} = {66D0D8B1-FAF4-4C6A-8303-07F3BA356FE3} + {B73AD907-6164-4294-88FB-F3C9C10DA1F1} = {B73AD907-6164-4294-88FB-F3C9C10DA1F1} + {F76B776E-86F5-48C5-8FC7-D2795ECC9746} = {F76B776E-86F5-48C5-8FC7-D2795ECC9746} EndProjectSection EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "WindowsAppRuntime_BootstrapDLL", "dev\WindowsAppRuntime_BootstrapDLL\WindowsAppRuntime_BootstrapDLL.vcxproj", "{F76B776E-86F5-48C5-8FC7-D2795ECC9746}" @@ -66,8 +66,8 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Framework.Math.Multiply", " EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Microsoft.WindowsAppRuntime.Framework", "test\DynamicDependency\data\Microsoft.WindowsAppRuntime.Framework\Microsoft.WindowsAppRuntime.Framework.vcxproj", "{9C1A6C58-52D6-4514-9120-5C339C5DF4BE}" ProjectSection(ProjectDependencies) = postProject - {BC5E5A3E-E733-4388-8B00-F8495DA7C778} = {BC5E5A3E-E733-4388-8B00-F8495DA7C778} {4410D374-A90C-4ADF-8B15-AA2AAE2636BF} = {4410D374-A90C-4ADF-8B15-AA2AAE2636BF} + {BC5E5A3E-E733-4388-8B00-F8495DA7C778} = {BC5E5A3E-E733-4388-8B00-F8495DA7C778} EndProjectSection EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "DynamicDependencyDataStore", "DynamicDependencyDataStore", "{441A3BB0-7FD2-4902-AEDB-A1C57B528C77}" @@ -93,10 +93,10 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "IDynamicDependencyLifetimeM EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "DynamicDependency_Test_WinRT", "test\DynamicDependency\Test_WinRT\DynamicDependency_Test_WinRT.vcxproj", "{B2546322-D329-4F6C-9C2E-7EFC3C9ED214}" ProjectSection(ProjectDependencies) = postProject - {B73AD907-6164-4294-88FB-F3C9C10DA1F1} = {B73AD907-6164-4294-88FB-F3C9C10DA1F1} {58E95711-A12F-4C0E-A978-C6B4A0842AC8} = {58E95711-A12F-4C0E-A978-C6B4A0842AC8} - {F76B776E-86F5-48C5-8FC7-D2795ECC9746} = {F76B776E-86F5-48C5-8FC7-D2795ECC9746} {66D0D8B1-FAF4-4C6A-8303-07F3BA356FE3} = {66D0D8B1-FAF4-4C6A-8303-07F3BA356FE3} + {B73AD907-6164-4294-88FB-F3C9C10DA1F1} = {B73AD907-6164-4294-88FB-F3C9C10DA1F1} + {F76B776E-86F5-48C5-8FC7-D2795ECC9746} = {F76B776E-86F5-48C5-8FC7-D2795ECC9746} EndProjectSection EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "UndockedRegFreeWinRT", "UndockedRegFreeWinRT", "{323E29A9-873F-419B-919E-D18BCE1DE120}" @@ -105,11 +105,11 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "UndockedRegFreeWinRT", "dev EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "AppLifecycleTests", "test\AppLifecycle\AppLifecycle.vcxproj", "{C62688A1-16A0-4729-B6ED-842F4FAA29F3}" ProjectSection(ProjectDependencies) = postProject - {B73AD907-6164-4294-88FB-F3C9C10DA1F1} = {B73AD907-6164-4294-88FB-F3C9C10DA1F1} + {9C1A6C58-52D6-4514-9120-5C339C5DF4BE} = {9C1A6C58-52D6-4514-9120-5C339C5DF4BE} {A3FBA80D-5B35-471F-9A45-DB4B29E195B9} = {A3FBA80D-5B35-471F-9A45-DB4B29E195B9} {A7391725-4EF5-438F-8DE1-645423E46955} = {A7391725-4EF5-438F-8DE1-645423E46955} - {9C1A6C58-52D6-4514-9120-5C339C5DF4BE} = {9C1A6C58-52D6-4514-9120-5C339C5DF4BE} {B71E818A-882E-456A-87E5-4DE4A6602B99} = {B71E818A-882E-456A-87E5-4DE4A6602B99} + {B73AD907-6164-4294-88FB-F3C9C10DA1F1} = {B73AD907-6164-4294-88FB-F3C9C10DA1F1} EndProjectSection EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "DynamicDependencyLifetimeManagerGC1000.Msix", "test\DynamicDependency\data\DynamicDependencyLifetimeManagerGC1000.Msix\DynamicDependencyLifetimeManagerGC1000.Msix.vcxproj", "{5D97F9A6-AFAD-4B0C-B609-E62DDDE6E4FA}" @@ -118,14 +118,14 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "DynamicDependencyLifetimeMa EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "ManualTestApp", "test\TestApps\ManualTestApp\ManualTestApp.vcxproj", "{8E52D7EA-A200-4A6B-BA74-8EFB49468CAF}" ProjectSection(ProjectDependencies) = postProject - {B73AD907-6164-4294-88FB-F3C9C10DA1F1} = {B73AD907-6164-4294-88FB-F3C9C10DA1F1} - {A7391725-4EF5-438F-8DE1-645423E46955} = {A7391725-4EF5-438F-8DE1-645423E46955} - {9C1A6C58-52D6-4514-9120-5C339C5DF4BE} = {9C1A6C58-52D6-4514-9120-5C339C5DF4BE} + {2E5BF0D2-78FA-4B60-A341-F65A0D58BD86} = {2E5BF0D2-78FA-4B60-A341-F65A0D58BD86} + {51293DFA-614B-4355-A60D-B30DF3228FD1} = {51293DFA-614B-4355-A60D-B30DF3228FD1} {8C79C46D-1577-44CA-83DF-88B74C3E4586} = {8C79C46D-1577-44CA-83DF-88B74C3E4586} + {9C1A6C58-52D6-4514-9120-5C339C5DF4BE} = {9C1A6C58-52D6-4514-9120-5C339C5DF4BE} + {A7391725-4EF5-438F-8DE1-645423E46955} = {A7391725-4EF5-438F-8DE1-645423E46955} {B71E818A-882E-456A-87E5-4DE4A6602B99} = {B71E818A-882E-456A-87E5-4DE4A6602B99} + {B73AD907-6164-4294-88FB-F3C9C10DA1F1} = {B73AD907-6164-4294-88FB-F3C9C10DA1F1} {D6BC25C5-1AA7-4C4A-A02C-B42DEDBFEA33} = {D6BC25C5-1AA7-4C4A-A02C-B42DEDBFEA33} - {2E5BF0D2-78FA-4B60-A341-F65A0D58BD86} = {2E5BF0D2-78FA-4B60-A341-F65A0D58BD86} - {51293DFA-614B-4355-A60D-B30DF3228FD1} = {51293DFA-614B-4355-A60D-B30DF3228FD1} EndProjectSection EndProject Project("{C7167F0D-BC9F-4E6E-AFE1-012C56B48DB5}") = "AppLifecycleTestPackage", "test\TestApps\AppLifecycleTestPackage\AppLifecycleTestPackage.wapproj", "{A3FBA80D-5B35-471F-9A45-DB4B29E195B9}" @@ -136,37 +136,37 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "PushNotifications", "dev\Pu EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "PushNotificationsDemoApp", "test\TestApps\PushNotificationsDemoApp\PushNotificationsDemoApp.vcxproj", "{5B2D17FE-C371-417F-860C-3D32397C2404}" ProjectSection(ProjectDependencies) = postProject - {B73AD907-6164-4294-88FB-F3C9C10DA1F1} = {B73AD907-6164-4294-88FB-F3C9C10DA1F1} - {A7391725-4EF5-438F-8DE1-645423E46955} = {A7391725-4EF5-438F-8DE1-645423E46955} {9C1A6C58-52D6-4514-9120-5C339C5DF4BE} = {9C1A6C58-52D6-4514-9120-5C339C5DF4BE} - {F76B776E-86F5-48C5-8FC7-D2795ECC9746} = {F76B776E-86F5-48C5-8FC7-D2795ECC9746} + {A7391725-4EF5-438F-8DE1-645423E46955} = {A7391725-4EF5-438F-8DE1-645423E46955} {B71E818A-882E-456A-87E5-4DE4A6602B99} = {B71E818A-882E-456A-87E5-4DE4A6602B99} + {B73AD907-6164-4294-88FB-F3C9C10DA1F1} = {B73AD907-6164-4294-88FB-F3C9C10DA1F1} + {F76B776E-86F5-48C5-8FC7-D2795ECC9746} = {F76B776E-86F5-48C5-8FC7-D2795ECC9746} EndProjectSection EndProject Project("{C7167F0D-BC9F-4E6E-AFE1-012C56B48DB5}") = "PushNotificationsDemoPackage", "test\TestApps\PushNotificationsDemoPackage\PushNotificationsDemoPackage.wapproj", "{424A6D96-37EE-4456-8347-08AB425C8DBE}" EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "PushNotificationsTestApp", "test\TestApps\PushNotificationsTestApp\PushNotificationsTestApp.vcxproj", "{56A1D696-FEDA-4333-BF37-772EBECECB10}" ProjectSection(ProjectDependencies) = postProject - {B73AD907-6164-4294-88FB-F3C9C10DA1F1} = {B73AD907-6164-4294-88FB-F3C9C10DA1F1} - {A7391725-4EF5-438F-8DE1-645423E46955} = {A7391725-4EF5-438F-8DE1-645423E46955} {9C1A6C58-52D6-4514-9120-5C339C5DF4BE} = {9C1A6C58-52D6-4514-9120-5C339C5DF4BE} - {F76B776E-86F5-48C5-8FC7-D2795ECC9746} = {F76B776E-86F5-48C5-8FC7-D2795ECC9746} + {A7391725-4EF5-438F-8DE1-645423E46955} = {A7391725-4EF5-438F-8DE1-645423E46955} {B71E818A-882E-456A-87E5-4DE4A6602B99} = {B71E818A-882E-456A-87E5-4DE4A6602B99} + {B73AD907-6164-4294-88FB-F3C9C10DA1F1} = {B73AD907-6164-4294-88FB-F3C9C10DA1F1} + {F76B776E-86F5-48C5-8FC7-D2795ECC9746} = {F76B776E-86F5-48C5-8FC7-D2795ECC9746} EndProjectSection EndProject Project("{C7167F0D-BC9F-4E6E-AFE1-012C56B48DB5}") = "PushNotificationsTestAppPackage", "test\TestApps\PushNotificationsTestAppPackage\PushNotificationsTestAppPackage.wapproj", "{D012E4BB-F16B-472D-A26D-D449CEFA988E}" EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "PushNotificationTests", "test\PushNotificationTests\PushNotificationTests.vcxproj", "{0A5FEE93-48B7-40EC-BB9A-B27D11060DA9}" ProjectSection(ProjectDependencies) = postProject - {B73AD907-6164-4294-88FB-F3C9C10DA1F1} = {B73AD907-6164-4294-88FB-F3C9C10DA1F1} - {A7391725-4EF5-438F-8DE1-645423E46955} = {A7391725-4EF5-438F-8DE1-645423E46955} - {9C1A6C58-52D6-4514-9120-5C339C5DF4BE} = {9C1A6C58-52D6-4514-9120-5C339C5DF4BE} - {F76B776E-86F5-48C5-8FC7-D2795ECC9746} = {F76B776E-86F5-48C5-8FC7-D2795ECC9746} - {B71E818A-882E-456A-87E5-4DE4A6602B99} = {B71E818A-882E-456A-87E5-4DE4A6602B99} {424A6D96-37EE-4456-8347-08AB425C8DBE} = {424A6D96-37EE-4456-8347-08AB425C8DBE} {56A1D696-FEDA-4333-BF37-772EBECECB10} = {56A1D696-FEDA-4333-BF37-772EBECECB10} - {D012E4BB-F16B-472D-A26D-D449CEFA988E} = {D012E4BB-F16B-472D-A26D-D449CEFA988E} {5B2D17FE-C371-417F-860C-3D32397C2404} = {5B2D17FE-C371-417F-860C-3D32397C2404} + {9C1A6C58-52D6-4514-9120-5C339C5DF4BE} = {9C1A6C58-52D6-4514-9120-5C339C5DF4BE} + {A7391725-4EF5-438F-8DE1-645423E46955} = {A7391725-4EF5-438F-8DE1-645423E46955} + {B71E818A-882E-456A-87E5-4DE4A6602B99} = {B71E818A-882E-456A-87E5-4DE4A6602B99} + {B73AD907-6164-4294-88FB-F3C9C10DA1F1} = {B73AD907-6164-4294-88FB-F3C9C10DA1F1} + {D012E4BB-F16B-472D-A26D-D449CEFA988E} = {D012E4BB-F16B-472D-A26D-D449CEFA988E} + {F76B776E-86F5-48C5-8FC7-D2795ECC9746} = {F76B776E-86F5-48C5-8FC7-D2795ECC9746} EndProjectSection EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "PushNotificationsLongRunningTask", "dev\PushNotifications\PushNotificationsLongRunningTask\PushNotificationsLongRunningTask.vcxproj", "{1307DD1B-BBE8-4CD0-B1A0-0DB6D61EEAA0}" @@ -248,23 +248,23 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "DynamicDependencyLifetimeMa EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "ToastNotificationTests", "test\ToastNotificationTests\ToastNotificationTests.vcxproj", "{E977B1BD-00DC-4085-A105-E0A18E0183D7}" ProjectSection(ProjectDependencies) = postProject - {B73AD907-6164-4294-88FB-F3C9C10DA1F1} = {B73AD907-6164-4294-88FB-F3C9C10DA1F1} - {A7391725-4EF5-438F-8DE1-645423E46955} = {A7391725-4EF5-438F-8DE1-645423E46955} - {D6A64926-4988-4C64-A5A8-2C14B1388696} = {D6A64926-4988-4C64-A5A8-2C14B1388696} - {9C1A6C58-52D6-4514-9120-5C339C5DF4BE} = {9C1A6C58-52D6-4514-9120-5C339C5DF4BE} - {F76B776E-86F5-48C5-8FC7-D2795ECC9746} = {F76B776E-86F5-48C5-8FC7-D2795ECC9746} {4B30C685-8490-440F-9879-A75D45DAA361} = {4B30C685-8490-440F-9879-A75D45DAA361} + {9C1A6C58-52D6-4514-9120-5C339C5DF4BE} = {9C1A6C58-52D6-4514-9120-5C339C5DF4BE} + {A7391725-4EF5-438F-8DE1-645423E46955} = {A7391725-4EF5-438F-8DE1-645423E46955} {B71E818A-882E-456A-87E5-4DE4A6602B99} = {B71E818A-882E-456A-87E5-4DE4A6602B99} + {B73AD907-6164-4294-88FB-F3C9C10DA1F1} = {B73AD907-6164-4294-88FB-F3C9C10DA1F1} {C422B090-F501-4204-8068-1084B0799405} = {C422B090-F501-4204-8068-1084B0799405} + {D6A64926-4988-4C64-A5A8-2C14B1388696} = {D6A64926-4988-4C64-A5A8-2C14B1388696} + {F76B776E-86F5-48C5-8FC7-D2795ECC9746} = {F76B776E-86F5-48C5-8FC7-D2795ECC9746} EndProjectSection EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "ToastNotificationsTestApp", "test\TestApps\ToastNotificationsTestApp\ToastNotificationsTestApp.vcxproj", "{4B30C685-8490-440F-9879-A75D45DAA361}" ProjectSection(ProjectDependencies) = postProject - {B73AD907-6164-4294-88FB-F3C9C10DA1F1} = {B73AD907-6164-4294-88FB-F3C9C10DA1F1} - {A7391725-4EF5-438F-8DE1-645423E46955} = {A7391725-4EF5-438F-8DE1-645423E46955} {9C1A6C58-52D6-4514-9120-5C339C5DF4BE} = {9C1A6C58-52D6-4514-9120-5C339C5DF4BE} - {F76B776E-86F5-48C5-8FC7-D2795ECC9746} = {F76B776E-86F5-48C5-8FC7-D2795ECC9746} + {A7391725-4EF5-438F-8DE1-645423E46955} = {A7391725-4EF5-438F-8DE1-645423E46955} {B71E818A-882E-456A-87E5-4DE4A6602B99} = {B71E818A-882E-456A-87E5-4DE4A6602B99} + {B73AD907-6164-4294-88FB-F3C9C10DA1F1} = {B73AD907-6164-4294-88FB-F3C9C10DA1F1} + {F76B776E-86F5-48C5-8FC7-D2795ECC9746} = {F76B776E-86F5-48C5-8FC7-D2795ECC9746} EndProjectSection EndProject Project("{C7167F0D-BC9F-4E6E-AFE1-012C56B48DB5}") = "ToastNotificationsTestAppPackage", "test\TestApps\ToastNotificationsTestAppPackage\ToastNotificationsTestAppPackage.wapproj", "{D6A64926-4988-4C64-A5A8-2C14B1388696}" @@ -285,9 +285,9 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "AccessControl", "dev\Access EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "AccessControlTests", "test\AccessControlTests\AccessControlTests.vcxproj", "{08BC78E0-63C6-49A7-81B3-6AFC3DEAC4DE}" ProjectSection(ProjectDependencies) = postProject - {B73AD907-6164-4294-88FB-F3C9C10DA1F1} = {B73AD907-6164-4294-88FB-F3C9C10DA1F1} - {9C1A6C58-52D6-4514-9120-5C339C5DF4BE} = {9C1A6C58-52D6-4514-9120-5C339C5DF4BE} {03EBF097-66C6-4996-95A3-28F6F5999E27} = {03EBF097-66C6-4996-95A3-28F6F5999E27} + {9C1A6C58-52D6-4514-9120-5C339C5DF4BE} = {9C1A6C58-52D6-4514-9120-5C339C5DF4BE} + {B73AD907-6164-4294-88FB-F3C9C10DA1F1} = {B73AD907-6164-4294-88FB-F3C9C10DA1F1} {D5667DF6-A151-4081-ABC7-B93E8E5604CE} = {D5667DF6-A151-4081-ABC7-B93E8E5604CE} EndProjectSection EndProject @@ -297,10 +297,10 @@ Project("{C7167F0D-BC9F-4E6E-AFE1-012C56B48DB5}") = "AccessControlTestAppPackage EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "ToastNotificationsDemoApp", "test\TestApps\ToastNotificationsDemoApp\ToastNotificationsDemoApp.vcxproj", "{412D023E-8635-4AD2-A0EA-E19E08D36915}" ProjectSection(ProjectDependencies) = postProject - {B73AD907-6164-4294-88FB-F3C9C10DA1F1} = {B73AD907-6164-4294-88FB-F3C9C10DA1F1} - {A7391725-4EF5-438F-8DE1-645423E46955} = {A7391725-4EF5-438F-8DE1-645423E46955} {9C1A6C58-52D6-4514-9120-5C339C5DF4BE} = {9C1A6C58-52D6-4514-9120-5C339C5DF4BE} + {A7391725-4EF5-438F-8DE1-645423E46955} = {A7391725-4EF5-438F-8DE1-645423E46955} {B71E818A-882E-456A-87E5-4DE4A6602B99} = {B71E818A-882E-456A-87E5-4DE4A6602B99} + {B73AD907-6164-4294-88FB-F3C9C10DA1F1} = {B73AD907-6164-4294-88FB-F3C9C10DA1F1} {C422B090-F501-4204-8068-1084B0799405} = {C422B090-F501-4204-8068-1084B0799405} EndProjectSection EndProject @@ -387,8 +387,8 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Test_DeploymentManagerAutoI EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Test_DeploymentManagerAutoInitialize_CS_Default", "test\Deployment\Test_DeploymentManagerAutoInitialize\CS\Test_DeploymentManagerAutoInitialize_CS_Default\Test_DeploymentManagerAutoInitialize_CS_Default.csproj", "{533DD2EE-7133-45B1-959C-306F80D50781}" ProjectSection(ProjectDependencies) = postProject - {B73AD907-6164-4294-88FB-F3C9C10DA1F1} = {B73AD907-6164-4294-88FB-F3C9C10DA1F1} {4782BB2A-2968-44B4-867C-5FAEB7A51C6B} = {4782BB2A-2968-44B4-867C-5FAEB7A51C6B} + {B73AD907-6164-4294-88FB-F3C9C10DA1F1} = {B73AD907-6164-4294-88FB-F3C9C10DA1F1} EndProjectSection EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Test_DeploymentManagerAutoInitialize_CS_Options_Default", "test\Deployment\Test_DeploymentManagerAutoInitialize\CS\Test_DeploymentManagerAutoInitialize_CS_Options_Default\Test_DeploymentManagerAutoInitialize_CS_Options_Default.csproj", "{608A8D5A-A839-45F6-98E6-766FC096104A}" @@ -418,17 +418,20 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "VersionInfo", "dev\VersionI EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "VersionInfoTests", "test\VersionInfo\VersionInfoTests.vcxproj", "{442FB943-1197-48FE-B3B6-8C1BCA1E81E4}" ProjectSection(ProjectDependencies) = postProject - {34519337-9249-451E-B5A9-1ECACF9C3DA8} = {34519337-9249-451E-B5A9-1ECACF9C3DA8} - {9C1A6C58-52D6-4514-9120-5C339C5DF4BE} = {9C1A6C58-52D6-4514-9120-5C339C5DF4BE} {0B01DB78-F115-4C90-B28F-7819071303C6} = {0B01DB78-F115-4C90-B28F-7819071303C6} + {34519337-9249-451E-B5A9-1ECACF9C3DA8} = {34519337-9249-451E-B5A9-1ECACF9C3DA8} {5E2CC9D5-7C05-41D9-9DB5-EC5DF64BA1DC} = {5E2CC9D5-7C05-41D9-9DB5-EC5DF64BA1DC} + {9C1A6C58-52D6-4514-9120-5C339C5DF4BE} = {9C1A6C58-52D6-4514-9120-5C339C5DF4BE} EndProjectSection EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "OAuth", "OAuth", "{95AAEE9C-7D53-4B7B-B2C9-DB388BEF6A14}" +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "OAuthTests", "test\OAuthTests\OAuthTests.vcxproj", "{21651459-648E-475C-91DB-2BDE359C75A4}" + ProjectSection(ProjectDependencies) = postProject + {C4454D2C-8024-41B8-BAC1-FC2E544C810F} = {C4454D2C-8024-41B8-BAC1-FC2E544C810F} + EndProjectSection EndProject -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "OAuthTestCommon", "test\OAuth\OAuthTestCommon\OAuthTestCommon.vcxitems", "{5CFEB8C6-9242-40AC-9CF9-2F5C18001E3C}" +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "OAuthTestApp", "test\TestApps\OAuthTestApp\OAuthTestApp.vcxproj", "{077BDBFD-C1AA-49C8-BD62-7C14221C45F2}" EndProject -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "OAuthTests", "test\OAuth\OAuthTests\OAuthTests.vcxproj", "{21651459-648E-475C-91DB-2BDE359C75A4}" +Project("{C7167F0D-BC9F-4E6E-AFE1-012C56B48DB5}") = "OAuthTestAppPackage", "test\TestApps\OAuthTestAppPackage\OAuthTestAppPackage.wapproj", "{C4454D2C-8024-41B8-BAC1-FC2E544C810F}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -1608,6 +1611,46 @@ Global {21651459-648E-475C-91DB-2BDE359C75A4}.Release|x64.Build.0 = Release|x64 {21651459-648E-475C-91DB-2BDE359C75A4}.Release|x86.ActiveCfg = Release|Win32 {21651459-648E-475C-91DB-2BDE359C75A4}.Release|x86.Build.0 = Release|Win32 + {077BDBFD-C1AA-49C8-BD62-7C14221C45F2}.Debug|Any CPU.ActiveCfg = Debug|x64 + {077BDBFD-C1AA-49C8-BD62-7C14221C45F2}.Debug|Any CPU.Build.0 = Debug|x64 + {077BDBFD-C1AA-49C8-BD62-7C14221C45F2}.Debug|ARM64.ActiveCfg = Debug|x64 + {077BDBFD-C1AA-49C8-BD62-7C14221C45F2}.Debug|ARM64.Build.0 = Debug|x64 + {077BDBFD-C1AA-49C8-BD62-7C14221C45F2}.Debug|x64.ActiveCfg = Debug|x64 + {077BDBFD-C1AA-49C8-BD62-7C14221C45F2}.Debug|x64.Build.0 = Debug|x64 + {077BDBFD-C1AA-49C8-BD62-7C14221C45F2}.Debug|x86.ActiveCfg = Debug|Win32 + {077BDBFD-C1AA-49C8-BD62-7C14221C45F2}.Debug|x86.Build.0 = Debug|Win32 + {077BDBFD-C1AA-49C8-BD62-7C14221C45F2}.Release|Any CPU.ActiveCfg = Release|x64 + {077BDBFD-C1AA-49C8-BD62-7C14221C45F2}.Release|Any CPU.Build.0 = Release|x64 + {077BDBFD-C1AA-49C8-BD62-7C14221C45F2}.Release|ARM64.ActiveCfg = Release|x64 + {077BDBFD-C1AA-49C8-BD62-7C14221C45F2}.Release|ARM64.Build.0 = Release|x64 + {077BDBFD-C1AA-49C8-BD62-7C14221C45F2}.Release|x64.ActiveCfg = Release|x64 + {077BDBFD-C1AA-49C8-BD62-7C14221C45F2}.Release|x64.Build.0 = Release|x64 + {077BDBFD-C1AA-49C8-BD62-7C14221C45F2}.Release|x86.ActiveCfg = Release|Win32 + {077BDBFD-C1AA-49C8-BD62-7C14221C45F2}.Release|x86.Build.0 = Release|Win32 + {C4454D2C-8024-41B8-BAC1-FC2E544C810F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C4454D2C-8024-41B8-BAC1-FC2E544C810F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C4454D2C-8024-41B8-BAC1-FC2E544C810F}.Debug|Any CPU.Deploy.0 = Debug|Any CPU + {C4454D2C-8024-41B8-BAC1-FC2E544C810F}.Debug|ARM64.ActiveCfg = Debug|ARM64 + {C4454D2C-8024-41B8-BAC1-FC2E544C810F}.Debug|ARM64.Build.0 = Debug|ARM64 + {C4454D2C-8024-41B8-BAC1-FC2E544C810F}.Debug|ARM64.Deploy.0 = Debug|ARM64 + {C4454D2C-8024-41B8-BAC1-FC2E544C810F}.Debug|x64.ActiveCfg = Debug|x64 + {C4454D2C-8024-41B8-BAC1-FC2E544C810F}.Debug|x64.Build.0 = Debug|x64 + {C4454D2C-8024-41B8-BAC1-FC2E544C810F}.Debug|x64.Deploy.0 = Debug|x64 + {C4454D2C-8024-41B8-BAC1-FC2E544C810F}.Debug|x86.ActiveCfg = Debug|x86 + {C4454D2C-8024-41B8-BAC1-FC2E544C810F}.Debug|x86.Build.0 = Debug|x86 + {C4454D2C-8024-41B8-BAC1-FC2E544C810F}.Debug|x86.Deploy.0 = Debug|x86 + {C4454D2C-8024-41B8-BAC1-FC2E544C810F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C4454D2C-8024-41B8-BAC1-FC2E544C810F}.Release|Any CPU.Build.0 = Release|Any CPU + {C4454D2C-8024-41B8-BAC1-FC2E544C810F}.Release|Any CPU.Deploy.0 = Release|Any CPU + {C4454D2C-8024-41B8-BAC1-FC2E544C810F}.Release|ARM64.ActiveCfg = Release|ARM64 + {C4454D2C-8024-41B8-BAC1-FC2E544C810F}.Release|ARM64.Build.0 = Release|ARM64 + {C4454D2C-8024-41B8-BAC1-FC2E544C810F}.Release|ARM64.Deploy.0 = Release|ARM64 + {C4454D2C-8024-41B8-BAC1-FC2E544C810F}.Release|x64.ActiveCfg = Release|x64 + {C4454D2C-8024-41B8-BAC1-FC2E544C810F}.Release|x64.Build.0 = Release|x64 + {C4454D2C-8024-41B8-BAC1-FC2E544C810F}.Release|x64.Deploy.0 = Release|x64 + {C4454D2C-8024-41B8-BAC1-FC2E544C810F}.Release|x86.ActiveCfg = Release|x86 + {C4454D2C-8024-41B8-BAC1-FC2E544C810F}.Release|x86.Build.0 = Release|x86 + {C4454D2C-8024-41B8-BAC1-FC2E544C810F}.Release|x86.Deploy.0 = Release|x86 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -1745,9 +1788,9 @@ Global {2A2D1131-273C-4E17-BCD3-8812170A4B95} = {448ED2E5-0B37-4D97-9E6B-8C10A507976A} {E3EDEC7F-A24E-4766-BB1D-6BDFBA157C51} = {2A2D1131-273C-4E17-BCD3-8812170A4B95} {442FB943-1197-48FE-B3B6-8C1BCA1E81E4} = {8630F7AA-2969-4DC9-8700-9B468C1DC21D} - {95AAEE9C-7D53-4B7B-B2C9-DB388BEF6A14} = {8630F7AA-2969-4DC9-8700-9B468C1DC21D} - {5CFEB8C6-9242-40AC-9CF9-2F5C18001E3C} = {95AAEE9C-7D53-4B7B-B2C9-DB388BEF6A14} - {21651459-648E-475C-91DB-2BDE359C75A4} = {95AAEE9C-7D53-4B7B-B2C9-DB388BEF6A14} + {21651459-648E-475C-91DB-2BDE359C75A4} = {8630F7AA-2969-4DC9-8700-9B468C1DC21D} + {077BDBFD-C1AA-49C8-BD62-7C14221C45F2} = {AC5FFC80-92FE-4933-BED2-EC5519AC4440} + {C4454D2C-8024-41B8-BAC1-FC2E544C810F} = {AC5FFC80-92FE-4933-BED2-EC5519AC4440} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {4B3D7591-CFEC-4762-9A07-ABE99938FB77} @@ -1756,7 +1799,6 @@ Global test\inc\inc.vcxitems*{08bc78e0-63c6-49a7-81b3-6afc3deac4de}*SharedItemsImports = 4 dev\PushNotifications\PushNotifications.vcxitems*{103c0c23-7ba8-4d44-a63c-83488e2e3a81}*SharedItemsImports = 9 test\inc\inc.vcxitems*{21651459-648e-475c-91db-2bde359c75a4}*SharedItemsImports = 4 - test\OAuth\OAuthTestCommon\OAuthTestCommon.vcxitems*{21651459-648e-475c-91db-2bde359c75a4}*SharedItemsImports = 4 test\inc\inc.vcxitems*{2cd5cd9b-cf45-4fa7-9769-ee4e02426bf0}*SharedItemsImports = 4 dev\EnvironmentManager\API\Microsoft.Process.Environment.vcxitems*{2f3fad1b-d3df-4866-a3a3-c2c777d55638}*SharedItemsImports = 9 dev\OAuth\OAuth.vcxitems*{3e7fd510-8b66-40e7-a80b-780cb8972f83}*SharedItemsImports = 9 @@ -1765,7 +1807,6 @@ Global dev\UndockedRegFreeWinRT\UndockedRegFreeWinRT.vcxitems*{56371ca6-144b-4989-a4e9-391ad4fa7651}*SharedItemsImports = 9 test\inc\inc.vcxitems*{56a1d696-feda-4333-bf37-772ebececb10}*SharedItemsImports = 4 test\inc\inc.vcxitems*{5b2d17fe-c371-417f-860c-3d32397c2404}*SharedItemsImports = 4 - test\OAuth\OAuthTestCommon\OAuthTestCommon.vcxitems*{5cfeb8c6-9242-40ac-9cf9-2f5c18001e3c}*SharedItemsImports = 9 test\inc\inc.vcxitems*{7c502995-59c3-483b-86ba-815985353633}*SharedItemsImports = 4 dev\Common\Common.vcxitems*{8828053c-d6ec-4744-8624-f8c676c2d4df}*SharedItemsImports = 9 dev\Licensing\Licensing.vcxitems*{885a43fa-052d-4b0d-a2dc-13ee15796435}*SharedItemsImports = 9 diff --git a/dev/OAuth/AuthFailure.cpp b/dev/OAuth/AuthFailure.cpp index b1367b0f44..40c8887a2d 100644 --- a/dev/OAuth/AuthFailure.cpp +++ b/dev/OAuth/AuthFailure.cpp @@ -15,32 +15,40 @@ namespace winrt::Microsoft::Windows::Security::Authentication::OAuth::implementa { std::map additionalParams; - for (auto&& entry : responseUri.QueryParsed()) - { - auto name = entry.Name(); - if (name == L"error"sv) + auto parseComponents = [&](const winrt::hstring& str) { + if (str.empty()) { - m_error = entry.Value(); + return; // Avoid unnecessary construction/activation } - else if (name == L"error_description"sv) - { - m_errorDescription = entry.Value(); - } - else if (name == L"error_uri"sv) - { - m_errorUri = Uri(entry.Value()); - } - else if (name == L"state"sv) - { - m_state = entry.Value(); - } - else + + for (auto&& entry : WwwFormUrlDecoder(str)) { - additionalParams.emplace(std::move(name), entry.Value()); + auto name = entry.Name(); + if (name == L"error"sv) + { + m_error = entry.Value(); + } + else if (name == L"error_description"sv) + { + m_errorDescription = entry.Value(); + } + else if (name == L"error_uri"sv) + { + m_errorUri = Uri(entry.Value()); + } + else if (name == L"state"sv) + { + m_state = entry.Value(); + } + else + { + additionalParams.emplace(std::move(name), entry.Value()); + } } - } + }; - // TODO: Look in the fragment part as well + parseComponents(responseUri.Query()); + parseComponents(fragment_component(responseUri)); m_additionalParams = winrt::single_threaded_map(std::move(additionalParams)).GetView(); } diff --git a/dev/OAuth/AuthManager.cpp b/dev/OAuth/AuthManager.cpp index a55c16e32a..7140bbde59 100644 --- a/dev/OAuth/AuthManager.cpp +++ b/dev/OAuth/AuthManager.cpp @@ -22,7 +22,7 @@ namespace winrt::Microsoft::Windows::Security::Authentication::OAuth::factory_im const oauth::AuthRequestParams& params) { auto paramsImpl = winrt::get_self(params); - auto asyncOp = winrt::make_self(authEndpoint, paramsImpl); + auto asyncOp = winrt::make_self(paramsImpl); { std::lock_guard guard{ m_mutex }; @@ -53,21 +53,33 @@ namespace winrt::Microsoft::Windows::Security::Authentication::OAuth::factory_im { // We need to extract the state in order to find the original request winrt::hstring state; - for (auto&& entry : responseUri.QueryParsed()) + auto tryFindState = [&](const winrt::hstring& str) { - if (entry.Name() == L"state") + if (str.empty()) { - state = entry.Value(); - break; + return; // Avoid unnecessary construction/activation } - } - // TODO: If we could not find the state, we need to check the fragment + for (auto&& entry : WwwFormUrlDecoder(str)) + { + if (entry.Name() == L"state") + { + state = entry.Value(); + break; + } + } + }; - // Don't throw an error. It could be the case that the application just blindly calls this function first + tryFindState(responseUri.Query()); if (state.empty()) { - return false; + tryFindState(fragment_component(responseUri)); + + // Don't throw an error. It could be the case that the application just blindly calls this function first + if (state.empty()) + { + return false; + } } // First check in our local pending list @@ -160,33 +172,41 @@ namespace winrt::Microsoft::Windows::Security::Authentication::OAuth::factory_im auto headers = request.Headers(); headers.Accept().ParseAdd(L"application/json"); - if (auto auth = clientAuth.Authorization()) + if (clientAuth) { - headers.Authorization(auth); - } + if (auto auth = clientAuth.Authorization()) + { + headers.Authorization(auth); + } - if (auto proxyAuth = clientAuth.ProxyAuthorization()) - { - headers.ProxyAuthorization(proxyAuth); - } + if (auto proxyAuth = clientAuth.ProxyAuthorization()) + { + headers.ProxyAuthorization(proxyAuth); + } - if (auto map = clientAuth.AdditionalHeaders()) - { - for (auto&& pair : map) + if (auto map = clientAuth.AdditionalHeaders()) { - if (!headers.TryAppendWithoutValidation(pair.Key(), pair.Value())) + for (auto&& pair : map) { - // TODO? Why might this fail? Throw? + if (!headers.TryAppendWithoutValidation(pair.Key(), pair.Value())) + { + // TODO? Why might this fail? Throw? + } } } } + auto cancellation = co_await winrt::get_cancellation_token(); + cancellation.enable_propagation(); + response = co_await httpClient.SendRequestAsync(request); // TODO: Check status code? if (!response.IsSuccessStatusCode()) { - __debugbreak(); // TODO - response.EnsureSuccessStatusCode(); // TODO: Could just use this? + auto status = response.StatusCode(); + HRESULT hr = MAKE_HRESULT(SEVERITY_ERROR, FACILITY_HTTP, static_cast(status)); + co_return implementation::TokenRequestResult::MakeFailure(std::move(response), + TokenFailureKind::HttpFailure, hr); } auto responseContentType = response.Content().Headers().ContentType().MediaType(); diff --git a/dev/OAuth/AuthRequestAsyncOperation.cpp b/dev/OAuth/AuthRequestAsyncOperation.cpp index c0f4c8b4de..81c4190a72 100644 --- a/dev/OAuth/AuthRequestAsyncOperation.cpp +++ b/dev/OAuth/AuthRequestAsyncOperation.cpp @@ -1,4 +1,4 @@ -#include +#include #include "common.h" #include "AuthManager.h" @@ -13,8 +13,7 @@ using namespace winrt::Windows::Foundation; using namespace winrt::Windows::Foundation::Collections; using namespace winrt::Windows::Security::Cryptography; -AuthRequestAsyncOperation::AuthRequestAsyncOperation(const Uri& authEndpoint, - implementation::AuthRequestParams* params) : +AuthRequestAsyncOperation::AuthRequestAsyncOperation(implementation::AuthRequestParams* params) : m_params(params->get_strong()) { try @@ -70,25 +69,28 @@ AuthRequestAsyncOperation::AuthRequestAsyncOperation(const Uri& authEndpoint, catch (...) { // Throwing in a constructor will cause the destructor not to run... - close_pipe(); + destroy(); throw; } } AuthRequestAsyncOperation::~AuthRequestAsyncOperation() { - close_pipe(); + destroy(); } -void AuthRequestAsyncOperation::close_pipe() +void AuthRequestAsyncOperation::destroy() { - if (m_state == state::reading) { - // TODO: Might this trigger a callback? If so, we may end up trying to perform more operations... Might need - // some synchronization and another state value here. - ::CancelIoEx(m_pipe, &m_overlapped); + // Expects lock to be held and is required since we haven't ensured all callbacks have completed + std::unique_lock guard{ m_mutex }; + close_pipe(); } + // Note that we don't hold the lock here for two reasons. The big reason is that 'WaitForThreadpoolWaitCallbacks may + // wait on a callback trying to acquire the lock. The second reason - and the reason we get away with this - is that + // this code path only gets called on destruction, meaning nothing except callbacks (which we wait for) will access + // or modify object state if (m_ptp) { if (!::SetThreadpoolWaitEx(m_ptp, nullptr, nullptr, nullptr)) @@ -107,9 +109,19 @@ void AuthRequestAsyncOperation::close_pipe() ::CloseHandle(m_overlapped.hEvent); m_overlapped.hEvent = nullptr; } +} + +void AuthRequestAsyncOperation::close_pipe() +{ + auto lastState = std::exchange(m_state, state::closed); + if (lastState == state::closed) + { + return; + } if (m_pipe != INVALID_HANDLE_VALUE) { + ::CancelIoEx(m_pipe, &m_overlapped); ::CloseHandle(m_pipe); m_pipe = INVALID_HANDLE_VALUE; } @@ -231,7 +243,7 @@ void AuthRequestAsyncOperation::transition_state(AsyncStatus status, const Uri& AsyncOperationCompletedHandler handler; { std::lock_guard guard{ m_mutex }; - // TODO: Should probably close pipe, however we might be in a callback which would deadlock... + close_pipe(); // State change is initiated by AuthManager and should never happen twice WINRT_ASSERT(m_status == AsyncStatus::Started); @@ -261,20 +273,35 @@ void CALLBACK AuthRequestAsyncOperation::async_callback(PTP_CALLBACK_INSTANCE, P TP_WAIT_RESULT waitResult) { auto pThis = static_cast(context); + pThis->callback(waitResult); +} +void AuthRequestAsyncOperation::callback(TP_WAIT_RESULT waitResult) +{ try { + state currentState; DWORD bytes = 0; DWORD overlappedError = ERROR_SUCCESS; - if (waitResult == WAIT_OBJECT_0) { - if (!::GetOverlappedResult(pThis->m_pipe, &pThis->m_overlapped, &bytes, false)) + std::shared_lock guard{ m_mutex }; + currentState = m_state; + if (currentState == state::closed) { - overlappedError = ::GetLastError(); + // Nothing productive we can do if the pipe was closed. This also likely means the result was an error + return; + } + + if (waitResult == WAIT_OBJECT_0) + { + if (!::GetOverlappedResult(m_pipe, &m_overlapped, &bytes, false)) + { + overlappedError = ::GetLastError(); + } } } - switch (pThis->m_state) + switch (currentState) { case state::connecting: { WINRT_ASSERT(waitResult == WAIT_OBJECT_0); // TODO: Is this valid? Maybe when we cancelled? Error? @@ -291,28 +318,27 @@ void CALLBACK AuthRequestAsyncOperation::async_callback(PTP_CALLBACK_INSTANCE, P L"Failed waiting for a client to connect to the pipe"); } - pThis->initiate_read(); + initiate_read(); } break; case state::reading: { if (overlappedError == ERROR_MORE_DATA) { - pThis->m_pipeReadData.insert(pThis->m_pipeReadData.end(), pThis->m_pipeReadBuffer, - pThis->m_pipeReadBuffer + pThis->m_overlapped.InternalHigh); - pThis->initiate_read(); // Need more data before we can complete + // NOTE: Pipe server is effectively single threaded, hence no synchronization needed here + m_pipeReadData.insert(m_pipeReadData.end(), m_pipeReadBuffer, + m_pipeReadBuffer + m_overlapped.InternalHigh); + initiate_read(); // Need more data before we can complete } else if ((waitResult != WAIT_OBJECT_0) || (overlappedError != ERROR_SUCCESS)) { // Ideally we could assume that read timeouts/failures are fatal, however we don't know if the client is // trustworthy and we don't want some arbitrary process to bait us into terminating the request - [[maybe_unused]] auto disconnectResult = ::DisconnectNamedPipe(pThis->m_pipe); - WINRT_ASSERT(disconnectResult); // TODO: What if the client disconnected from us? - pThis->connect_to_new_client(); + connect_to_new_client(true); } else { - pThis->on_read_complete(); + on_read_complete(); } } break; @@ -325,12 +351,13 @@ void CALLBACK AuthRequestAsyncOperation::async_callback(PTP_CALLBACK_INSTANCE, P } catch (...) { - winrt::make_self()->error(pThis, winrt::to_hresult()); + winrt::make_self()->error(this, winrt::to_hresult()); } } bool AuthRequestAsyncOperation::try_create_pipe(const winrt::hstring& state) { + // NOTE: Called on construction where no synchronization is needed auto name = request_pipe_name(state); m_pipe = ::CreateNamedPipeW(name.c_str(), PIPE_ACCESS_INBOUND | FILE_FLAG_FIRST_PIPE_INSTANCE | FILE_FLAG_OVERLAPPED, @@ -345,24 +372,50 @@ bool AuthRequestAsyncOperation::try_create_pipe(const winrt::hstring& state) return false; } -void AuthRequestAsyncOperation::connect_to_new_client() +void AuthRequestAsyncOperation::connect_to_new_client(bool disconnect) { m_pipeReadData.clear(); - [[maybe_unused]] auto connectResult = ::ConnectNamedPipe(m_pipe, &m_overlapped); - WINRT_ASSERT(!connectResult); // Only non-zero in synchronous mode, even if already connected - if (auto err = ::GetLastError(); err == ERROR_PIPE_CONNECTED) + DWORD lastError; + { + std::shared_lock guard{ m_mutex }; + if (m_state == state::closed) + { + return; + } + + if (disconnect) + { + [[maybe_unused]] auto disconnectResult = ::DisconnectNamedPipe(m_pipe); + WINRT_ASSERT(disconnectResult); // TODO: Correct if the client disconnected from us? + } + + [[maybe_unused]] auto connectResult = ::ConnectNamedPipe(m_pipe, &m_overlapped); + WINRT_ASSERT(!connectResult); // Only non-zero in asynchronous mode, even if already connected + lastError = ::GetLastError(); + } + + if (lastError == ERROR_PIPE_CONNECTED) { // Client already connected initiate_read(); } - else if (err != ERROR_IO_PENDING) + else if (lastError != ERROR_IO_PENDING) { - throw winrt::hresult_error(HRESULT_FROM_WIN32(err), L"Failed to listen for clients on the pipe"); + throw winrt::hresult_error(HRESULT_FROM_WIN32(lastError), L"Failed to listen for clients on the pipe"); } else { - m_state = state::connecting; + { + std::lock_guard guard{ m_mutex }; + if (m_state == state::closed) + { + // Don't set the threadpool wait again as we may have just cleared it! + return; + } + + m_state = state::connecting; + } ::SetThreadpoolWait(m_ptp, m_overlapped.hEvent, nullptr); } } @@ -371,7 +424,19 @@ void AuthRequestAsyncOperation::initiate_read() { while (true) { - if (::ReadFile(m_pipe, m_pipeReadBuffer, sizeof(m_pipeReadBuffer), nullptr, &m_overlapped)) + BOOL readResult; + { + std::shared_lock guard{ m_mutex }; + if (m_state == state::closed) + { + // No pipe to read from + return; + } + + readResult = ::ReadFile(m_pipe, m_pipeReadBuffer, sizeof(m_pipeReadBuffer), nullptr, &m_overlapped); + } + + if (readResult) { // Immediate success. No need to wait on_read_complete(); @@ -387,6 +452,13 @@ void AuthRequestAsyncOperation::initiate_read() else if (err == ERROR_IO_PENDING) { // Reading asynchronously + std::lock_guard guard{ m_mutex }; + if (m_state == state::closed) + { + // Simultaneously closed; don't set the threadpool wait as we may have just cleared it! + return; + } + m_state = state::reading; std::int64_t timeout = std::chrono::duration_cast(-50ms).count(); // 50ms timeout ::SetThreadpoolWait(m_ptp, m_overlapped.hEvent, reinterpret_cast(&timeout)); @@ -394,11 +466,7 @@ void AuthRequestAsyncOperation::initiate_read() } else { - // Ideally we could assume that read timeouts/failures are fatal, however we don't know if the client is - // trustworthy and we don't want some arbitrary process to bait us into terminating the request - [[maybe_unused]] auto disconnectResult = ::DisconnectNamedPipe(m_pipe); - WINRT_ASSERT(disconnectResult); // TODO: What if the client disconnected from us? - connect_to_new_client(); + connect_to_new_client(true); break; } } @@ -414,17 +482,38 @@ void AuthRequestAsyncOperation::on_read_complete() auto expectedState = m_params->State(); auto encryptedBuffer = CryptographicBuffer::CreateFromByteArray(m_pipeReadData); auto uriString = decrypt(encryptedBuffer, expectedState); - Uri responseUri(uriString); // An exception is unlikely (we needed the state from the URI to open the pipe in the first place), but could // happen if someone is connecting and sending garbage data. We'll catch below, so all is okay - auto state = responseUri.QueryParsed().GetFirstValueByName(L"state"); - if (state == expectedState) + Uri responseUri(uriString); + winrt::hstring state; + auto tryFindState = [&](const winrt::hstring& str) { - if (winrt::make_self()->try_complete_local(state, responseUri)) + if (str.empty()) + { + return; // Avoid unnecessary construction/activation + } + + for (auto&& entry : WwwFormUrlDecoder(str)) { - shouldReconnect = false; + if (entry.Name() == L"state") + { + state = entry.Value(); + break; + } } + }; + + tryFindState(responseUri.Query()); + if (state.empty()) + { + tryFindState(fragment_component(responseUri)); + } + + if (state == expectedState) + { + shouldReconnect = + winrt::make_self()->try_complete_local(state, responseUri); } } catch (...) @@ -432,14 +521,11 @@ void AuthRequestAsyncOperation::on_read_complete() // Likely handed bad data; just disconnect and attempt a reconnect } - // The client will only ever send a single message, so disconnect and form a new connection - [[maybe_unused]] auto disconnectResult = ::DisconnectNamedPipe(m_pipe); - WINRT_ASSERT(disconnectResult); // TODO - if (shouldReconnect) { - connect_to_new_client(); + connect_to_new_client(true); } + // Otherwise the 'try_complete_local' call should have closed the pipe } void AuthRequestAsyncOperation::invoke_handler(const AsyncOperationCompletedHandler& handler) diff --git a/dev/OAuth/AuthRequestAsyncOperation.h b/dev/OAuth/AuthRequestAsyncOperation.h index a1ece2c894..c215bbcf20 100644 --- a/dev/OAuth/AuthRequestAsyncOperation.h +++ b/dev/OAuth/AuthRequestAsyncOperation.h @@ -6,7 +6,7 @@ struct AuthRequestAsyncOperation : winrt::implements, foundation::IAsyncInfo> { - AuthRequestAsyncOperation(const foundation::Uri& authEndpoint, oauth::implementation::AuthRequestParams* params); + AuthRequestAsyncOperation(oauth::implementation::AuthRequestParams* params); ~AuthRequestAsyncOperation(); // IAsyncInfo @@ -29,15 +29,17 @@ struct AuthRequestAsyncOperation : private: enum class state { + closed, connecting, reading, }; static void CALLBACK async_callback(PTP_CALLBACK_INSTANCE, PVOID context, PTP_WAIT, TP_WAIT_RESULT waitResult); + void callback(TP_WAIT_RESULT waitResult); bool try_create_pipe(const winrt::hstring& state); void close_pipe(); - void connect_to_new_client(); + void connect_to_new_client(bool disconnect = false); void initiate_read(); void on_read_complete(); @@ -45,6 +47,8 @@ struct AuthRequestAsyncOperation : winrt::hresult hr = {}); void invoke_handler(const foundation::AsyncOperationCompletedHandler& handler); + void destroy(); + std::shared_mutex m_mutex; winrt::com_ptr m_params; diff --git a/dev/OAuth/AuthRequestParams.cpp b/dev/OAuth/AuthRequestParams.cpp index 9d2865af7a..a77e9c40ca 100644 --- a/dev/OAuth/AuthRequestParams.cpp +++ b/dev/OAuth/AuthRequestParams.cpp @@ -28,13 +28,15 @@ namespace winrt::Microsoft::Windows::Security::Authentication::OAuth::implementa oauth::AuthRequestParams AuthRequestParams::CreateForAuthorizationCodeRequest(const winrt::hstring& clientId) { - return winrt::make(L"code", clientId); + return CreateForAuthorizationCodeRequest(clientId, nullptr); } oauth::AuthRequestParams AuthRequestParams::CreateForAuthorizationCodeRequest(const winrt::hstring& clientId, const Uri& redirectUri) { - return winrt::make(L"code", clientId, redirectUri); + auto result = winrt::make_self(L"code", clientId, redirectUri); + result->m_codeChallengeMethod = CodeChallengeMethodKind::S256; + return *result; } oauth::AuthRequestParams AuthRequestParams::CreateForImplicitRequest(const winrt::hstring& clientId) diff --git a/dev/OAuth/AuthRequestParams.h b/dev/OAuth/AuthRequestParams.h index abcf636431..7d8f28b2f3 100644 --- a/dev/OAuth/AuthRequestParams.h +++ b/dev/OAuth/AuthRequestParams.h @@ -62,7 +62,7 @@ namespace winrt::Microsoft::Windows::Security::Authentication::OAuth::implementa winrt::hstring m_state; winrt::hstring m_scope; winrt::hstring m_codeVerifier; - oauth::CodeChallengeMethodKind m_codeChallengeMethod; + oauth::CodeChallengeMethodKind m_codeChallengeMethod = oauth::CodeChallengeMethodKind::None; winrt::com_ptr> m_additionalParams = winrt::make_self>(); }; diff --git a/dev/OAuth/AuthRequestResult.cpp b/dev/OAuth/AuthRequestResult.cpp index 5c3f62618b..57faf80d88 100644 --- a/dev/OAuth/AuthRequestResult.cpp +++ b/dev/OAuth/AuthRequestResult.cpp @@ -18,24 +18,32 @@ namespace winrt::Microsoft::Windows::Security::Authentication::OAuth::implementa // We first need to figure out if this is a success or failure response bool isError = false; bool isSuccess = false; - for (auto&& entry : m_responseUri.QueryParsed()) - { - auto name = entry.Name(); - if ((name == L"code") || (name == L"access_token")) + auto checkComponent = [&](const winrt::hstring& str) { + if (str.empty()) { - isSuccess = true; - break; + return; // Avoid unnecessary construction/activation } - else if (name == L"error") + + for (auto&& entry : WwwFormUrlDecoder(str)) { - isError = true; - break; + auto name = entry.Name(); + if ((name == L"code") || (name == L"access_token")) + { + isSuccess = true; + break; + } + else if (name == L"error") + { + isError = true; + break; + } } - } + }; + checkComponent(responseUri.Query()); if (!isError && !isSuccess) { - // TODO: May also need to check the fragment + checkComponent(fragment_component(responseUri)); } // If we don't recognize the response as an error, interpret it as success. The application may be using an diff --git a/dev/OAuth/AuthResponse.cpp b/dev/OAuth/AuthResponse.cpp index a2a8ff345a..e4b36b8eb9 100644 --- a/dev/OAuth/AuthResponse.cpp +++ b/dev/OAuth/AuthResponse.cpp @@ -15,41 +15,48 @@ namespace winrt::Microsoft::Windows::Security::Authentication::OAuth::implementa m_requestParams(requestParams->get_strong()) { std::map additionalParams; - - for (auto&& entry : responseUri.QueryParsed()) - { - auto name = entry.Name(); - if (name == L"state"sv) - { - m_state = entry.Value(); - } - else if (name == L"code"sv) - { - m_code = entry.Value(); - } - else if (name == L"access_token"sv) - { - m_accessToken = entry.Value(); - } - else if (name == L"token_type"sv) - { - m_tokenType = entry.Value(); - } - else if (name == L"expires_in"sv) + auto parseComponents = [&](const winrt::hstring& str) { + if (str.empty()) { - m_expiresIn = entry.Value(); + return; // Avoid unnecessary construction/activation } - else if (name == L"scope"sv) - { - m_scope = entry.Value(); - } - else + + for (auto&& entry : WwwFormUrlDecoder(str)) { - additionalParams.emplace(std::move(name), entry.Value()); + auto name = entry.Name(); + if (name == L"state"sv) + { + m_state = entry.Value(); + } + else if (name == L"code"sv) + { + m_code = entry.Value(); + } + else if (name == L"access_token"sv) + { + m_accessToken = entry.Value(); + } + else if (name == L"token_type"sv) + { + m_tokenType = entry.Value(); + } + else if (name == L"expires_in"sv) + { + m_expiresIn = entry.Value(); + } + else if (name == L"scope"sv) + { + m_scope = entry.Value(); + } + else + { + additionalParams.emplace(std::move(name), entry.Value()); + } } - } + }; - // TODO: Look in the fragment part as well + parseComponents(responseUri.Query()); + parseComponents(fragment_component(responseUri)); m_additionalParams = winrt::single_threaded_map(std::move(additionalParams)).GetView(); } diff --git a/dev/OAuth/Crypto.h b/dev/OAuth/Crypto.h index a6d8ed488c..217d0ccda4 100644 --- a/dev/OAuth/Crypto.h +++ b/dev/OAuth/Crypto.h @@ -60,10 +60,27 @@ inline crypto::Core::CryptographicKey create_key(const winrt::hstring& keyString { using namespace winrt::Windows::Security::Cryptography; using namespace winrt::Windows::Security::Cryptography::Core; + using namespace winrt::Windows::Storage::Streams; + WINRT_ASSERT(!keyString.empty()); + + // AES key must be 128, 192, or 256 bits (16, 24, or 32 bytes). Note that the key doesn't have to make a valid + // string. If we end up slicing a UTF-8 character, that's okay auto keyBuffer = CryptographicBuffer::ConvertStringToBinary(keyString, BinaryStringEncoding::Utf8); + auto keyBufferBegin = keyBuffer.data(); + auto keyBufferEnd = keyBufferBegin + keyBuffer.Length(); + + // Repeat the key string as necessary to achieve the desired length + std::vector buffer(keyBufferBegin, keyBufferEnd); + auto desiredSize = (buffer.size() <= 16) ? 16 : (buffer.size() <= 24) ? 24 : 32; + while (buffer.size() < desiredSize) + { + buffer.insert(buffer.end(), keyBufferBegin, keyBufferEnd); + } + buffer.resize(desiredSize); + auto algo = SymmetricKeyAlgorithmProvider::OpenAlgorithm(SymmetricAlgorithmNames::AesEcbPkcs7()); - return algo.CreateSymmetricKey(keyBuffer); + return algo.CreateSymmetricKey(CryptographicBuffer::CreateFromByteArray(buffer)); } inline streams::IBuffer encrypt(const winrt::hstring& message, const winrt::hstring& keyString) diff --git a/dev/OAuth/TokenRequestParams.cpp b/dev/OAuth/TokenRequestParams.cpp index 4dea605f5b..48565611f9 100644 --- a/dev/OAuth/TokenRequestParams.cpp +++ b/dev/OAuth/TokenRequestParams.cpp @@ -224,12 +224,9 @@ namespace winrt::Microsoft::Windows::Security::Authentication::OAuth::implementa addIfSet(L"password", m_password); addIfSet(L"scope", m_scope); addIfSet(L"refresh_token", m_refreshToken); - if (m_additionalParams) + for (auto&& pair : IMap{ *m_additionalParams }) { - for (auto&& pair : IMap{ *m_additionalParams }) - { - result.emplace(pair.Key(), pair.Value()); - } + result.emplace(pair.Key(), pair.Value()); } return result; diff --git a/dev/OAuth/common.h b/dev/OAuth/common.h index ad167e8980..3753e749c5 100644 --- a/dev/OAuth/common.h +++ b/dev/OAuth/common.h @@ -18,3 +18,16 @@ namespace oauth = winrt::Microsoft::Windows::Security::Authentication::OAuth; namespace streams = winrt::Windows::Storage::Streams; #include "Crypto.h" + +inline winrt::hstring fragment_component(const foundation::Uri& uri) +{ + auto fragment = uri.Fragment(); + std::wstring_view fragmentStr = fragment; + if (!fragmentStr.empty()) + { + WINRT_ASSERT(fragmentStr.front() == '#'); + fragmentStr = fragmentStr.substr(1); + } + + return winrt::hstring(fragmentStr); +} diff --git a/test/OAuth/OAuthTestCommon/OAuthTestCommon.vcxitems b/test/OAuth/OAuthTestCommon/OAuthTestCommon.vcxitems deleted file mode 100644 index 9f6b21550c..0000000000 --- a/test/OAuth/OAuthTestCommon/OAuthTestCommon.vcxitems +++ /dev/null @@ -1,19 +0,0 @@ - - - - $(MSBuildAllProjects);$(MSBuildThisFileFullPath) - true - {5cfeb8c6-9242-40ac-9cf9-2f5c18001e3c} - - - - %(AdditionalIncludeDirectories);$(MSBuildThisFileDirectory) - - - - - - - - - \ No newline at end of file diff --git a/test/OAuth/OAuthTestCommon/OAuthTestValues.h b/test/OAuth/OAuthTestCommon/OAuthTestValues.h deleted file mode 100644 index c4fd9e3124..0000000000 --- a/test/OAuth/OAuthTestCommon/OAuthTestValues.h +++ /dev/null @@ -1,107 +0,0 @@ -#pragma once - -#include -#include - -#include -#include - -// Strings associated with errors -namespace strings::error -{ - inline constexpr std::wstring_view invalid_request = L"invalid_request"; - inline constexpr std::wstring_view unauthorized_client = L"unauthorized_client"; - inline constexpr std::wstring_view access_denied = L"access_denied"; - inline constexpr std::wstring_view unsupported_response_type = L"unsupported_response_type"; - inline constexpr std::wstring_view invalid_scope = L"invalid_scope"; - inline constexpr std::wstring_view server_error = L"server_error"; - inline constexpr std::wstring_view temporarily_unavailable = L"temporarily_unavailable"; -} - -// Strings associated with the request -namespace strings::request -{ - inline constexpr std::wstring_view response_type = L"response_type"; - inline constexpr std::wstring_view client_id = L"client_id"; - inline constexpr std::wstring_view redirect_uri = L"redirect_uri"; - inline constexpr std::wstring_view scope = L"scope"; - inline constexpr std::wstring_view state = L"state"; - inline constexpr std::wstring_view code_challenge = L"code_challenge"; - inline constexpr std::wstring_view code_challenge_method = L"code_challenge_method"; -} - -namespace strings::response_type -{ - - inline constexpr std::wstring_view code = L"code"; - inline constexpr std::wstring_view token = L"token"; -} - -// Test parameters that we embed in the client_id -namespace strings::client_id -{ - inline constexpr std::wstring_view scenario = L"scenario"; - //inline constexpr std::wstring_view expect_success = L"expect_success"; - //inline constexpr std::wstring_view expect_additional_params = L"expect_additional_params"; - //inline constexpr std::wstring_view report_expires_in = L"report_expires_in"; - //inline constexpr std::wstring_view limit_scope = L"limit_scope"; -} - -// We're not issuing real tokens, so use a predictable string for validation. This string is specifically formulated to -// ensure that we escape/encode characters properly -inline constexpr std::wstring_view token = L"tacos=yummy&location=\"my tummy\""; - -struct uri_builder -{ - std::wstring uri; - wchar_t prefix; - - uri_builder(const winrt::Windows::Foundation::Uri& uri, bool useQuery = true) : - uri(uri.RawUri()) - { - if (useQuery) - { - prefix = uri.Query().empty() ? L'?' : '&'; - } - else - { - prefix = '#'; - } - } - - void add(std::wstring_view name, std::wstring_view value) - { - assert(!name.empty() && !value.empty()); - - uri.push_back(prefix); - prefix = L'&'; - uri.append(winrt::Windows::Foundation::Uri::EscapeComponent(name)); - uri.push_back(L'='); - uri.append(winrt::Windows::Foundation::Uri::EscapeComponent(value)); - } - - void add_optional(std::wstring_view name, std::wstring_view value) - { - if (!value.empty()) - { - add(name, value); - } - } - - winrt::Windows::Foundation::Uri get() - { - return winrt::Windows::Foundation::Uri{ uri }; - } -}; - -// Each test has its own "client id" so that we can differentiate which test is running. Note that this intentionally -// uses characters that need to be urlencoded -// #define TEST_CLIENT_ID(test) L"client:test=" ##test -// -//#define INCORRECT_STATE_CLIENT_ID TEST_CLIENT_ID(IncorrectState) -//#define COMPLETE_TWICE_CLIENT_ID TEST_CLIENT_ID(CompleteTwice) -// -//// We control the behavior of the fake user agent and authorization server through the client id -//#define TEST_CLIENT_ID(expectSuccess, expectAdditionalParams, reportExpiresIn, limitScope) \ -// L"expect_success=" ##expectSuccess "&expect_additional_params=" ##expectAdditionalParams \ -// "&report_expires_in=" ##reportExpiresIn L"&limit_scope=" ##limitScope diff --git a/test/OAuth/OAuthTests/OAuthTests.cpp b/test/OAuth/OAuthTests/OAuthTests.cpp deleted file mode 100644 index 8efb2a119a..0000000000 --- a/test/OAuth/OAuthTests/OAuthTests.cpp +++ /dev/null @@ -1,462 +0,0 @@ - -#include -#include -#include -#include -#include - -#include - -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include - -#include // Included last to enable the most features - -#define VERIFY_WINSOCK_SUCCEEDED(expr) \ - do { \ - auto wsaResultCode = (expr); \ - if (wsaResultCode == SOCKET_ERROR) { \ - auto wsaLastError = ::WSAGetLastError(); \ - std::wstring msg = L"WSASucceeded(" #expr ") - Error (" + std::to_wstring(wsaLastError) + L")"; \ - WEX::TestExecution::Verify::Fail(msg.c_str(), WEX::TestExecution::ErrorInfo{ __WFILE__, __WFUNCTION__, __LINE__ }); \ - } \ - } while(0, 0) - -using namespace std::literals; -using namespace WEX::Common; -using namespace WEX::Logging; -using namespace WEX::TestExecution; -using namespace winrt::Microsoft::Windows::Security::Authentication::OAuth; -using namespace winrt::Windows::Data::Json; -using namespace winrt::Windows::Foundation; -using namespace winrt::Windows::Foundation::Collections; -using namespace winrt::Windows::Security::Cryptography; -using namespace winrt::Windows::Storage::Streams; - -EXTERN_C IMAGE_DOS_HEADER __ImageBase; - -struct OAuthTests -{ - BEGIN_TEST_CLASS(OAuthTests) - TEST_CLASS_PROPERTY(L"ThreadingModel", L"MTA") - END_TEST_CLASS() - - TEST_CLASS_SETUP(Setup) - { - Test::Bootstrap::Setup(); - - // Detour ShellExecuteW - ::DetourTransactionBegin(); - ::DetourUpdateThread(::GetCurrentThread()); - if (auto err = ::DetourAttach(reinterpret_cast(&RealShellExecuteW), &DetouredShellExecuteW)) - { - Log::Error(WEX::Common::String().Format(L"DetourAttach failed: %d", err)); - ::DetourTransactionAbort(); - return false; - } - ::DetourTransactionCommit(); - - // Initialize the HTTP server we use for token requests - VERIFY_WIN32_SUCCEEDED(::HttpInitialize(HTTPAPI_VERSION_2, HTTP_INITIALIZE_SERVER, nullptr)); - VERIFY_WIN32_SUCCEEDED(::HttpCreateRequestQueue(HTTPAPI_VERSION_2, nullptr, nullptr, 0, &m_requestQueue)); - VERIFY_WIN32_SUCCEEDED(::HttpCreateServerSession(HTTPAPI_VERSION_2, &m_serverSessionId, 0)); - VERIFY_WIN32_SUCCEEDED(::HttpCreateUrlGroup(m_serverSessionId, &m_urlGroup, 0)); - - HTTP_BINDING_INFO bindingInfo = {}; - bindingInfo.Flags.Present = 1; - bindingInfo.RequestQueueHandle = m_requestQueue; - VERIFY_WIN32_SUCCEEDED(::HttpSetUrlGroupProperty(m_urlGroup, HttpServerBindingProperty, &bindingInfo, - static_cast(sizeof(bindingInfo)))); - - // Find an open port; note that ports in the low 50000s are frequently claimed, hence the large iteration bounds - ULONG err = 0; - for (std::uint16_t i = 0; i < 500; ++i) - { - wchar_t buffer[18 + 5 + 1]; - std::swprintf(buffer, std::size(buffer), L"http://127.0.0.1:%d/", m_serverPort); - - err = ::HttpAddUrlToUrlGroup(m_urlGroup, buffer, 0, 0); - if (err == NO_ERROR) - { - m_serverUrlBase = buffer; - break; - } - - ++m_serverPort; - } - - VERIFY_WIN32_SUCCEEDED(err, L"Failed to find an open port"); - m_httpServerThread = std::thread([this] { - RunHttpServer(); - }); - - return true; - } - - TEST_CLASS_CLEANUP(Cleanup) - { - // Tear down the HTTP server - m_serverShutdownEvent.SetEvent(); - m_httpServerThread.join(); - - if (m_urlGroup) - { - ::HttpCloseUrlGroup(m_urlGroup); - m_urlGroup = 0; - } - - if (m_serverSessionId) - { - ::HttpCloseServerSession(m_serverSessionId); - m_serverSessionId = 0; - } - - if (m_requestQueue) - { - ::HttpCloseRequestQueue(m_requestQueue); - m_requestQueue = nullptr; - } - - ::HttpTerminate(HTTP_INITIALIZE_SERVER, nullptr); - - // Clean up our detours - ::DetourTransactionBegin(); - ::DetourUpdateThread(::GetCurrentThread()); - ::DetourDetach(reinterpret_cast(&RealShellExecuteW), &DetouredShellExecuteW); - ::DetourTransactionCommit(); - - Test::Bootstrap::Cleanup(); - - return true; - } - - template - static bool WaitWithTimeout(const IAsyncOperation& op) - { - wil::unique_event event(wil::EventOptions::None); - op.Completed([&](const IAsyncOperation&, AsyncStatus) { - event.SetEvent(); - }); - - // 10 seconds is beyond - if (::WaitForSingleObject(event.get(), 10000) == WAIT_OBJECT_0) - { - return true; - } - - op.Cancel(); - return false; - } - - static inline constexpr std::wstring_view auth_url = L"http://oauthtests.com/oauth"sv; - static inline constexpr std::wstring_view localhost_redirect_url = L"http://127.0.0.1/oauth"sv; - - TEST_METHOD(Localhost_BasicEndToEnd) - { - static constexpr std::wstring_view client_id = L"scenario=Localhost_BasicEndToEnd"sv; - - auto params = AuthRequestParams::CreateForAuthorizationCodeRequest(client_id, Uri{ localhost_redirect_url }); - auto asyncOp = AuthManager::InitiateAuthRequestAsync(Uri{ auth_url }, params); - VERIFY_IS_TRUE(WaitWithTimeout(asyncOp)); - VERIFY_ARE_EQUAL(AsyncStatus::Completed, asyncOp.Status()); - - auto result = asyncOp.GetResults(); - auto response = result.Response(); - VERIFY_IS_NOT_NULL(response); - VERIFY_IS_NULL(result.Failure()); - - VERIFY_ARE_EQUAL(params.State(), response.State()); - } - - // Detoured Functions - static HINSTANCE __stdcall DetouredShellExecuteW(_In_opt_ HWND hwnd, _In_opt_ LPCWSTR operation, _In_ LPCWSTR file, - _In_opt_ LPCWSTR params, _In_opt_ LPCWSTR directory, _In_ INT showCmd) try - { - std::wstring_view fileStr = file; - if (fileStr.substr(0, auth_url.size()) == auth_url) - { - // There's no point in launching the browser and trying to fake an authorization flow as that would do - // nothing to test the API. Instead, perform the logic of the browser and authorization flow here in-proc - winrt::hstring responseType; - winrt::hstring clientId; - Uri redirectUri{ nullptr }; - winrt::hstring scope; - winrt::hstring state; - winrt::hstring codeChallenge; - winrt::hstring codeChallengeMethod; - - winrt::hstring scenario; - - Uri uri{ fileStr }; - winrt::hstring errorString; - winrt::hstring errorMessage; - for (auto&& entry : uri.QueryParsed()) - { - auto name = entry.Name(); - auto value = entry.Value(); - if (name == strings::request::response_type) - { - responseType = value; - } - else if (name == strings::request::client_id) - { - clientId = value; - } - else if (name == strings::request::redirect_uri) - { - redirectUri = Uri{ value }; - } - else if (name == strings::request::scope) - { - scope = value; - } - else if (name == strings::request::state) - { - state = value; - } - else if (name == strings::request::code_challenge) - { - codeChallenge = value; - } - else if (name == strings::request::code_challenge_method) - { - codeChallengeMethod = value; - } - else - { - // TODO: Respond with error? We might not have the redirect URI or state yet... - errorString = strings::error::invalid_request; - errorMessage = L"Unrecognized query parameter '"s + name + L"'"; - } - } - - for (auto&& entry : WwwFormUrlDecoder{ Uri::UnescapeComponent(clientId) }) - { - auto name = entry.Name(); - auto value = entry.Value(); - if (name == strings::client_id::scenario) - { - scenario = value; - } - else - { - errorString = strings::error::invalid_request; - errorMessage = L"Unrecognized client_id parameter '"s + name + L"'"; - } - } - - if (state.empty()) - { - // If no state is provided, we'll be unable to correlate the response to the request. The best we can - // really do here is to fail the launch which will fail the test early and reliably - Log::Error(L"No 'state' value provided in the URI"); - ::SetLastError(ERROR_INVALID_PARAMETER); - return nullptr; - } - else if (responseType.empty()) - { - errorString = strings::error::invalid_request; - errorMessage = L"Missing 'response_type'"; - } - else if (clientId.empty()) - { - errorString = strings::error::invalid_request; - errorMessage = L"Missing 'client_id'"; - } - - if (!redirectUri) - { - // TODO: Some tests will want to test us not providing a URI - // If we aren't given a URI and we didn't expect to not be given a URI, then we can't reliably return - // back an error response - Log::Error(L"No 'redirect_uri' value provided in the URI"); - ::SetLastError(ERROR_INVALID_PARAMETER); - return nullptr; - } - - Uri responseUri{ nullptr }; - if (responseType == strings::response_type::code) - { - std::wstring code = L"client="; - code += Uri::EscapeComponent(clientId); - if (codeChallengeMethod.empty()) - { - code += L"&challenge_method=none"; - } - else - { - code += L"&challenge_method="; - code += codeChallengeMethod; - code += L"&challenge="; - code += codeChallenge; - } - - uri_builder builder{ redirectUri }; - builder.add(L"code", code); - builder.add(L"state", state); - responseUri = builder.get(); - } - else if (responseType == strings::response_type::token) - { - // TODO - } - else - { - errorString = strings::error::unsupported_response_type; - errorMessage = L"Unknown response type '"s + responseType + L"'"; - } - - if (!errorString.empty()) - { - // NOTE: We may have created a response URI already, in which case we want to overwrite it here - uri_builder builder(redirectUri, responseType != strings::response_type::token); - builder.add(L"state", state); - builder.add(L"error", errorString); - builder.add_optional(L"error_description", errorMessage); - // TODO: error_uri - responseUri = builder.get(); - } - - if (responseUri.SchemeName() != L"http") - { - // Protocol activation - return RealShellExecuteW(hwnd, L"open", responseUri.RawUri().c_str(), nullptr, nullptr, SW_SHOWDEFAULT); - } - - // Simulating a localhost server. This would give the response back in-proc so we can just go ahead and - // do that directly. Note that we do this in the same callstack as that will test more interesting code - // paths - if (!AuthManager::CompleteAuthRequest(responseUri)) - { - Log::Warning(L"Failed to complete auth request"); - } - - return reinterpret_cast(42); // Value doesn't really matter; must be greater than 32 - } - - // Not intercepting. Let this "fall through" to the implementation - return RealShellExecuteW(hwnd, operation, file, params, directory, showCmd); - } - catch (...) - { - ::SetLastError(ERROR_FILE_NOT_FOUND); - return reinterpret_cast(ERROR_FILE_NOT_FOUND); - } - - // HTTP Server Thread Callback - void RunHttpServer() - { - wil::unique_event event{ wil::EventOptions::None }; - OVERLAPPED overlapped = {}; - overlapped.hEvent = event.get(); - - ULONG bufferSize = 0x1000; // 4 KB - auto buffer = std::make_unique(bufferSize); - auto request = reinterpret_cast(buffer.get()); - while (true) - { - auto err = ::HttpReceiveHttpRequest(m_requestQueue, HTTP_NULL_ID, HTTP_RECEIVE_REQUEST_FLAG_COPY_BODY, - request, bufferSize, nullptr, &overlapped); - if (err == ERROR_IO_PENDING) - { - // Wait for either shutdown or a request to come in - HANDLE handles[] = { event.get(), m_serverShutdownEvent.get() }; - auto waitResult = ::WaitForMultipleObjects(2, handles, false, INFINITE); - if (waitResult == (WAIT_OBJECT_0 + 1)) - { - // Shutdown - ::CancelIo(m_requestQueue); - break; - } - else if (waitResult != WAIT_OBJECT_0) - { - Log::Warning(WEX::Common::String().Format( - L"WaitForMultipleObjects failed in the HTTP server thread: %d", ::GetLastError())); - ::CancelIo(m_requestQueue); - break; - } - } - - // We have a request; we'll block here until we have all data, if needed - DWORD bytes; - ::GetOverlappedResult(m_requestQueue, &overlapped, &bytes, false); - err = ::GetLastError(); - if (err == ERROR_MORE_DATA) - { - bufferSize = bytes; - buffer = std::make_unique(bufferSize); - request = reinterpret_cast(buffer.get()); - err = ::HttpReceiveHttpRequest(m_requestQueue, request->RequestId, HTTP_RECEIVE_REQUEST_FLAG_COPY_BODY, - request, bufferSize, &bytes, nullptr); - } - - if (err == ERROR_CONNECTION_INVALID) - { - // Connection corrupted by peer - continue; - } - else if (err != ERROR_SUCCESS) - { - Log::Warning(WEX::Common::String().Format(L"HttpReceiveHttpRequest failed: %d", err)); - break; - } - - switch (request->Verb) - { - case HttpVerbGET: - HandleGetRequest(request); - break; - - case HttpVerbPOST: - HandlePostRequest(request); - break; - - default: - Log::Warning(L"Received an HTTP request with an unexpected verb"); - break; - } - } - } - - void HandleGetRequest(HTTP_REQUEST* request) - { - (void)request; // TODO - } - - void HandlePostRequest(HTTP_REQUEST* request) - { - WINRT_ASSERT(request->EntityChunkCount == 1); - WINRT_ASSERT(request->pEntityChunks->DataChunkType == HttpDataChunkFromMemory); - auto& data = request->pEntityChunks->FromMemory; - std::string_view body{ static_cast(data.pBuffer), data.BufferLength }; - - for (auto&& entry : WwwFormUrlDecoder(winrt::to_hstring(body))) - { - (void)entry; // TODO - } - } - - // Detours Information - static inline decltype(&::ShellExecuteW) RealShellExecuteW = &::ShellExecuteW; - - // Local server for performing the token exchange - wil::unique_event m_serverShutdownEvent{ wil::EventOptions::None }; - std::thread m_httpServerThread; - HANDLE m_requestQueue = nullptr; - HTTP_SERVER_SESSION_ID m_serverSessionId = 0; - HTTP_URL_GROUP_ID m_urlGroup = 0; - std::uint16_t m_serverPort = 50001; - std::wstring m_serverUrlBase; -}; diff --git a/test/OAuthTests/OAuthTestValues.h b/test/OAuthTests/OAuthTestValues.h new file mode 100644 index 0000000000..f35569343f --- /dev/null +++ b/test/OAuthTests/OAuthTestValues.h @@ -0,0 +1,114 @@ +#pragma once + +#include +#include + +#include +#include + +// The 'client_id' describes the behavior and expectations of our mocked authorization server +// Specifying grant type is required +#define GRANT_TYPE_CODE L"grant=code" +#define GRANT_TYPE_TOKEN L"grant=token" +#define GRANT_TYPE_PASSWORD L"grant=password" +#define GRANT_TYPE_CLIENT L"grant=client" +#define GRANT_TYPE_REFRESH L"grant=refresh" +#define GRANT_TYPE_EXTENSION L"grant=extension" + +// Specifying redirect type is required +#define REDIRECT_TYPE_LOCALHOST "&redirect=localhost" +#define REDIRECT_TYPE_PROTOCOL "&redirect=protocol" +#define REDIRECT_TYPE_INFERRED "&redirect=inferred" + +// 'S256' is the default if not specified +#define PKCE_TYPE_S256 "&pkce=S256" +#define PKCE_TYPE_PLAIN "&pkce=plain" +#define PKCE_TYPE_NONE "&pkce=none" + +// 'none' is the default if not specified +#define SCOPE_TYPE_NONE "&scope=none" +#define SCOPE_TYPE_SINGLE "&scope=single" +#define SCOPE_TYPE_MULTIPLE "&scope=multiple" + +// 'none' is the default if not specified +#define AUTH_TYPE_NONE "&auth=none" +#define AUTH_TYPE_HEADER "&auth=header" + +// 'none' is the default if not specified +#define NO_ERROR_RESPONSE "&error=none" +#define AUTH_ERROR_RESPONSE "&error=auth" +#define TOKEN_ERROR_RESPONSE "&error=token" + +// 'true' is the default if not specified +#define COMPLETE_REQUEST "&complete=true" +#define DONT_COMPLETE_REQUEST "&complete=false" + +// 'false' is the default if not specified +#define NO_ADDITIONAL_PARAMS "&additional_params=false" +#define ADDITIONAL_PARAMS "&additional_params=true" + +// 'false' is the default if not specified +#define NO_AUTH_URL_QUERY_STRING "&query=false" +#define AUTH_URL_QUERY_STRING "&query=true" + +// Constants to validate expectations. The strings are specifically chosen to validate proper escaping of special characters +inline constexpr std::wstring_view error_description = L"This is an error & it contains characters like \"=\""; +inline constexpr std::wstring_view json_escaped_error_description = L"This is an error & it contains characters like \\\"=\\\""; +inline constexpr std::wstring_view error_uri = L"https://contoso.com/errors?foo=bar"; + +inline constexpr std::wstring_view additional_param_key = L"use=key&name=foo"; +inline constexpr std::wstring_view additional_param_value = L"use=value&name=bar"; + +inline constexpr std::wstring_view extension_grant_uri = L"oauth:test:extension"; + +inline constexpr std::wstring_view single_scope = L"foo=bar?"; +inline constexpr std::wstring_view multiple_scope = L"foo=bar? &\"foobar\""; + +inline constexpr std::wstring_view token = L"tacos=yummy&location=\"my tummy\""; +inline constexpr std::wstring_view json_escaped_token = L"tacos=yummy&location=\\\"my tummy\\\""; +inline constexpr std::wstring_view refresh_token_old = L"~!@#$%^&*()_+`-=[]\\{};':\",./<>?-old"; +inline constexpr std::wstring_view refresh_token = L"~!@#$%^&*()_+`-=[]\\{};':\",./<>?"; +inline constexpr std::wstring_view json_escaped_refresh_token = L"~!@#$%^&*()_+`-=[]\\\\{};':\\\",./<>?"; + +struct uri_builder +{ + std::wstring uri; + wchar_t prefix; + + uri_builder(const winrt::Windows::Foundation::Uri& uri, bool useQuery = true) : + uri(uri.RawUri()) + { + if (useQuery) + { + prefix = uri.Query().empty() ? L'?' : '&'; + } + else + { + prefix = '#'; + } + } + + void add(std::wstring_view name, std::wstring_view value) + { + assert(!name.empty() && !value.empty()); + + uri.push_back(prefix); + prefix = L'&'; + uri.append(winrt::Windows::Foundation::Uri::EscapeComponent(name)); + uri.push_back(L'='); + uri.append(winrt::Windows::Foundation::Uri::EscapeComponent(value)); + } + + void add_optional(std::wstring_view name, std::wstring_view value) + { + if (!value.empty()) + { + add(name, value); + } + } + + winrt::Windows::Foundation::Uri get() + { + return winrt::Windows::Foundation::Uri{ uri }; + } +}; diff --git a/test/OAuthTests/OAuthTests.cpp b/test/OAuthTests/OAuthTests.cpp new file mode 100644 index 0000000000..469c91452f --- /dev/null +++ b/test/OAuthTests/OAuthTests.cpp @@ -0,0 +1,1484 @@ + +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "OAuthTestValues.h" + +// NOTE: Thise files don't include verything they need, hence they are here last +#include +#include + +#include // Included last to enable the most features + +using namespace std::literals; +using namespace WEX::Common; +using namespace WEX::Logging; +using namespace WEX::TestExecution; +using namespace winrt::Microsoft::Windows::Security::Authentication::OAuth; +using namespace winrt::Windows::Data::Json; +using namespace winrt::Windows::Foundation; +using namespace winrt::Windows::Foundation::Collections; +using namespace winrt::Windows::Security::Cryptography; +using namespace winrt::Windows::Security::Cryptography::Core; +using namespace winrt::Windows::Storage::Streams; + +EXTERN_C IMAGE_DOS_HEADER __ImageBase; + +struct OAuthTests +{ + BEGIN_TEST_CLASS(OAuthTests) + TEST_CLASS_PROPERTY(L"ThreadingModel", L"MTA") + END_TEST_CLASS() + + TEST_CLASS_SETUP(Setup) + { + Test::Bootstrap::Setup(); + + Test::Packages::WapProj::AddPackage(Test::TAEF::GetDeploymentDir(), L"OAuthTestAppPackage", L".msix"); + + // Detour ShellExecuteW + ::DetourTransactionBegin(); + ::DetourUpdateThread(::GetCurrentThread()); + if (auto err = ::DetourAttach(reinterpret_cast(&RealShellExecuteW), &DetouredShellExecuteW)) + { + Log::Error(WEX::Common::String().Format(L"DetourAttach failed: %d", err)); + ::DetourTransactionAbort(); + return false; + } + ::DetourTransactionCommit(); + + // Initialize the HTTP server we use for token requests + VERIFY_WIN32_SUCCEEDED(::HttpInitialize(HTTPAPI_VERSION_2, HTTP_INITIALIZE_SERVER, nullptr)); + VERIFY_WIN32_SUCCEEDED(::HttpCreateRequestQueue(HTTPAPI_VERSION_2, nullptr, nullptr, 0, &m_requestQueue)); + VERIFY_WIN32_SUCCEEDED(::HttpCreateServerSession(HTTPAPI_VERSION_2, &m_serverSessionId, 0)); + VERIFY_WIN32_SUCCEEDED(::HttpCreateUrlGroup(m_serverSessionId, &m_urlGroup, 0)); + + HTTP_BINDING_INFO bindingInfo = {}; + bindingInfo.Flags.Present = 1; + bindingInfo.RequestQueueHandle = m_requestQueue; + VERIFY_WIN32_SUCCEEDED(::HttpSetUrlGroupProperty(m_urlGroup, HttpServerBindingProperty, &bindingInfo, + static_cast(sizeof(bindingInfo)))); + + // Find an open port; note that ports in the low 50000s are frequently claimed, hence the large iteration bounds + ULONG err = 0; + for (std::uint16_t i = 0; i < 500; ++i) + { + wchar_t buffer[18 + 5 + 1]; + std::swprintf(buffer, std::size(buffer), L"http://127.0.0.1:%d/", m_serverPort); + + err = ::HttpAddUrlToUrlGroup(m_urlGroup, buffer, 0, 0); + if (err == NO_ERROR) + { + m_serverUrlBase = buffer; + break; + } + + ++m_serverPort; + } + + VERIFY_WIN32_SUCCEEDED(err, L"Looking for an open port"); + m_httpServerThread = std::thread([this] { + RunHttpServer(); + }); + + return true; + } + + TEST_CLASS_CLEANUP(Cleanup) + { + Test::Packages::RemovePackage(L"OAuthTestAppPackage_1.0.0.0_" WINDOWSAPPRUNTIME_TEST_PACKAGE_DDLM_ARCHITECTURE L"__8wekyb3d8bbwe"); + + // Tear down the HTTP server + m_serverShutdownEvent.SetEvent(); + m_httpServerThread.join(); + + if (m_urlGroup) + { + ::HttpCloseUrlGroup(m_urlGroup); + m_urlGroup = 0; + } + + if (m_serverSessionId) + { + ::HttpCloseServerSession(m_serverSessionId); + m_serverSessionId = 0; + } + + if (m_requestQueue) + { + ::HttpCloseRequestQueue(m_requestQueue); + m_requestQueue = nullptr; + } + + ::HttpTerminate(HTTP_INITIALIZE_SERVER, nullptr); + + // Clean up our detours + ::DetourTransactionBegin(); + ::DetourUpdateThread(::GetCurrentThread()); + ::DetourDetach(reinterpret_cast(&RealShellExecuteW), &DetouredShellExecuteW); + ::DetourTransactionCommit(); + + Test::Bootstrap::Cleanup(); + + return true; + } + + template + static void WaitWithTimeout(const IAsyncOperation& op, AsyncStatus expectedStatus) + { + wil::unique_event event(wil::EventOptions::None); + op.Completed([event = event.get()](const IAsyncOperation&, AsyncStatus) { + ::SetEvent(event); + }); + + // 10 seconds is beyond + if (::WaitForSingleObject(event.get(), 1000) == WAIT_OBJECT_0) + { + VERIFY_ARE_EQUAL(expectedStatus, op.Status()); + return; + } + + Log::Warning(L"Timed out waiting for IAsyncOperation to complete; cancelling..."); + op.Cancel(); + + // Cancel should cause the operation to complete with the cancellation + if (::WaitForSingleObject(event.get(), 1000) != WAIT_OBJECT_0) + { + // Lambda holds a reference to the event. Best just to leak it here + Log::Warning(L"Failed to cancel IAsyncOperation; leaking event"); + event.release(); + } + + VERIFY_FAIL(L"IAsyncOperation did not complete in a reasonable amount of time"); + } + + template + static void VerifyErrorNull(const ErrorT& error) + { + if (error) + { + Log::Error(WEX::Common::String().Format(L"Error object expected to be null! Message: %ls", + error.ErrorDescription().c_str())); + } + VERIFY_IS_NULL(error); + } + + AuthResponse InitiateAndWaitForSuccessfulAuthResponse(const AuthRequestParams& params) + { + auto op = AuthManager::InitiateAuthRequestAsync(Uri{ auth_url }, params); + WaitWithTimeout(op, AsyncStatus::Completed); + + auto result = op.GetResults(); + VerifyErrorNull(result.Failure()); + + auto response = result.Response(); + VERIFY_IS_NOT_NULL(response); + VERIFY_ARE_EQUAL(params.State(), response.State()); + + return response; + } + + TokenResponse RequestTokenAndWaitForSuccessfulResponse(const TokenRequestParams& params, const ClientAuthentication& auth = { nullptr }) + { + IAsyncOperation op{ nullptr }; + if (auth) + { + op = AuthManager::RequestTokenAsync(Uri{ m_serverUrlBase + L"token" }, params, auth); + } + else + { + op = AuthManager::RequestTokenAsync(Uri{ m_serverUrlBase + L"token" }, params); + } + WaitWithTimeout(op, AsyncStatus::Completed); + + auto result = op.GetResults(); + VerifyErrorNull(result.Failure()); + + auto response = result.Response(); + VERIFY_IS_NOT_NULL(response); + VERIFY_ARE_EQUAL(token, response.AccessToken()); + VERIFY_ARE_EQUAL(L"Bearer", response.TokenType()); + VERIFY_ARE_EQUAL(3600, response.ExpiresIn()); + VERIFY_ARE_EQUAL(refresh_token, response.RefreshToken()); + VERIFY_ARE_EQUAL(L"all", response.Scope()); + + return response; + } + + static inline constexpr std::wstring_view auth_url = L"http://oauthtests.com/oauth"sv; + + // Redirect URIs + static inline constexpr std::wstring_view localhost_redirect_uri = L"http://127.0.0.1/oauth"sv; + static inline constexpr std::wstring_view protocol_redirect_uri = L"oauthtestapp:oauth"sv; + + void DoEndToEndAuthCodeTest(const AuthRequestParams& requestParams) + { + auto authResponse = InitiateAndWaitForSuccessfulAuthResponse(requestParams); + VERIFY_IS_FALSE(authResponse.Code().empty()); + + auto tokenParams = TokenRequestParams::CreateForAuthorizationCodeRequest(authResponse); + RequestTokenAndWaitForSuccessfulResponse(tokenParams); + } + + void DoBasicEndToEndAuthCodeTest(std::wstring_view clientId, std::wstring_view redirectUri) + { + auto requestParams = AuthRequestParams::CreateForAuthorizationCodeRequest(clientId, Uri{ redirectUri }); + DoEndToEndAuthCodeTest(requestParams); + } + + TEST_METHOD(Localhost_AuthorizationCode_BasicEndToEnd) + { + static constexpr std::wstring_view client_id = GRANT_TYPE_CODE REDIRECT_TYPE_LOCALHOST; + DoBasicEndToEndAuthCodeTest(client_id, localhost_redirect_uri); + } + + TEST_METHOD(Protocol_AuthorizationCode_BasicEndToEnd) + { + static constexpr std::wstring_view client_id = GRANT_TYPE_CODE REDIRECT_TYPE_PROTOCOL; + DoBasicEndToEndAuthCodeTest(client_id, protocol_redirect_uri); + } + + TEST_METHOD(Implicit_BasicEndToEnd) + { + // NOTE: Responses for implicit requests are communicated via the URI fragment, meaning that in practice, an + // application would need code running in the browser. This effectively requires running a simple HTTP server, + // at which point the application can just recover the full URI for itself and does not need to use protocol + // activation, so there's no major reason to test it. That code path would be virtually identical to the auth + // code scenarios, so there's even more reason to avoid exploding out the number of test cases + static constexpr std::wstring_view client_id = GRANT_TYPE_TOKEN REDIRECT_TYPE_LOCALHOST PKCE_TYPE_NONE; + auto requestParams = AuthRequestParams::CreateForImplicitRequest(client_id, Uri{ localhost_redirect_uri }); + auto requestResponse = InitiateAndWaitForSuccessfulAuthResponse(requestParams); + + VERIFY_ARE_EQUAL(token, requestResponse.AccessToken()); + VERIFY_ARE_EQUAL(L"Bearer", requestResponse.TokenType()); + VERIFY_ARE_EQUAL(L"3600", requestResponse.ExpiresIn()); + VERIFY_ARE_EQUAL(L"all", requestResponse.Scope()); + } + + void DoChallengeMethodPlainTest(std::wstring_view clientId, std::wstring_view redirectUri) + { + auto requestParams = AuthRequestParams::CreateForAuthorizationCodeRequest(clientId, Uri{ redirectUri }); + requestParams.CodeChallengeMethod(CodeChallengeMethodKind::Plain); + DoEndToEndAuthCodeTest(requestParams); + } + + TEST_METHOD(Localhost_ChallengeMethodPlain) + { + static constexpr std::wstring_view client_id = GRANT_TYPE_CODE REDIRECT_TYPE_LOCALHOST PKCE_TYPE_PLAIN; + DoChallengeMethodPlainTest(client_id, localhost_redirect_uri); + } + + TEST_METHOD(Protocol_ChallengeMethodPlain) + { + static constexpr std::wstring_view client_id = GRANT_TYPE_CODE REDIRECT_TYPE_PROTOCOL PKCE_TYPE_PLAIN; + DoChallengeMethodPlainTest(client_id, protocol_redirect_uri); + } + + void DoChallengeMethodNoneTest(std::wstring_view clientId, std::wstring_view redirectUri) + { + auto requestParams = AuthRequestParams::CreateForAuthorizationCodeRequest(clientId, Uri{ redirectUri }); + requestParams.CodeChallengeMethod(CodeChallengeMethodKind::None); + DoEndToEndAuthCodeTest(requestParams); + } + + TEST_METHOD(Localhost_ChallengeMethodNone) + { + static constexpr std::wstring_view client_id = GRANT_TYPE_CODE REDIRECT_TYPE_LOCALHOST PKCE_TYPE_NONE; + DoChallengeMethodNoneTest(client_id, localhost_redirect_uri); + } + + TEST_METHOD(Protocol_ChallengeMethodNone) + { + static constexpr std::wstring_view client_id = GRANT_TYPE_CODE REDIRECT_TYPE_PROTOCOL PKCE_TYPE_NONE; + DoChallengeMethodNoneTest(client_id, protocol_redirect_uri); + } + + void DoCustomStateAuthCodeTest(std::wstring_view clientId, std::wstring_view redirectUri) + { + auto runTest = [&](std::wstring_view state) { + auto requestParams = AuthRequestParams::CreateForAuthorizationCodeRequest(clientId, Uri{ redirectUri }); + requestParams.State(state); + InitiateAndWaitForSuccessfulAuthResponse(requestParams); + }; + + // AES needs a key of size 16, 24, or 32 bytes. Test with states between each of these sizes to ensure we pad + // correctly + runTest(L"=?-_/"); // 5 bytes + runTest(L"!@#$%^&*()-=_+/\\"); // 16 bytes + runTest(L"!@#$%^&*()-=_+[]{};/"); // 20 bytes + runTest(L"!@#$%^&*()_+-=[]{};',./~"); // 24 bytes + runTest(L"!@#$%^&*()_+-=[]{};',./<>?`~"); // 28 bytes + runTest(L"!@#$%^&*()_+-=[]{};',./<>?:\"`~|\\"); // 32 bytes + runTest(L"!@#$%^&*()_+-=[]{};',./<>?:\"`~|\\abc123"); // 38 bytes + } + + TEST_METHOD(Localhost_AuthorizationCode_CustomState) + { + static constexpr std::wstring_view client_id = GRANT_TYPE_CODE REDIRECT_TYPE_LOCALHOST; + DoCustomStateAuthCodeTest(client_id, localhost_redirect_uri); + } + + TEST_METHOD(Protocol_AuthorizationCode_CustomState) + { + static constexpr std::wstring_view client_id = GRANT_TYPE_CODE REDIRECT_TYPE_PROTOCOL; + DoCustomStateAuthCodeTest(client_id, protocol_redirect_uri); + } + + TEST_METHOD(AuthorizationCode_InferredRedirectUri) + { + static constexpr std::wstring_view client_id = GRANT_TYPE_CODE REDIRECT_TYPE_INFERRED; + auto requestParams = AuthRequestParams::CreateForAuthorizationCodeRequest(client_id); + auto authResponse = InitiateAndWaitForSuccessfulAuthResponse(requestParams); + + auto tokenParams = TokenRequestParams::CreateForAuthorizationCodeRequest(authResponse); + RequestTokenAndWaitForSuccessfulResponse(tokenParams); + } + + void DoImplicitCustomScopeTest(std::wstring_view clientId, std::wstring_view scope) + { + auto requestParams = AuthRequestParams::CreateForImplicitRequest(clientId, Uri{ localhost_redirect_uri }); + requestParams.Scope(scope); + + auto authResponse = InitiateAndWaitForSuccessfulAuthResponse(requestParams); + VERIFY_ARE_EQUAL(token, authResponse.AccessToken()); + VERIFY_ARE_EQUAL(scope, authResponse.Scope()); + } + + TEST_METHOD(Implicit_SingleCustomScope) + { + static constexpr std::wstring_view client_id = GRANT_TYPE_TOKEN REDIRECT_TYPE_LOCALHOST PKCE_TYPE_NONE SCOPE_TYPE_SINGLE; + DoImplicitCustomScopeTest(client_id, single_scope); + } + + TEST_METHOD(Implicit_MultipleCustomScope) + { + static constexpr std::wstring_view client_id = GRANT_TYPE_TOKEN REDIRECT_TYPE_LOCALHOST PKCE_TYPE_NONE SCOPE_TYPE_MULTIPLE; + DoImplicitCustomScopeTest(client_id, multiple_scope); + } + + TEST_METHOD(AuthRequestPreserveQueryString) + { + static constexpr std::wstring_view client_id = GRANT_TYPE_CODE REDIRECT_TYPE_LOCALHOST AUTH_URL_QUERY_STRING; + auto requestParams = AuthRequestParams::CreateForAuthorizationCodeRequest(client_id, Uri{ localhost_redirect_uri }); + auto requestAsyncOp = AuthManager::InitiateAuthRequestAsync(Uri{ std::wstring(auth_url) + L"?foo=bar" }, requestParams); + WaitWithTimeout(requestAsyncOp, AsyncStatus::Completed); + + auto authResponse = requestAsyncOp.GetResults().Response(); + VERIFY_IS_NOT_NULL(authResponse); + } + + TEST_METHOD(AuthorizationCodeWithClientAuth) + { + // NOTE: This is testing client auth, which is a token request only thing, hence only using a single redirection type + static constexpr std::wstring_view client_id = GRANT_TYPE_CODE REDIRECT_TYPE_LOCALHOST AUTH_TYPE_HEADER; + auto requestParams = AuthRequestParams::CreateForAuthorizationCodeRequest(client_id, Uri{ localhost_redirect_uri }); + auto authResponse = InitiateAndWaitForSuccessfulAuthResponse(requestParams); + + auto tokenParams = TokenRequestParams::CreateForAuthorizationCodeRequest(authResponse); + auto auth = ClientAuthentication::CreateForBasicAuthorization(client_id, L"password"); + auto tokenAsyncOp = AuthManager::RequestTokenAsync(Uri{ m_serverUrlBase + L"token" }, tokenParams, auth); + WaitWithTimeout(tokenAsyncOp, AsyncStatus::Completed); + + auto tokenResult = tokenAsyncOp.GetResults(); + auto tokenResponse = tokenResult.Response(); + VerifyErrorNull(tokenResult.Failure()); + VERIFY_IS_NOT_NULL(tokenResponse); + VERIFY_ARE_EQUAL(token, tokenResponse.AccessToken()); + } + + TEST_METHOD(UserCredentialsTokenRequest) + { + static constexpr std::wstring_view client_id = GRANT_TYPE_PASSWORD AUTH_TYPE_HEADER; + auto tokenParams = TokenRequestParams::CreateForResourceOwnerPasswordCredentials(L"username", L"password"); + auto auth = ClientAuthentication::CreateForBasicAuthorization(client_id, L"password"); + RequestTokenAndWaitForSuccessfulResponse(tokenParams, auth); + } + + TEST_METHOD(ClientCredentialsTokenRequest) + { + static constexpr std::wstring_view client_id = GRANT_TYPE_CLIENT AUTH_TYPE_HEADER; + auto tokenParams = TokenRequestParams::CreateForClientCredentials(); + auto auth = ClientAuthentication::CreateForBasicAuthorization(client_id, L"password"); + RequestTokenAndWaitForSuccessfulResponse(tokenParams, auth); + } + + TEST_METHOD(RefreshTokenRequest) + { + static constexpr std::wstring_view client_id = GRANT_TYPE_REFRESH AUTH_TYPE_HEADER; + auto tokenParams = TokenRequestParams::CreateForRefreshToken(refresh_token_old); + auto auth = ClientAuthentication::CreateForBasicAuthorization(client_id, L"password"); + RequestTokenAndWaitForSuccessfulResponse(tokenParams, auth); + } + + TEST_METHOD(ExtensionTokenRequest) + { + static constexpr std::wstring_view client_id = GRANT_TYPE_EXTENSION AUTH_TYPE_HEADER; + auto tokenParams = TokenRequestParams::CreateForExtension(Uri{ extension_grant_uri }); + auto auth = ClientAuthentication::CreateForBasicAuthorization(client_id, L"password"); + RequestTokenAndWaitForSuccessfulResponse(tokenParams, auth); + } + + void DoAuthCodeAuthRequestErrorResponseTest(std::wstring_view clientId, std::wstring_view redirectUri) + { + auto requestParams = AuthRequestParams::CreateForAuthorizationCodeRequest(clientId, Uri{ redirectUri }); + auto requestAsyncOp = AuthManager::InitiateAuthRequestAsync(Uri{ auth_url }, requestParams); + WaitWithTimeout(requestAsyncOp, AsyncStatus::Completed); + + auto requestResult = requestAsyncOp.GetResults(); + auto authError = requestResult.Failure(); + VERIFY_IS_NULL(requestResult.Response()); + VERIFY_IS_NOT_NULL(authError); + auto additionalParams = authError.AdditionalParams(); + VERIFY_IS_NOT_NULL(additionalParams); + + VERIFY_ARE_EQUAL(requestParams.State(), authError.State()); + VERIFY_ARE_EQUAL(L"server_error", authError.Error()); + VERIFY_ARE_EQUAL(error_description, authError.ErrorDescription()); + VERIFY_IS_NOT_NULL(authError.ErrorUri()); + VERIFY_ARE_EQUAL(error_uri, authError.ErrorUri().RawUri()); + VERIFY_IS_TRUE(additionalParams.HasKey(additional_param_key)); + VERIFY_ARE_EQUAL(additional_param_value, additionalParams.Lookup(additional_param_key)); + } + + TEST_METHOD(Localhost_AuthRequestErrorResponse) + { + static constexpr std::wstring_view client_id = GRANT_TYPE_CODE REDIRECT_TYPE_LOCALHOST AUTH_ERROR_RESPONSE; + DoAuthCodeAuthRequestErrorResponseTest(client_id, localhost_redirect_uri); + } + + TEST_METHOD(Protocol_AuthRequestErrorResponse) + { + static constexpr std::wstring_view client_id = GRANT_TYPE_CODE REDIRECT_TYPE_PROTOCOL AUTH_ERROR_RESPONSE; + DoAuthCodeAuthRequestErrorResponseTest(client_id, protocol_redirect_uri); + } + + TEST_METHOD(TokenRequestErrorResponse) + { + static constexpr std::wstring_view client_id = GRANT_TYPE_PASSWORD TOKEN_ERROR_RESPONSE; + auto tokenParams = TokenRequestParams::CreateForResourceOwnerPasswordCredentials(L"username", L"password"); + auto auth = ClientAuthentication::CreateForBasicAuthorization(client_id, L"password"); + auto tokenAsyncOp = AuthManager::RequestTokenAsync(Uri{ m_serverUrlBase + L"token" }, tokenParams, auth); + WaitWithTimeout(tokenAsyncOp, AsyncStatus::Completed); + + auto tokenResult = tokenAsyncOp.GetResults(); + auto tokenError = tokenResult.Failure(); + VERIFY_IS_NULL(tokenResult.Response()); + VERIFY_IS_NOT_NULL(tokenError); + auto additionalParams = tokenError.AdditionalParams(); + VERIFY_IS_NOT_NULL(additionalParams); + + VERIFY_ARE_EQUAL(E_FAIL, tokenError.ErrorCode().value); + VERIFY_ARE_EQUAL(L"server_error", tokenError.Error()); + VERIFY_ARE_EQUAL(error_description, tokenError.ErrorDescription()); + VERIFY_IS_NOT_NULL(tokenError.ErrorUri()); + VERIFY_ARE_EQUAL(error_uri, tokenError.ErrorUri().RawUri()); + + VERIFY_IS_TRUE(additionalParams.HasKey(additional_param_key)); + auto jsonValue = additionalParams.Lookup(additional_param_key); + VERIFY_ARE_EQUAL(JsonValueType::String, jsonValue.ValueType()); + VERIFY_ARE_EQUAL(additional_param_value, jsonValue.GetString()); + } + + TEST_METHOD(AdditionalParams) + { + static constexpr std::wstring_view client_id = GRANT_TYPE_CODE REDIRECT_TYPE_LOCALHOST ADDITIONAL_PARAMS; + auto requestParams = AuthRequestParams::CreateForAuthorizationCodeRequest(client_id, Uri{ localhost_redirect_uri }); + auto additionalRequestParams = requestParams.AdditionalParams(); + additionalRequestParams.Insert(additional_param_key, additional_param_value); + auto authResponse = InitiateAndWaitForSuccessfulAuthResponse(requestParams); + + auto tokenParams = TokenRequestParams::CreateForAuthorizationCodeRequest(authResponse); + auto additionalTokenParams = tokenParams.AdditionalParams(); + additionalTokenParams.Insert(additional_param_key, additional_param_value); + + auto tokenAsyncOp = AuthManager::RequestTokenAsync(Uri{ m_serverUrlBase + L"token" }, tokenParams); + WaitWithTimeout(tokenAsyncOp, AsyncStatus::Completed); + + auto tokenResult = tokenAsyncOp.GetResults(); + auto tokenResponse = tokenResult.Response(); + VerifyErrorNull(tokenResult.Failure()); + VERIFY_IS_NOT_NULL(tokenResponse); + } + + TEST_METHOD(CancelAuthRequest) + { + // NOTE: Grant type and redirection URI are irrelevant for testing cancellation, hence we only test this once + static constexpr std::wstring_view client_id = GRANT_TYPE_CODE REDIRECT_TYPE_LOCALHOST DONT_COMPLETE_REQUEST; + auto requestParams = AuthRequestParams::CreateForAuthorizationCodeRequest(client_id, Uri{ localhost_redirect_uri }); + auto requestAsyncOp = AuthManager::InitiateAuthRequestAsync(Uri{ auth_url }, requestParams); + VERIFY_ARE_EQUAL(AsyncStatus::Started, requestAsyncOp.Status()); + + requestAsyncOp.Cancel(); + WaitWithTimeout(requestAsyncOp, AsyncStatus::Canceled); + } + + TEST_METHOD(CompleteAuthRequestTwice) + { + // NOTE: Grant type is irrelevant for this test and redirect URI is only relevant for the initial completion, + // meaning it too is irrelavent for this test + static constexpr std::wstring_view client_id = GRANT_TYPE_CODE REDIRECT_TYPE_LOCALHOST; + auto requestParams = AuthRequestParams::CreateForAuthorizationCodeRequest(client_id, Uri{ localhost_redirect_uri }); + auto requestAsyncOp = AuthManager::InitiateAuthRequestAsync(Uri{ auth_url }, requestParams); + WaitWithTimeout(requestAsyncOp, AsyncStatus::Completed); + + auto requestResult = requestAsyncOp.GetResults(); + auto requestResponseUri = requestResult.ResponseUri(); + VERIFY_IS_NOT_NULL(requestResponseUri); + VERIFY_IS_FALSE(AuthManager::CompleteAuthRequest(requestResponseUri)); + } + + TEST_METHOD(CompleteInvalidState) + { + VERIFY_IS_FALSE(AuthManager::CompleteAuthRequest(Uri{ L"unknown-protocol:" })); // No query parameters at all + VERIFY_IS_FALSE(AuthManager::CompleteAuthRequest(Uri{ L"http://127.0.0.1/oauth?code=abc123" })); // Missing state + VERIFY_IS_FALSE(AuthManager::CompleteAuthRequest(Uri{ L"oauthtestapp:oauth?code=abc&state=invalid" })); + VERIFY_IS_FALSE(AuthManager::CompleteAuthRequest(Uri{ L"http://127.0.0.1/oauth?code=abc123&state=invalid"})); + } + + // Detoured Functions + static HINSTANCE __stdcall DetouredShellExecuteW(_In_opt_ HWND hwnd, _In_opt_ LPCWSTR operation, _In_ LPCWSTR file, + _In_opt_ LPCWSTR params, _In_opt_ LPCWSTR directory, _In_ INT showCmd) try + { + std::wstring_view fileStr = file; + if (fileStr.substr(0, auth_url.size()) == auth_url) + { + winrt::hstring errorString; + winrt::hstring errorMessage; + auto assignInvalidRequestError = [&](std::wstring_view msg) { + if (errorString.empty()) + { + errorString = L"invalid_request"; + errorMessage = msg; + } + }; + auto assignMismatchedArgsError = [&](std::wstring_view name, std::wstring_view expected, std::wstring_view actual) { + if (errorString.empty()) + { + std::wstring msg = L"Unexpected value for '"; + msg.append(name); + msg += L"'. Expected '"; + msg.append(expected); + msg += L"' but got '"; + msg.append(actual); + msg += L"'"; + errorString = L"invalid_request"; + errorMessage = msg; + } + }; + + // There's no point in launching the browser and trying to fake an authorization flow as that would do + // nothing to test the API. Instead, perform the logic of the browser and authorization flow here in-proc + winrt::hstring responseType; + winrt::hstring clientId; + Uri redirectUri{ nullptr }; + winrt::hstring scope; + winrt::hstring state; + winrt::hstring codeChallenge; + winrt::hstring codeChallengeMethod; + winrt::hstring additionalParam; + winrt::hstring foo; + for (auto&& entry : Uri(fileStr).QueryParsed()) + { + auto name = entry.Name(); + auto value = entry.Value(); + if (name == L"response_type") + { + responseType = value; + } + else if (name == L"client_id") + { + clientId = value; + } + else if (name == L"redirect_uri") + { + redirectUri = Uri{ value }; + } + else if (name == L"scope") + { + scope = value; + } + else if (name == L"state") + { + state = value; + } + else if (name == L"code_challenge") + { + codeChallenge = value; + } + else if (name == L"code_challenge_method") + { + codeChallengeMethod = value; + } + else if (name == additional_param_key) + { + additionalParam = value; + } + else if (name == L"foo") + { + foo = value; + } + else + { + assignInvalidRequestError(L"Unrecognized query parameter '"s + name + L"'"); + } + } + + // Some behavior is encoded in the client id + winrt::hstring expectedGrantType; + winrt::hstring expectedRedirectType; + winrt::hstring expectedPkceType = L"S256"; + winrt::hstring expectedScopeType = L"none"; + winrt::hstring expectedError = L"none"; + bool completeRequest = true; + bool expectAdditionalParams = false; + bool expectAuthUrlQueryString = false; + for (auto&& entry : WwwFormUrlDecoder{ clientId }) + { + auto name = entry.Name(); + auto value = entry.Value(); + if (name == L"grant") + { + expectedGrantType = value; + } + else if (name == L"redirect") + { + expectedRedirectType = value; + } + else if (name == L"pkce") + { + expectedPkceType = value; + } + else if (name == L"scope") + { + expectedScopeType = value; + } + else if (name == L"error") + { + expectedError = value; + } + else if (name == L"complete") + { + completeRequest = (value == L"true"); + } + else if (name == L"additional_params") + { + expectAdditionalParams = (value == L"true"); + } + else if (name == L"query") + { + expectAuthUrlQueryString = (value == L"true"); + } + // Ignore other values as these are specific to the token request + } + + if (state.empty()) + { + // If no state is provided, we'll be unable to correlate the response to the request. The best we can + // really do here is to fail the launch which will fail the test early and reliably + Log::Error(L"No 'state' value provided in the URI"); + ::SetLastError(ERROR_INVALID_PARAMETER); + return nullptr; + } + else if (responseType.empty()) + { + assignInvalidRequestError(L"Missing 'response_type'"); + } + else if (clientId.empty()) + { + assignInvalidRequestError(L"Missing 'client_id'"); + } + else if (expectedGrantType.empty()) + { + assignInvalidRequestError(L"Client id is missing the expected grant type"); + } + else if (expectedRedirectType.empty()) + { + assignInvalidRequestError(L"Client id is missing the expected redirect type"); + } + + if (!redirectUri) + { + if (expectedRedirectType == L"inferred") + { + redirectUri = Uri{ localhost_redirect_uri }; + } + else + { + // If we aren't given a URI and we didn't expect to not be given a URI, then we can't reliably + // return back an error response + Log::Error(L"No 'redirect_uri' value provided in the URI"); + ::SetLastError(ERROR_INVALID_PARAMETER); + return nullptr; + } + } + else if (expectedRedirectType == L"inferred") + { + assignMismatchedArgsError(L"redirect_uri", L"", redirectUri.RawUri()); + } + + if (responseType != expectedGrantType) + { + assignMismatchedArgsError(L"response_type", expectedGrantType, responseType); + } + + auto expectedUri = (expectedRedirectType == L"protocol") ? protocol_redirect_uri : localhost_redirect_uri; + if (redirectUri.RawUri() != expectedUri) + { + assignMismatchedArgsError(L"redirect_uri", expectedUri, redirectUri.RawUri()); + } + + if (expectedPkceType == L"none") + { + if (!codeChallengeMethod.empty()) + { + assignMismatchedArgsError(L"code_challenge_method", L"", codeChallengeMethod); + } + } + else if (expectedPkceType != codeChallengeMethod) + { + assignMismatchedArgsError(L"code_challenge_method", expectedPkceType, codeChallengeMethod); + } + + if (expectedScopeType == L"none") + { + if (!scope.empty()) + { + assignMismatchedArgsError(L"scope", L"", scope); + } + } + else if (scope.empty()) + { + assignInvalidRequestError(L"Expected a 'scope' parameter, but none provided"); + } + else if (expectedScopeType == L"single") + { + if (scope != single_scope) + { + assignMismatchedArgsError(L"scope", single_scope, scope); + } + } + else if (expectedScopeType == L"multiple") + { + if (scope != multiple_scope) + { + assignMismatchedArgsError(L"scope", multiple_scope, scope); + } + } + + if (expectAdditionalParams) + { + if (additionalParam.empty()) + { + assignInvalidRequestError(L"Expected additional params, but none provided"); + } + else if (additionalParam != additional_param_value) + { + assignMismatchedArgsError(L"additional param", additional_param_value, additionalParam); + } + } + else if (!additionalParam.empty()) + { + assignInvalidRequestError(L"Expected no additional params, but one was provided"); + } + + if (!expectAuthUrlQueryString) + { + if (!foo.empty()) + { + assignInvalidRequestError(L"Query parameter 'foo' was unexpected"); + } + } + else if (foo != L"bar") + { + assignMismatchedArgsError(L"foo", L"bar", foo); + } + + Uri responseUri{ nullptr }; + if (expectedError == L"auth") + { + uri_builder builder(redirectUri, responseType != L"token"); + builder.add(L"state", state); + builder.add(L"error", L"server_error"); + builder.add(L"error_description", error_description); + builder.add(L"error_uri", error_uri); + builder.add(additional_param_key, additional_param_value); + responseUri = builder.get(); + } + else if (responseType == L"code") + { + // For simplicity, encode the client id and PKCE info in the code + std::wstring code = L"client="; + code += Uri::EscapeComponent(clientId); + if (codeChallengeMethod.empty()) + { + code += L"&challenge_method=none"; + } + else + { + code += L"&challenge_method="; + code += codeChallengeMethod; + code += L"&challenge="; + code += codeChallenge; + } + + // NOTE: The 'scope' should be empty, but we should never indicate an expected 'scope' other than 'none' + // for tests that use the auth code grant type + + uri_builder builder{ redirectUri }; + builder.add(L"code", code); + builder.add(L"state", state); + responseUri = builder.get(); + } + else if (responseType == L"token") + { + if (!codeChallengeMethod.empty()) + { + assignInvalidRequestError(L"Use of PKCE is not valid for implicit requests"); + } + + uri_builder builder{ redirectUri, false }; + builder.add(L"state", state); + builder.add(L"access_token", token); + builder.add(L"token_type", L"Bearer"); + builder.add(L"expires_in", L"3600"); + if (scope.empty()) + { + builder.add(L"scope", L"all"); + } + else + { + builder.add(L"scope", scope); + } + + responseUri = builder.get(); + } + else + { + assignInvalidRequestError(L"Unknown response type '"s + responseType + L"'"); + } + + if (!errorString.empty()) + { + // NOTE: We may have created a response URI already, in which case we want to overwrite it here + uri_builder builder(redirectUri, responseType != L"token"); + builder.add(L"state", state); + builder.add(L"error", errorString); + builder.add_optional(L"error_description", errorMessage); + responseUri = builder.get(); + } + + if (responseUri.SchemeName() != L"http") + { + // Protocol activation + return RealShellExecuteW(hwnd, L"open", responseUri.RawUri().c_str(), nullptr, nullptr, SW_SHOWDEFAULT); + } + + // Simulating a localhost server. This would give the response back in-proc so we can just go ahead and + // do that directly. Note that we do this in the same callstack as that will test more interesting code + // paths. TODO: Async completion as a parameter? Or just let protocol activation test that path + if (completeRequest && !AuthManager::CompleteAuthRequest(responseUri)) + { + Log::Warning(L"Failed to complete auth request"); + } + + return reinterpret_cast(42); // Value doesn't really matter; must be greater than 32 + } + + // Not intercepting. Let this "fall through" to the implementation + return RealShellExecuteW(hwnd, operation, file, params, directory, showCmd); + } + catch (...) + { + ::SetLastError(ERROR_FILE_NOT_FOUND); + return reinterpret_cast(ERROR_FILE_NOT_FOUND); + } + + // HTTP Server Thread Callback + void RunHttpServer() + { + wil::unique_event event{ wil::EventOptions::None }; + OVERLAPPED overlapped = {}; + overlapped.hEvent = event.get(); + + ULONG bufferSize = 0x1000; // 4 KB + auto buffer = std::make_unique(bufferSize); + auto request = reinterpret_cast(buffer.get()); + while (true) + { + auto err = ::HttpReceiveHttpRequest(m_requestQueue, HTTP_NULL_ID, HTTP_RECEIVE_REQUEST_FLAG_COPY_BODY, + request, bufferSize, nullptr, &overlapped); + if (err == ERROR_IO_PENDING) + { + // Wait for either shutdown or a request to come in + HANDLE handles[] = { event.get(), m_serverShutdownEvent.get() }; + auto waitResult = ::WaitForMultipleObjects(2, handles, false, INFINITE); + if (waitResult == (WAIT_OBJECT_0 + 1)) + { + // Shutdown + ::CancelIo(m_requestQueue); + break; + } + else if (waitResult != WAIT_OBJECT_0) + { + Log::Warning(WEX::Common::String().Format( + L"WaitForMultipleObjects failed in the HTTP server thread: %d", ::GetLastError())); + ::CancelIo(m_requestQueue); + break; + } + } + + // We have a request; we'll block here until we have all data, if needed + DWORD bytes; + ::GetOverlappedResult(m_requestQueue, &overlapped, &bytes, false); + err = ::GetLastError(); + if (err == ERROR_MORE_DATA) + { + bufferSize = bytes; + buffer = std::make_unique(bufferSize); + request = reinterpret_cast(buffer.get()); + err = ::HttpReceiveHttpRequest(m_requestQueue, request->RequestId, HTTP_RECEIVE_REQUEST_FLAG_COPY_BODY, + request, bufferSize, &bytes, nullptr); + } + + if (err == ERROR_CONNECTION_INVALID) + { + // Connection corrupted by peer + continue; + } + else if (err != ERROR_SUCCESS) + { + Log::Warning(WEX::Common::String().Format(L"HttpReceiveHttpRequest failed: %d", err)); + break; + } + + switch (request->Verb) + { + case HttpVerbPOST: + HandlePostRequest(request); + break; + + default: + Log::Warning(L"Received an HTTP request with an unexpected verb"); + break; + } + } + } + + void HandlePostRequest(HTTP_REQUEST* request) + { + std::string body; + for (USHORT i = 0; i < request->EntityChunkCount; ++i) + { + auto& chunk = request->pEntityChunks[i]; + WINRT_ASSERT(chunk.DataChunkType == HttpDataChunkFromMemory); + auto& data = chunk.FromMemory; + body.append(static_cast(data.pBuffer), data.BufferLength); + } + + if (request->Flags & HTTP_REQUEST_FLAG_MORE_ENTITY_BODY_EXISTS) + { + ULONG bufferLength = 2048; + auto buffer = std::make_unique(bufferLength); + while (true) + { + ULONG bytes = 0; + auto result = ::HttpReceiveRequestEntityBody(m_requestQueue, request->RequestId, 0, buffer.get(), + bufferLength, &bytes, nullptr); + if ((result == NO_ERROR) || (result == ERROR_HANDLE_EOF)) + { + body.append(buffer.get(), bytes); + } + else + { + Log::Warning(WEX::Common::String().Format(L"HttpReceiveRequestEntityBody failed: %d", result)); + return; // TODO: Should we send a response here? Getting an error probably means we shouldn't? + } + + if (result == ERROR_HANDLE_EOF) break; + } + } + + winrt::hstring errorString; + winrt::hstring errorMessage; + auto assignInvalidRequestError = [&](std::wstring_view msg) { + if (errorString.empty()) + { + errorString = L"invalid_request"; + errorMessage = msg; + } + }; + auto assignMismatchedArgsError = [&](std::wstring_view name, std::wstring_view expected, std::wstring_view actual) { + if (errorString.empty()) + { + std::wstring msg = L"Unexpected value for '"; + msg.append(name); + msg += L"'. Expected '"; + msg.append(expected); + msg += L"' but got '"; + msg.append(actual); + msg += L"'"; + errorString = L"invalid_request"; + errorMessage = msg; + } + }; + + winrt::hstring grantType; + winrt::hstring code; + Uri redirectUri{ nullptr }; + winrt::hstring clientId; + winrt::hstring codeVerifier; + winrt::hstring username; + winrt::hstring password; + winrt::hstring scope; + winrt::hstring refreshToken; + winrt::hstring additionalParam; + for (auto&& entry : WwwFormUrlDecoder(winrt::to_hstring(body))) + { + auto name = entry.Name(); + auto value = entry.Value(); + if (name == L"grant_type") + { + grantType = value; + } + else if (name == L"code") + { + code = value; + } + else if (name == L"redirect_uri") + { + redirectUri = Uri{ value }; + } + else if (name == L"client_id") + { + clientId = value; + } + else if (name == L"code_verifier") + { + codeVerifier = value; + } + else if (name == L"username") + { + username = value; + } + else if (name == L"password") + { + password = value; + } + else if (name == L"scope") + { + scope = value; + } + else if (name == L"refresh_token") + { + refreshToken = value; + } + else if (name == additional_param_key) + { + additionalParam = value; + } + else + { + assignInvalidRequestError(L"Unrecognized query parameter '"s + name + L"'"); + } + } + + auto& authHeader = request->Headers.KnownHeaders[HttpHeaderAuthorization]; + if (authHeader.RawValueLength > 0) + { + // Should be of the form ' ' + std::string_view authHeaderStr(authHeader.pRawValue, authHeader.RawValueLength); + auto firstSpace = authHeaderStr.find_first_of(' '); + if (firstSpace == authHeaderStr.npos) + { + assignInvalidRequestError(L"Bad Authorization hedaer"); + } + else + { + auto scheme = authHeaderStr.substr(0, firstSpace); + auto value = authHeaderStr.substr(firstSpace + 1); + if (scheme != "Basic") + { + assignInvalidRequestError(L"Authorization must use 'Basic' type"); + } + else + { + // 'value' is 'client_id:client_crednetials' base64urlencoded + auto credsBuffer = CryptographicBuffer::DecodeFromBase64String(winrt::to_hstring(value)); + auto fullCreds = CryptographicBuffer::ConvertBinaryToString(BinaryStringEncoding::Utf8, credsBuffer); + std::wstring_view fullCredsStr = fullCreds; + auto colonPos = fullCredsStr.find_first_of(':'); + if (colonPos == fullCredsStr.npos) + { + assignInvalidRequestError(L"Bad Authorization header"); + } + else + { + auto credsClientId = fullCredsStr.substr(0, colonPos); + auto credsClientSecret = fullCredsStr.substr(colonPos + 1); + if (credsClientSecret != L"password") + { + assignMismatchedArgsError(L"Authorization client secret", L"password", credsClientSecret); + } + else if (clientId.empty()) + { + clientId = credsClientId; + } + else if (credsClientId != clientId) + { + assignMismatchedArgsError(L"Authorization client id", clientId, credsClientId); + } + } + } + } + } + + if (clientId.empty()) + { + assignInvalidRequestError(L"Client id not provided"); + } + + winrt::hstring expectedGrantType; + winrt::hstring expectedRedirectType; + winrt::hstring expectedPkceType = L"S256"; + winrt::hstring expectedScopeType = L"none"; + winrt::hstring expectedAuthType = L"none"; + winrt::hstring expectedError = L"none"; + bool expectAdditionalParams = false; + for (auto&& entry : WwwFormUrlDecoder{ clientId }) + { + auto name = entry.Name(); + auto value = entry.Value(); + if (name == L"grant") + { + expectedGrantType = value; + } + else if (name == L"redirect") + { + expectedRedirectType = value; + } + else if (name == L"pkce") + { + expectedPkceType = value; + } + else if (name == L"scope") + { + expectedScopeType = value; + } + else if (name == L"auth") + { + expectedAuthType = value; + } + else if (name == L"error") + { + expectedError = value; + } + else if (name == L"additional_params") + { + expectAdditionalParams = (value == L"true"); + } + // Ignore other values as these are specific to the authorization request + } + + auto checkUnexpectedArg = [&](std::wstring_view name, const winrt::hstring& value) + { + if (!value.empty()) + { + assignMismatchedArgsError(name, L"", value); + } + }; + + if (expectAdditionalParams) + { + if (additionalParam.empty()) + { + assignInvalidRequestError(L"Expected additional params, but none provided"); + } + else if (additionalParam != additional_param_value) + { + assignMismatchedArgsError(L"additional param", additional_param_value, additionalParam); + } + } + else if (!additionalParam.empty()) + { + assignInvalidRequestError(L"Expected no additional params, but one was provided"); + } + + if ((expectedAuthType == L"header") && (authHeader.RawValueLength == 0)) + { + assignInvalidRequestError(L"Authorization header expected, but not provided"); + } + + std::wstring responseJson; + if (expectedError == L"token") + { + errorString = L"server_error"; + errorMessage = json_escaped_error_description; + } + else if (grantType == L"authorization_code") + { + if (expectedGrantType != L"code") + { + assignMismatchedArgsError(L"grant_type", expectedGrantType, grantType); + } + else if (code.empty()) + { + assignInvalidRequestError(L"Authorization code not provided"); + } + + if (redirectUri) + { + auto expectedUri = (expectedRedirectType == L"protocol") ? protocol_redirect_uri : localhost_redirect_uri; + if (redirectUri.RawUri() != expectedUri) + { + assignMismatchedArgsError(L"redirect_uri", expectedUri, redirectUri.RawUri()); + } + } + else if (expectedRedirectType != L"inferred") + { + assignInvalidRequestError(L"Expected a 'redirect_uri', but none provided"); + } + + checkUnexpectedArg(L"username", username); + checkUnexpectedArg(L"password", password); + checkUnexpectedArg(L"scope", scope); // Only expected during auth request + checkUnexpectedArg(L"refresh_token", refreshToken); + + winrt::hstring codeClientId; + winrt::hstring codeChallengeMethod; + winrt::hstring codeChallenge; + for (auto&& entry : WwwFormUrlDecoder{ code }) + { + auto name = entry.Name(); + auto value = entry.Value(); + if (name == L"client") + { + codeClientId = value; + } + else if (name == L"challenge_method") + { + codeChallengeMethod = value; + } + else if (name == L"challenge") + { + codeChallenge = value; + } + else + { + assignInvalidRequestError(L"Unrecognized query parameter '" + name + L"' in code"); + } + } + + if (clientId != codeClientId) + { + assignMismatchedArgsError(L"client_id", codeClientId, clientId); + } + + if (expectedPkceType != codeChallengeMethod) + { + assignMismatchedArgsError(L"code challenge method", expectedPkceType, codeChallengeMethod); + } + else if (codeChallengeMethod == L"none") + { + if (!codeVerifier.empty()) + { + assignMismatchedArgsError(L"code_verifier", L"", codeVerifier); + } + } + else if (codeVerifier.empty()) + { + assignInvalidRequestError(L"Expected 'code_verifier', but none provided"); + } + + if (codeChallengeMethod == L"S256") + { + // We can't "unhash" the code challenge, so hash the code verifier and base64urlencode it + auto algo = HashAlgorithmProvider::OpenAlgorithm(HashAlgorithmNames::Sha256()); + auto hash = CryptographicBuffer::ConvertStringToBinary(codeVerifier, BinaryStringEncoding::Utf8); + auto base64Hash = CryptographicBuffer::EncodeToBase64String(hash); + + std::wstring base64urlencodedHash; + base64urlencodedHash.reserve(base64Hash.size()); + for (auto ch : base64Hash) + { + switch (ch) + { + case '+': base64urlencodedHash.push_back('-'); break; + case '/': base64urlencodedHash.push_back('_'); break; + case '=': break; // No padding + default: base64urlencodedHash.push_back(ch); break; + } + } + + if (codeChallenge != base64urlencodedHash) + { + assignInvalidRequestError(L"The code verifier does not match the original code challenge"); + } + } + else if (codeChallengeMethod == L"plain") + { + if (codeChallenge != codeVerifier) + { + assignInvalidRequestError(L"Code verifier does not match the expected value"); + } + } + } + else if (grantType == L"password") + { + if (expectedGrantType != L"password") + { + assignMismatchedArgsError(L"grant_type", expectedGrantType, grantType); + } + else if (username.empty()) + { + assignMismatchedArgsError(L"username", L"username", L""); + } + else if (username != L"username") + { + assignMismatchedArgsError(L"username", L"username", username); + } + else if (password.empty()) + { + assignMismatchedArgsError(L"password", L"password", L""); + } + else if (password != L"password") + { + assignMismatchedArgsError(L"password", L"password", password); + } + + checkUnexpectedArg(L"code", code); + // checkUnexpectedArg(L"redirect_uri", redirectUri); + checkUnexpectedArg(L"code_verifier", codeVerifier); + checkUnexpectedArg(L"refresh_token", refreshToken); + + } + else if (grantType == L"client_credentials") + { + if (expectedGrantType != L"client") + { + assignMismatchedArgsError(L"grant_type", expectedGrantType, grantType); + } + + checkUnexpectedArg(L"code", code); + // checkUnexpectedArg(L"redirect_uri", redirectUri); + checkUnexpectedArg(L"code_verifier", codeVerifier); + checkUnexpectedArg(L"username", username); + checkUnexpectedArg(L"password", password); + checkUnexpectedArg(L"refresh_token", refreshToken); + } + else if (grantType == L"refresh_token") + { + if (expectedGrantType != L"refresh") + { + assignMismatchedArgsError(L"grant_type", expectedGrantType, grantType); + } + else if (refreshToken != refresh_token_old) + { + assignMismatchedArgsError(L"refresh_token", refresh_token_old, refreshToken); + } + + checkUnexpectedArg(L"code", code); + // checkUnexpectedArg(L"redirect_uri", redirectUri); + checkUnexpectedArg(L"code_verifier", codeVerifier); + checkUnexpectedArg(L"username", username); + checkUnexpectedArg(L"password", password); + } + else if (grantType == extension_grant_uri) + { + if (expectedGrantType != L"extension") + { + assignMismatchedArgsError(L"grant_type", expectedGrantType, grantType); + } + + checkUnexpectedArg(L"code", code); + // checkUnexpectedArg(L"redirect_uri", redirectUri); + checkUnexpectedArg(L"code_verifier", codeVerifier); + checkUnexpectedArg(L"username", username); + checkUnexpectedArg(L"password", password); + checkUnexpectedArg(L"refresh_token", refreshToken); + } + else + { + assignInvalidRequestError(L"Unrecognized grant type '"s + grantType + L"'"); + } + + if (errorString.empty()) + { + // NOTE: All responses are the same + responseJson = L"{\"access_token\":\""; + responseJson += json_escaped_token; + responseJson += L"\",\"token_type\":\"Bearer\",\"expires_in\":3600,\"refresh_token\":\""; + responseJson += json_escaped_refresh_token; + responseJson += L"\""; + if (scope.empty()) + { + responseJson += L",\"scope\":\"all\""; + } + responseJson += L"}"; + } + else + { + responseJson = L"{\"error\":\"" + errorString + L"\",\"error_description\":\"" + errorMessage + + L"\",\"error_uri\":\"" + error_uri + L"\",\"" + additional_param_key + L"\":\"" + + additional_param_value + L"\"}"; + } + + WINRT_ASSERT(!responseJson.empty()); + + HTTP_RESPONSE response = {}; + response.StatusCode = 200; + response.pReason = "OK"; + response.ReasonLength = 2; + + auto& contentTypeHeader = response.Headers.KnownHeaders[HttpHeaderContentType]; + contentTypeHeader.pRawValue = "application/json; charset=UTF-8"; + contentTypeHeader.RawValueLength = static_cast(::strlen(contentTypeHeader.pRawValue)); + + auto responseJsonUtf8 = winrt::to_string(responseJson); + HTTP_DATA_CHUNK dataChunk = {}; + dataChunk.DataChunkType = HttpDataChunkFromMemory; + dataChunk.FromMemory.pBuffer = responseJsonUtf8.data(); + dataChunk.FromMemory.BufferLength = static_cast(responseJsonUtf8.size()); + + response.EntityChunkCount = 1; + response.pEntityChunks = &dataChunk; + + ULONG bytesSent; + auto sendResult = ::HttpSendHttpResponse(m_requestQueue, request->RequestId, 0, &response, nullptr, &bytesSent, + nullptr, 0, nullptr, nullptr); + if (sendResult != NO_ERROR) + { + Log::Warning(WEX::Common::String().Format(L"HttpSendHttpResponse failed: %d", sendResult)); + } + } + + // Detours Information + static inline decltype(&::ShellExecuteW) RealShellExecuteW = &::ShellExecuteW; + + // Local server for performing the token exchange + wil::unique_event m_serverShutdownEvent{ wil::EventOptions::None }; + std::thread m_httpServerThread; + HANDLE m_requestQueue = nullptr; + HTTP_SERVER_SESSION_ID m_serverSessionId = 0; + HTTP_URL_GROUP_ID m_urlGroup = 0; + std::uint16_t m_serverPort = 50001; + std::wstring m_serverUrlBase; +}; diff --git a/test/OAuthTests/OAuthTests.vcxproj b/test/OAuthTests/OAuthTests.vcxproj new file mode 100644 index 0000000000..01d6783caf --- /dev/null +++ b/test/OAuthTests/OAuthTests.vcxproj @@ -0,0 +1,157 @@ + + + + + + Debug + ARM64 + + + Debug + Win32 + + + Release + ARM64 + + + Release + Win32 + + + Debug + x64 + + + Release + x64 + + + + 16.0 + Win32Proj + {21651459-648e-475c-91db-2bde359c75a4} + OAuthTests + 10.0 + + + + DynamicLibrary + v143 + Unicode + + + true + + + false + true + + + + + + + + + + + + + + Level4 + true + true + NOMINMAX;WIN32_LEAN_AND_MEAN;OAUTHTESTS_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) + true + + ..\..\dev\Detours; + $(OutDir)\..\WindowsAppRuntime_DLL; + $(OutDir)\..\WindowsAppRuntime_BootstrapDLL; + %(AdditionalIncludeDirectories) + + + + Windows + true + false + httpapi.lib;%(AdditionalDependencies) + Microsoft.WindowsAppRuntime.dll;%(DelayLoadDLLs) + + + + + _DEBUG;%(PreprocessorDefinitions) + + + + + true + true + NDEBUG;%(PreprocessorDefinitions) + + + true + true + + + + + WIN32;%(PreprocessorDefinitions) + + + + + WIN32;%(PreprocessorDefinitions) + + + + + + + + + + .Debug + _Debug + $(AppxPackageDir)\OAuthTestAppPackage_1.0.0.0_$(PlatformTarget)$(TestPkgDebugConfigName)_Test + $(TestPkgOutputPath)\OAuthTestAppPackage_1.0.0.0_$(PlatformTarget)$(TestPkgDebugConfigName).msix + + + + + + + + {d6bc25c5-1aa7-4c4a-a02c-b42dedbfea33} + + + {f76b776e-86f5-48c5-8fc7-d2795ecc9746} + + + + + $(OutDir)\..\WindowsAppRuntime_DLL\Microsoft.Windows.Security.Authentication.OAuth.winmd + true + $(OutDir)\..\WindowsAppRuntime_DLL\Microsoft.WindowsAppRuntime.dll + + + + + + + + + + + This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. + + + + + + + + + + \ No newline at end of file diff --git a/test/OAuth/OAuthTests/OAuthTests.vcxproj.filters b/test/OAuthTests/OAuthTests.vcxproj.filters similarity index 100% rename from test/OAuth/OAuthTests/OAuthTests.vcxproj.filters rename to test/OAuthTests/OAuthTests.vcxproj.filters diff --git a/test/OAuth/OAuthTests/packages.config b/test/OAuthTests/packages.config similarity index 100% rename from test/OAuth/OAuthTests/packages.config rename to test/OAuthTests/packages.config diff --git a/test/OAuth/OAuthTests/OAuthTests.vcxproj b/test/TestApps/OAuthTestApp/OAuthTestApp.vcxproj similarity index 70% rename from test/OAuth/OAuthTests/OAuthTests.vcxproj rename to test/TestApps/OAuthTestApp/OAuthTestApp.vcxproj index 5fddba07b3..39fb17bbca 100644 --- a/test/OAuth/OAuthTests/OAuthTests.vcxproj +++ b/test/TestApps/OAuthTestApp/OAuthTestApp.vcxproj @@ -30,13 +30,13 @@ 16.0 Win32Proj - {21651459-648e-475c-91db-2bde359c75a4} - OAuthTests + {077bdbfd-c1aa-49c8-bd62-7c14221c45f2} + OAuthTestApp 10.0 - DynamicLibrary + Application v143 Unicode @@ -51,8 +51,6 @@ - - @@ -61,23 +59,14 @@ Level4 - true true - NOMINMAX;WIN32_LEAN_AND_MEAN;OAUTHTESTS_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) + _CONSOLE;%(PreprocessorDefinitions) + true true - - ..\..\..\dev\Detours; - $(OutDir)\..\WindowsAppRuntime_DLL; - $(OutDir)\..\WindowsAppRuntime_BootstrapDLL; - %(AdditionalIncludeDirectories) - - Windows + Console true - false - httpapi.lib;%(AdditionalDependencies) - Microsoft.WindowsAppRuntime.dll;%(DelayLoadDLLs) @@ -101,26 +90,23 @@ WIN32;%(PreprocessorDefinitions) - - - WIN32;%(PreprocessorDefinitions) - - - + - - {d6bc25c5-1aa7-4c4a-a02c-b42dedbfea33} - {f76b776e-86f5-48c5-8fc7-d2795ecc9746} + + $(BaseOutputPath)\WindowsAppRuntime_DLL\Microsoft.Windows.AppLifecycle.winmd + true + $(OutDir)\..\WindowsAppRuntime_DLL\Microsoft.WindowsAppRuntime.dll + $(OutDir)\..\WindowsAppRuntime_DLL\Microsoft.Windows.Security.Authentication.OAuth.winmd true @@ -130,8 +116,6 @@ - - @@ -139,10 +123,8 @@ - - - \ No newline at end of file + diff --git a/test/TestApps/OAuthTestApp/OAuthTestApp.vcxproj.filters b/test/TestApps/OAuthTestApp/OAuthTestApp.vcxproj.filters new file mode 100644 index 0000000000..ce0c35ccf4 --- /dev/null +++ b/test/TestApps/OAuthTestApp/OAuthTestApp.vcxproj.filters @@ -0,0 +1,22 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + + + Source Files + + + \ No newline at end of file diff --git a/test/TestApps/OAuthTestApp/main.cpp b/test/TestApps/OAuthTestApp/main.cpp new file mode 100644 index 0000000000..81cede8faa --- /dev/null +++ b/test/TestApps/OAuthTestApp/main.cpp @@ -0,0 +1,30 @@ + +#include + +#include +#include +#include +#include + +using namespace winrt::Microsoft::Windows::AppLifecycle; +using namespace winrt::Microsoft::Windows::Security::Authentication::OAuth; +using namespace winrt::Windows::ApplicationModel::Activation; + +int main() +{ + auto args = AppInstance::GetCurrent().GetActivatedEventArgs(); + auto kind = args.Kind(); + if (kind == ExtendedActivationKind::Protocol) + { + auto uri = args.Data().as().Uri(); + if (!AuthManager::CompleteAuthRequest(uri)) + { + std::printf("WARNING: Failed to complete auth request with uri '%ls'.\n", uri.RawUri().c_str()); + std::printf("WARNING: This may or may not be expected depending on which test is running.\n"); + } + } + else + { + std::printf("WARNING: Application was launched with something other than protocol activation!\n"); + } +} diff --git a/test/TestApps/OAuthTestApp/packages.config b/test/TestApps/OAuthTestApp/packages.config new file mode 100644 index 0000000000..fb3afa8d2f --- /dev/null +++ b/test/TestApps/OAuthTestApp/packages.config @@ -0,0 +1,4 @@ + + + + diff --git a/test/TestApps/OAuthTestAppPackage/Images/LockScreenLogo.scale-200.png b/test/TestApps/OAuthTestAppPackage/Images/LockScreenLogo.scale-200.png new file mode 100644 index 0000000000000000000000000000000000000000..735f57adb5dfc01886d137b4e493d7e97cf13af3 GIT binary patch literal 1430 zcmaJ>TTC2P7~aKltDttVHYH6u8Io4i*}3fO&d$gd*bA_<3j~&e7%8(eXJLfhS!M@! zKrliY>>6yT4+Kr95$!DoD(Qn-5TP|{V_KS`k~E6(LGS@#`v$hQo&^^BKsw3HIsZBT z_y6C2n`lK@apunKojRQ^(_P}Mgewt$(^BBKCTZ;*xa?J3wQ7~@S0lUvbcLeq1Bg4o zH-bvQi|wt~L7q$~a-gDFP!{&TQfc3fX*6=uHv* zT&1&U(-)L%Xp^djI2?~eBF2cxC@YOP$+9d?P&h?lPy-9M2UT9fg5jKm1t$m#iWE{M zIf%q9@;fyT?0UP>tcw-bLkz;s2LlKl2qeP0w zECS7Ate+Awk|KQ+DOk;fl}Xsy4o^CY=pwq%QAAKKl628_yNPsK>?A>%D8fQG6IgdJ ztnxttBz#NI_a@fk7SU`WtrpsfZsNs9^0(2a z@C3#YO3>k~w7?2hipBf{#b6`}Xw1hlG$yi?;1dDs7k~xDAw@jiI*+tc;t2Lflg&bM)0!Y;0_@=w%`LW^8DsYpS#-bLOklX9r?Ei}TScw|4DbpW%+7 zFgAI)f51s}{y-eWb|vrU-Ya!GuYKP)J7z#*V_k^Xo>4!1Yqj*m)x&0L^tg3GJbVAJ zJ-Pl$R=NAabouV=^z_t;^K*0AvFs!vYU>_<|I^#c?>>CR<(T?=%{;U=aI*SbZADLH z&(f2wz_Y0??Tf|g;?|1Znw6}6U43Q#qNRwv1vp9uFn1)V#*4p&%$mP9x&15^OaBiDS(XppT|z^>;B{PLVEbS3IFYV yGvCsSX*m literal 0 HcmV?d00001 diff --git a/test/TestApps/OAuthTestAppPackage/Images/SplashScreen.scale-200.png b/test/TestApps/OAuthTestAppPackage/Images/SplashScreen.scale-200.png new file mode 100644 index 0000000000000000000000000000000000000000..023e7f1feda78d5100569825acedfd213a0d84e9 GIT binary patch literal 7700 zcmeHLYj~4Yw%(;oxoEH#Kxq-eR|+VkP17b#Vk;?4QwkI+A{L04G+#<<(x#Un1#+h5>eArRq zTw$)ZvTWW_Y?bDho0nPVTh08+s`sp!j74rJTTtXIDww0SILedFv?sZ?yb@@}GN;#8 znk_b~Q(A0YR#uV4ef!osoV1M3;vQ8N$O|fStfgf$S5;ddUNv`tWtGjM;koG#N;7M< zP*84lnx(bn_KF&9Z5Ai$)#Cs3a|$OFw>WKCT$of*L7_CqQEinflT|W{JT+aKp-E0v zsxmYg)1(T>DROm+LN1eQw8}KCTp=C!$H7`PU!t9_Hw@TsTI2`udRZv*!a5`#A9hK6Y95L(CDUX&_@QxKV z_feX{UhA#ZWlvgpL$#w^D#lq`_A4AzDqd|Zv6y9PX&DNcN|l}_D^{q@GG&H^Pg583 z8FI6N8^H7b5WjGp;urW)d7F+_lcp%KsLX0viCmE(OHH+=%ZfD_=`voUuoUxFO^L;- z;!;2{g-YiiO6m4bs89OuF9!p{FGtH-f%8<2gY!h9s)4ciN%{Kh1+`}{^}M~+TDH9N z^Z5PlgVXMC&2&k*Hw^Lb9gny#ro$MOIxIt{+r)EA10$VR3 zanN8D{TUkl+v0CQ_>ZoHP<M-x#8@8ZiT#$Kh`(uRaX1g$Bg|qy$<#7 zSSAi{Nb8Y=lvNVeio+UGLCAtoLBfL`iOv`)yoJMDJBN>4IH@(l7YRF;61@>qq1iM9 zr@b#OC~SAxSle?5Pp8Z78{VO0YFr1x7kZU64Z23eLf2T2#6J_t;-E}DkB?NufZ0Ug zi?J&byXeaB-uTNVhuiM!UVQw}bZrJ3GtAETYp->!{q#zfN7D3AS9@Q7*V^85jGx#R z(QxYV(wW#F0XF9^^s>>H8pPlVJ>)3Oz z&_X8Sf@~?cH_O*cgi$U#`v`RRfv#y3m(ZpKk^5uLup+lVs$~}FZU$r_+}#hl%?g5m z-u-}-666ssp-xWQak~>PPy$mRc|~?pVSs1_@mBEXpPVfLF6(Ktf1S* zPPh@QZ=tFMs?LM2(5P3L2;l_6XX6s&cYsP1ip#eg0`ZEP0HGYh{UmS@o`MihLLvkU zgyAG0G`b1|qjxxh1(ODKFE%AP}Dq=3vK$P7TXP4GrM1kQ72!GUVMDl`rDC&2;TA}*nF z8$nQD&6ys_nc1*E7$*1S@R8$ymy(sQV}imGSedB@{!QR5P&N_H=-^o!?LsWs+2|mH z-e=)T^SvI)=_JIm7}j4;@*Z17=(#}m=~YF~z~CLI+vdAGlJDcdF$TM?CVI1%LhUrN zaa6DJ=Yh$)$k&Oz{-~8yw^GM^8prYxSxo zvI4k#ibryMa%%*8oI-5m61Koa_A_xg=(fwp0aBX{;X4Q;NXUhtaoJDo1>TqhWtn=_ zd5~chq#&6~c%8JZK#t_&J(9EVUU&upYeIovLt1>vaHe}UUq>#RGQj!EN#5+0@T`(@ z^g~>*c`VGRiSt;!$_4+0hk^I!@O3``5=sZ8IwlxWW7km1B&_t&E*u0_9UBa#VqwY* zz>nxv?FAsVnRaD(Bui=6i==BFUw0k4n$>`umU`F2l?7CYTD^)c2X+d9X&ddS9|gj? zM?knGkGCX&W8offw8aLC2$D{PjC3nVZwd4k?eZH8*mZ)U@3Qk8RDFOz_#WUA#vnzy zyP>KrCfKwSXea7}jgJjBc}PGY+4#6%lbZyjhy`5sZd_Vy6Wz;ixa?czkN}J9It1K6 zY!eu>|AwF^fwZlLAYyQI*lM@^>O>Iu6Vf6i>Q$?v!SeUS<{>UYMwz$*%Aq?w^`j{h z!$GZbhu=^D{&ET8;))LL%ZBDZkQqRd2;u~!d9bHGmLRhLDctNgYyjsuvoSZ#iVdoB z2!f--UUA#U;<{je#?cYt^{PIyKa%hW>}uepWMyAI{{Zo7?2>?$c9;whJae%oN|I-kpTQSx_C$Z&;f zi2i)qmEn=y4U0uvk)$m;zKfjPK@oc?I`}1Jzl$Q~aoKBd3kt7L#7gyt|A_qgz6ai< z=X%D1i!d2h?rHR^R8SUj&G||dkC?DT>{o#Yau<@uqVT{Xef&XG}5*E4aPk{}~ zplx&XhaV)&1EfI3Em;Bw#O5SV^c;{twb-1Rw)+=0!e_BLbd7tYmXCH0wrlOSS+~`7He8Iqx0{CN+DVit9;*6L~JAN zD&cyT)2?h}xnYmL?^)<7YyzZ3$FHU^Eg;DLqAV{#wv#Wj7S`Jdl1pX&{3(uZ?!uh} zDc$ZTNV*7le_W6}Hju~GMTxZQ1aWCeUc%!jv3MHAzt>Y-nQK%zfT*3ebDQA5b?iGn; zBjv3B+GhLTexd_(CzZDP4|#n5^~scvB6#Pk%Ho!kQ>yYw((Dv{6=$g3jT1!u6gORW zx5#`7Wy-ZHRa~IxGHdrp(bm%lf>2%J660nj$fCqN(epv@y!l9s7@k6EvxS{AMP>WY zX4$@F8^kayphIx-RGO$+LYl9YdoI5d|4#q9##`_F5Xnx`&GPzp2fB{-{P@ATw=X@~ z_|&^UMWAKD;jjBKTK(~o?cUFRK8EX=6>cXpfzg4ZpMB>*w_^8GSiT-Jp|xBOnzM+j z*09-@-~qJ(eqWq5@R4i^u4^{McCP(!3}C|v_WsTR*bIUxN(Nx`u##3B4{sE`Z`v8w zAwIG`?1~PkID~W{uDzmqH98Pew_1(;x2%8r^vY{)_&J2K)cN{W+h5+g)ZcjP&Ci#O zgy|8K@4kyMfwilHd&6TDlhb%++Pk!>9HRld6HT7gwyZGrxS$}CsD6`>6!!2K1@Mjf z(P0WYB7V_OFZyeWrbOFb>O54BNXf~K&?}3=^v;v_wT{DKr?jN^DtN&DXwX%u?s*c6`%8>WFz z7}YW^tp0bp^NriE)AB6M2l<7rn7fzePtR*omOevpfm9n?}2V*+0iW;S)C zhg`NAjL?D=W#k*$aR{>pGf~lD-rVtD;5jW1_*Jn1j1=es@Kcx4ySM_bwcQCT=d+DV z>Sz~L=Hj@(X%31nK$mWI@7d>}ORB`K(p=+`UD)+99YUGQc7y^bHZ1F(8|tL0 zdK*DT0kSXG_{BKTpP2*2PecdKV9;dq$^ZZDP;Nyq1kp-&GI5eAyZsK!e3V zK@rPy*{(`KIfo+lc878mDKk^V#`VT05}64kBtk%DgwLrOvLMj5-;*GNKv6c6pzMuL z6EP%ob|_0IW}lLRXCP2!9wWhEw3LA7iF#1O1mIZ@Z=6&bz41F;@S_GvYAG-#CW3z{ zP3+6vHhvP&A3$##Vo9$dT^#MoGg^|MDm=Bt1d2RRwSZ<;ZHICpLBv5Xs!D?BH^(9_ z7`H=N&^v|Z-%mP}wNzG{aiFCsRgwzwq!N6obW9+7(R; z(SZ=23`|`>qil!LMGG{_Heq!BD>(Y-zV9wD)}hz25JA37YR%39;kI4y9pgtcUass6 zP24}ZY$vvYeI`zy&)A_X#nY3017ap*0&jx|mVwyGhg3;!keU53a}Uhm3BZI$N$6Se zLWlAmy1S0xKJm4G_U@sN_Tm=`$xWJSEwKU98rZ&)1R^*$$1vA3oG#&*%SMxY_~oGP zP&PFJatFLM-Ps%84IV-+Ow)T{C7cqUAvauy4C z(FRz&?6$Rypj{xO!`y=*J5o4@U8Q-(y5(*=YoKeZ+-1YdljXxkA#B)zo=FeQH#?Le zycNUmEEHWO9a=X^pb#&cOq7-`7UA87#|S22)<7RUtZo|(zibX=w;K3qur9vy#`MNV z6UUcf9ZwEnKCCp+OoBnF@OdbvH)ANXO0o~Pi9l8=x3))}L<#vO0-~O4!~--Ket?d} zJaqsj<@CD1%S2cTW%rOP{Vto%0sGW~1RMa_j^)5nil0Yw- z0EE#bP+l4#P^%PQ+N*oxu1Zq05xZ!bXfYTg>9c{(Iw*lnjR^>kz%lAN^zFce7rppy zY8zA~3GD=A6d*hze&l4D_wA~+O!56)BZTe_rEu}Ezi<4!kG|W#amBZ5{&XS2@6R~H z{9o^y*BkH4$~yX9U&@CgbOzX1bn9xqF|zh$Dh0Y5y*E0e90*$!ObrHY3Ok0`2=O~r zCuke6KrP9KOf?V(YDsM<6pX2nVoN%M$LT^q#FmtaF?1^27F*IcNX~XRB(|hCFvdcc zc)$=S-)acdk$g4?_>jRqxpI6M3vHZk?0c^3=byamYDNf;uB{3NlKW5IhnOS3DNkMV z?tK8?kJ}pmvp%&&eTVOVjHP`q34hN1@!aK}H(K!vI`~gf|Gv+FNEQD5Yd<~yX7k_l h&G-K)@HZb3BABY{)U1?^%I#E6`MGoTtustd{~yM6srvu` literal 0 HcmV?d00001 diff --git a/test/TestApps/OAuthTestAppPackage/Images/Square150x150Logo.scale-200.png b/test/TestApps/OAuthTestAppPackage/Images/Square150x150Logo.scale-200.png new file mode 100644 index 0000000000000000000000000000000000000000..af49fec1a5484db1d52a7f9b5ec90a27c7030186 GIT binary patch literal 2937 zcma)84OCO-8BSud5)jwMLRVKgX(S?$n?Ld|vrsm<$CF7)&zTbyy1FE5bU`Q17MRv`9ue$;R(@8kR;#vJ*IM0>cJIAOte!d7oRgdH zd%ySjdB6L9=gX^A6)VzH7p2l@v~3zJAMw|DFy#^)F@@F*`mqUn=Il>l)8_+ab;nOW{%+iPx z+s{Eu|&pIs)Z7{La9~?xKfyl z#43?gjEL15d4WbOZo#SiP%>DB^+BcnJ=7dHEe;r#G=tuw|ka z%q@}##Uh7;tc%L_64m(kHtw74ty%BJMb)_1)#S0j`)F8_1jF7vScpsnH=0V19bO8y zR`0SjIdCUo&=>JwMQF8KHA<{ODHTiQh}0^@5QRmCA?gOH6_H3K^-_sNB^RrdNuK-R zOO*vOrKCVvDwgUck`kF(E7j{I#iiN;b*ZdCt4m@HPA`EuEqGGf4%!K<;(=I=&Vyrw z%TwcWtxa}8mCZ%Cyf&ActJ6_$ox5z6-D!0-dvnRx6t7y3d+h6QYpKWO;8OdnvERo7 zuEf>ih5`wqY)~o@OeVt-wM?Q!>QzdGRj!bz6fzYrfw$hZfAKzr2-M+D+R>}~oT574c;_3zquHcElqKIsryILt3g8n3jcMb+j?i?-L3FpZJ z2WRVBRdDPc+G5aaYg#5hpE+6nQ|(VSoxT3|biF;BUq#==-27Xi=gihDPYP$7?=9cP zYKE$jeQ|3~_L0VG-(F~2ZPyD0=k{J4Q~h(t__{-mz_w8{JDY9{`1ouzz!Vr5!ECdE z6U~O1k8c}24V7~zzXWTV-Pe4)y}wQJS&q%H5`Fo_f_JvIU489aCX$;P`u#!I-=^4ijC2{&9!O&h>mi?9oYD=GC#%)6{GzN6nQYw+Fal50!#x^asjBBR50i`+mho*ttoqV)ubM2KD9S~k7+FR4>{29?6 z{!l6kDdyTN0YJ9LgkPWeXm|gyi@zM3?0@{&pXT12w|78&W-q!RRF)&iLCEZVH<|fR zN0fr2^t8H(>L?>K#>^+jWROLral(Qy-xoBq1U7A&DV||wClb)Otd9?(gZ|8znMF}D zf<1haWz^s0qgecz;RFGt0C-B4g`jNGHsFU+;{<%t65v^sjk^h$lmWn#B0#_)9ij&d z-~lc`A)YYExi^7sBuPM^Y|wA2g*5?`K?#7tzELQYNxGo$UB$4J8RJp1k(8Jj+~hMT zlN~>M@KTTh^--8y3PK_NZ@AC!{PT=CziBzGd+wTJ^@icH!Bd}%)g8V)%K?|c&WTUk zy}qv1C%(fjRoZ4ozC3{O%@5?)XzH35zHns$pgU*Q?fj4v?fp1Qbm+j;3l;9jam9Da zXVcKjPlQ73x78QPu|Ffm6x?`~e3oD=gl=4kYK?={kD5j~QCXU)`HSdduNNENzA*2$ zOm3PzF!lN5e*06-f1Uot67wY#{o-S1!KZ7E=!~7ynnk9_iJR#kFoNbAOT#^2Gd17F zMmvU6>lndZQGd|ax9kUoXXO+$N?|j@6qpsF&_j7YXvwo_C{JpmLw5&#e6k>atv%es z5)7r*Wvv_JkUpT}M!_o!nVlEk1Zbl=a*2hQ*<|%*K1Glj^FcF`6kTzGQ3lz~2tCc@ z&x|tj;aH&1&9HwcJBcT`;{?a+pnej;M1HO(6Z{#J!cZA04hnFl;NXA+&`=7bjW_^o zfC40u3LMG?NdPtwGl>Tq6u}*QG)}-y;)lu-_>ee3kibW(69n0$0Zy!}9rQz%*v1iO zT9_H>99yIrSPYVy6^);rR}7Yo=J_T@hi+qhTZXnVWyf;JDYm5#eYLTxr*?kiNn!+Y zQ+LUkBafNJ#rH#C(?d5^;gw9o#%daEI{mA*LHPIHPU`#|H$hD zwm>0&+kahQ)E#%~k>&5@&#Vg82H?s%71=)(soi@174pi9--2{w{1$}Sz4zGn3Du&x bht0Iza^2ykEt4(epJ78uh5nDlX8(TxzDYwP literal 0 HcmV?d00001 diff --git a/test/TestApps/OAuthTestAppPackage/Images/Square44x44Logo.scale-200.png b/test/TestApps/OAuthTestAppPackage/Images/Square44x44Logo.scale-200.png new file mode 100644 index 0000000000000000000000000000000000000000..ce342a2ec8a61291ba76c54604aea7e9d20af11b GIT binary patch literal 1647 zcmaJ?eM}Q)7(e+G1Q(|`V9JhTI2>MkceK4;p;PR&$Pi?ejk3YQ_3o`S&|W_dsOZ8# zWPTt69g`t$ab`0cj-Y0yiBSOqmd)tG7G(}M5aP0_%&9TijB#&)I{zSE^4@#z^FF`l z`8{8`o%wlL(UI|y2!cdsuVamHH~H86F!*-15em4)NqUpCQM5?aoC_eCf@lV4wvF2a zjDQn1JBL69f&@2M3rvzJcfE!eZ8FZUBlFlC5RD)it33{mF9#B82AiyQE%w)`vlwa> zv{<1sm&kSKK$&%2jSFn7$t&P%%6Ue>R=EAnG8N7fqynWG8L3p!4801a;8{+nliO(qd(jNJ_?+9W3#hLIDLoT6~3fx9=`CC-D}-AMrpEO7HK zt3$GicGPc?GmDjy7K2P@La;eu4!$zWCZ`ym{Z$b zu-O6RM&K4JT|BIZB`E-gxqG%FzanI#+2FFmqHqXG7yxWB=w55RGOM)$xMb(>kSNR z2w=1AZi%z=AmG~yea~XaXJR!v7vLn(RUnELfiB1|6D84ICOS}^Zo2AdN}<&*h}G_u z{xZ!(%>tLT3J3<5XhWy-tg+6)0nmUUENLW8TWA{R6bgVd3X;anYFZ^IRis*_P-C-r z;i>%1^eL3UI2-{w8nuFFcs0e~7J{O2k^~Ce%+Ly4U?|=!0LH=t6()xi<^I-rs+9sF z*q{E-CxZbGPeu#a;XJwE;9S1?#R&uns>^0G3p`hEUF*v`M?@h%T%J%RChmD|EVydq zmHWh*_=S%emRC*mhxaVLzT@>Z2SX0u9v*DIJ@WC^kLVdlGV6LpK$KIrlJqc zpJ921)+3JJdTx|<`G&kXpKkjGJv=76R`yYIQ{#c-`%+`#V(7}Q;&@6U8!Td1`d;?N z_9mnI#?AA}4J!r)LN4!E-@H5eXauuB7TOawS>Y|{-P?NNx-lq+z1W-+y(;39P&&LP zL{N80?&=C*qKmdA^moMZRuPcD!B<*mq$ch=0Cnlitw#txRWhb3%TQvPqjkC`F69G4b! ze7z9MZ#+;_#l?H37UqUhDFb^l&s2{oM$3I0o^Q!yx;;V)QmCMo)Tb_ui|mit8MS?U zm##6$sZZ1$@|s%?l@>4Z<*Q}sRBSKMhb4I{e5LdEhsHIHTe8Bod5c>6QtT>$XgUBz z6MK`kO$=jmt@FqggOhJ5j~e@ygRbG;<{Vu)*+nn9aQeo0;$#j;|MS=S$&L?BeV25z xs3B`@=#`5TF{^6(A1rvdY@|-RtQ|iS5{tyX+wH?;n8E)G$kykv-D^wh{{!TZT%7;_ literal 0 HcmV?d00001 diff --git a/test/TestApps/OAuthTestAppPackage/Images/Square44x44Logo.targetsize-24_altform-unplated.png b/test/TestApps/OAuthTestAppPackage/Images/Square44x44Logo.targetsize-24_altform-unplated.png new file mode 100644 index 0000000000000000000000000000000000000000..f6c02ce97e0a802b85f6021e822c89f8bf57d5cd GIT binary patch literal 1255 zcmaJ>TWs4@7*5+{G#S+&C!qC#> zf>5N3P6jO*Cz>ug*(_DmW=)kea&m$gZ^+nyiF`;j%w@}y8)>p*SH}C`m?DXeieF2U zyQHecc_L%Gh!7GMt+hG06y;+|p4>m~}PjA}rKViGiEnn7G0ZO<>G|7q;2?NwGCM3s?eued6%hd$B+ z*kQJ{#~$S=DFE(%=E+UkmlEI*%3llUf~8Ja9YU1Vui0IbGBkW_gHB%Rd&!!ioX zs40O?i9I{};kle7GMvE7(rk`la=gTI)47=>%?q@^iL-nUo3}h4S}N-KHn8t5mVP8w z&bSErwp+37 zNJJ8?a|{r5Q3R0Z5s-LB1WHOwYC@7pCHWND#cL1cZ?{kJ368_*(UDWUDyb<}0y@o# zfMF016iMWPCb6obAxT$JlB6(2DrlXDTB&!0`!m??4F(qWMhjVZo?JXQmz`1*58Z=& zcDmB|S-E@j?BoFGix0flckqdS4jsPNzhfWyWIM98GxcLs89C(~dw%$_t;JjX-SD}E zfiGV;{8Q%8r}w9x>EEigW81>`kvnU@pK)4+xk9@+bNj9L!AAZ@SZ@q|)&BmY3+HZx zul~BeG4|}-;L%cHViQGQX?^zFfO0&#cHwel=d`lH9sJ-@Sl@n*(8J2>%Ac`IxyY?Q z{=GhWvC#gu-~Ia7*n{=+;qM?Ul_wy1+u7ho;=`>EwP^g~R@{unBds`!#@}tluZQpS zm)M~nYEifJWJGx?_6DcTy>#uh%>!H9=hb^(v`=m3F1{L>db=<5_tm+_&knAQ2EU$s Mu9UqpbNZeC0BbUo^Z)<= literal 0 HcmV?d00001 diff --git a/test/TestApps/OAuthTestAppPackage/Images/StoreLogo.png b/test/TestApps/OAuthTestAppPackage/Images/StoreLogo.png new file mode 100644 index 0000000000000000000000000000000000000000..7385b56c0e4d3c6b0efe3324aa1194157d837826 GIT binary patch literal 1451 zcmaJ>eN5D57_Z|bH;{0+1#mbl)eTU3{h)Wf7EZV?;HD@XL@{B`Ui%(2aMxQ~xdXSv z5nzWi(LW)U2=Vc-cY@s7nPt{i0hc6!7xN4NNHI#EQl>YNBy8l4%x9gr_W-j zEZMQmmTIy(>;lblRfh`dIyTgc9W5d!VP$L4(kKrN1c5G~(O_#xG zAJCNTstD^5SeXFB+&$h=ToJP2H>xr$iqPs-#O*;4(!Fjw25-!gEb*)mU}=)J;Iu>w zxK(5XoD0wrPSKQ~rbL^Cw6O_03*l*}i=ydbu7adJ6y;%@tjFeXIXT+ms30pmbOP%Q zX}S;+LBh8Tea~TSkHzvX6$rYb)+n&{kSbIqh|c7hmlxmwSiq5iVhU#iEQ<>a18|O^Sln-8t&+t`*{qBWo5M?wFM(JuimAOb5!K#D}XbslM@#1ZVz_;!9U zpfEpLAOz=0g@bd6Xj_ILi-x^!M}73h^o@}hM$1jflTs|Yuj9AL@A3<-?MV4!^4q`e z)fO@A;{9K^?W?DbnesnPr6kK>$zaKo&;FhFd(GYFCIU^T+OIMb%Tqo+P%oq(IdX7S zf6+HLO?7o0m+p>~Tp5UrXWh!UH!wZ5kv!E`_w)PTpI(#Iw{AS`gH4^b(bm^ZCq^FZ zY9DD7bH}rq9mg88+KgA$Zp!iWncuU2n1AuIa@=sWvUR-s`Qb{R*kk(SPU^`$6BXz8 zn#7yaFOIK%qGxyi`dYtm#&qqox0$h=pNi#u=M8zUG@bpiZ=3sT=1}Trr}39cC)H|v zbL?W)=&s4zrh)7>L(|cc%$1#!zfL?HjpeP%T+x_a+jZ16b^iKOHxFEX$7d|8${H-* zIrOJ5w&i$>*D>AKaIoYg`;{L@jM((Kt?$N$5OnuPqVvq**Nm}(f0wwOF%iX_Pba;V z;m@wxX&NcV3?<1+u?A{y_DIj7#m3Af1rCE)o`D&Y3}0%7E;iX1yMDiS)sh0wKi!36 zL!Wmq?P^Ku&rK~HJd97KkLTRl>ScGFYZNlYytWnhmuu|)L&ND8_PmkayQb{HOY640 bno1(wj@u8DCVuFR|31B*4ek@pZJqxCDDe1x literal 0 HcmV?d00001 diff --git a/test/TestApps/OAuthTestAppPackage/Images/Wide310x150Logo.scale-200.png b/test/TestApps/OAuthTestAppPackage/Images/Wide310x150Logo.scale-200.png new file mode 100644 index 0000000000000000000000000000000000000000..288995b397fdbef1fb7e85afd71445d5de1952c5 GIT binary patch literal 3204 zcmbVPeQXow8NYmBd90>}0NP?GhXW~VaeThm=a0tV#EwJMI!)6M3}|c4_Bl3=Kd>G0 z(GHx1wl<7(tP?FsOQkTilSo*iIvF%uArExJ73~P zSv1xEy!U(Wd4A9D`FQV@W3@F^qJ@PEF$@z`Z!*BbFsS(^?B zyiAzJ+q})bkgiQHWqEb*jJD-coHYr1^iocg)l!Qa{Xqs-l~6J}p-|##ZHYofskQ3$ zI0;xzXyhazBeXhIsg5A=%ufo@f)1yy&ScKS0;HF^!r_2UE^lpZEom(+@duma3awTv zCrCL-%D_SvYWIcdHkmI}#50(fkUi)Qgx!80ju>g1za^}ff>JI8Z@^-iCiaCgg@TgF z+vtE?Q9{VQUX&MW9SYYmGcxA14%N2@7FwBTD4N<(2{nWgV8$e3?-F=L^&FrtWn~(U_Q~~^uYiyeY6-KoTnfh9AWz@ zIKje0)u!_Lw)E}G!#kEfwKVdNt(UAf9*f>tEL_(=xco-T%jTi@7YlC3hs2ik%Le0H ztj}RTeCF(5mwvi3_56>-yB?l;J>-1%!9~=fs|QcNG3J~a@JCu`4SB460s0ZO+##4fFUSGLcj_ja^fL4&BKALfb#$6$O?>P@qx2Agl^x0i&ugt zsy5Pyu=()`7HRMG3IB7F1@`_ z+-!J%#i6e^U$e#+C%Q>_qVRzWRsG^W_n+@OcX@vzI&z;mzHNb!GQ?LWA(wtpqHqTM z1OFw_{Zn?fD)p)`c`kOgv{de=v@suGRqY{N^U7gI1VF3*F=obwaXI6ob5__Yn zVTguS!%(NI09J8x#AO_aW!9W7k*UvB;IWDFC3srwftr{kHj%g)fvnAm;&h_dnl~

MY- zf+K}sCe8qU6Ujs`3ua{U0Of$R_gVQBuUA za0v=mu#vIOqiiAZOr&h*$WyOw&k-xr$;G4Ixa!#TJNr>95(h>l%)PUy4p+^SgR(uR zta%k*?ny-+nAr8spEk1fo{J4i!b^Fia`N{_F6@zidA2ZTTrjl#^5Z-2KfB@Cu}l9s z(*|Z2jc?p~vn2f)3y9i*7zJV1L{$?|&q)4oaT;uXi6>1GkRXVTOzAz(RHEmr=eFIi z`}<>-Q?K0GN8!IYxeP1XKXO+jsJbp~o^);Bc;%b7Flpe7;1`Ny@3r7ZR;?R)aJt8C ziNlEC<@3f_lIV4TwV}&e;D!Ee5_|e#g0LUh=5vmYWYm7&2h*M>QPKvGh9-)wfMMW3 z8J9b%1k7dzPzO0_NGQy92BZ^FR6R~6;^6?lqO;-QUP4BY%cG%3vEhbm#>4vIhPBh3 z-+pZGjh$x%Hp{?=FHsMp0&wNPlj00us{&`1ZOZTqs8%4X&xH=UDr*xyBW(Zp&Em94 zf)ZSfn#yg0N)>!1kWdkqJ^S*z0FF5|fj&qcE#Na|%OY0$uO>!&hP+1ywfD_WXk@4J(?MBftK7>$Nvqh@tDuarN%PrTLQ2Uzysx>UV=V zk^RrDSvdQ?0;=hY67EgII-f4`t=+i*yS=Y~!XlqIy_4x&%+OdfbKOFPXS2X5%4R{N z$SQMX^AK6(fA + + + 15.0 + + + + Debug + x86 + + + Release + x86 + + + Debug + x64 + + + Release + x64 + + + Debug + ARM64 + + + Release + ARM64 + + + + $(MSBuildExtensionsPath)\Microsoft\DesktopBridge\ + + + + c4454d2c-8024-41b8-bac1-fc2e544c810f + 10.0.19041.0 + 10.0.17763.0 + en-US + True + ..\OAuthTestApp\OAuthTestApp.vcxproj + true + False + $(RepoTestCertificatePFX) + $(RepoTestCertificatePassword) + false + SHA256 + True + True + $(Platform) + 0 + + + + Designer + + + + + + + + + + + + + + + + diff --git a/test/TestApps/OAuthTestAppPackage/Package.appxmanifest b/test/TestApps/OAuthTestAppPackage/Package.appxmanifest new file mode 100644 index 0000000000..c4f8563fe9 --- /dev/null +++ b/test/TestApps/OAuthTestAppPackage/Package.appxmanifest @@ -0,0 +1,58 @@ + + + + + + + + OAuthTestAppPackage + Microsoft Corporation + Images\StoreLogo.png + + + + + + + + + + + + + + + + + + + + + OAuth Test App + + + + + + + + + + + From cd15712238858e049a7e05c309374774b3765bc0 Mon Sep 17 00:00:00 2001 From: Duncan Horn Date: Tue, 29 Nov 2022 15:14:07 -0800 Subject: [PATCH 5/9] Use velocity --- .../WindowsAppSDK-BuildProject-Steps.yml | 8 +++++ dev/Common/TerminalVelocityFeatures-OAuth.h | 32 +++++++++++++++++++ dev/Common/TerminalVelocityFeatures-OAuth.xml | 20 ++++++++++++ dev/OAuth/AuthFailure.cpp | 1 + dev/OAuth/AuthManager.cpp | 12 +++++-- dev/OAuth/AuthRequestParams.cpp | 3 ++ dev/OAuth/AuthRequestResult.cpp | 6 ++-- dev/OAuth/AuthResponse.cpp | 1 + dev/OAuth/ClientAuthentication.cpp | 9 +++++- dev/OAuth/ClientAuthentication.h | 2 +- dev/OAuth/OAuth.idl | 28 ++++++++-------- dev/OAuth/OAuth.vcxitems | 7 ++-- dev/OAuth/TokenFailure.cpp | 1 + dev/OAuth/TokenRequestParams.cpp | 11 ++++--- dev/OAuth/TokenRequestResult.cpp | 6 ++-- dev/OAuth/TokenResponse.cpp | 1 + dev/OAuth/common.h | 2 ++ docs/Coding-Guidelines/Experimental.md | 2 +- test/OAuthTests/OAuthTests.cpp | 8 +++++ test/OAuthTests/OAuthTests.vcxproj | 1 + 20 files changed, 130 insertions(+), 31 deletions(-) create mode 100644 dev/Common/TerminalVelocityFeatures-OAuth.h create mode 100644 dev/Common/TerminalVelocityFeatures-OAuth.xml diff --git a/build/AzurePipelinesTemplates/WindowsAppSDK-BuildProject-Steps.yml b/build/AzurePipelinesTemplates/WindowsAppSDK-BuildProject-Steps.yml index fecea379d0..ae393603d0 100644 --- a/build/AzurePipelinesTemplates/WindowsAppSDK-BuildProject-Steps.yml +++ b/build/AzurePipelinesTemplates/WindowsAppSDK-BuildProject-Steps.yml @@ -136,6 +136,14 @@ steps: arguments: -Path $(Build.SourcesDirectory)\dev\common\TerminalVelocityFeatures-EnvironmentManager.xml -Channel ${{ parameters.channel }} -Language C++ -Namespace Microsoft.Windows.System -Output $(Build.SourcesDirectory)\dev\common\TerminalVelocityFeatures-EnvironmentManager.h workingDirectory: '$(Build.SourcesDirectory)' + - task: powershell@2 + displayName: 'Create OAuth TerminalVelocity features' + inputs: + targetType: filePath + filePath: tools\TerminalVelocity\Generate-TerminalVelocityFeatures.ps1 + arguments: -Path $(Build.SourcesDirectory)\dev\common\TerminalVelocityFeatures-OAuth.xml -Channel ${{ parameters.channel }} -Language C++ -Namespace Microsoft.Windows.Security.Authentication.OAuth -Output $(Build.SourcesDirectory)\dev\common\TerminalVelocityFeatures-OAuth.h + workingDirectory: '$(Build.SourcesDirectory)' + - task: powershell@2 name: UpdateTraceloggingConfig inputs: diff --git a/dev/Common/TerminalVelocityFeatures-OAuth.h b/dev/Common/TerminalVelocityFeatures-OAuth.h new file mode 100644 index 0000000000..21b805d2b3 --- /dev/null +++ b/dev/Common/TerminalVelocityFeatures-OAuth.h @@ -0,0 +1,32 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See LICENSE in the project root for license information. + +// THIS FILE IS AUTOMATICALLY GENERATED; DO NOT EDIT IT + +// INPUT FILE: dev\common\TerminalVelocityFeatures-OAuth.xml +// OPTIONS: -Channel Experimental -Language C++ -Namespace Microsoft.Windows.Security.Authentication.OAuth -Path dev\common\TerminalVelocityFeatures-OAuth.xml -Output dev\common\TerminalVelocityFeatures-OAuth.h + +#if defined(__midlrt) +namespace features +{ + feature_name Feature_OAuth = { DisabledByDefault, FALSE }; +} +#endif // defined(__midlrt) + +// Feature constants +#define WINDOWSAPPRUNTIME_MICROSOFT_WINDOWS_SECURITY_AUTHENTICATION_OAUTH_FEATURE_OAUTH_ENABLED 1 + +#if defined(__cplusplus) + +namespace Microsoft::Windows::Security::Authentication::OAuth +{ + +__pragma(detect_mismatch("ODR_violation_WINDOWSAPPRUNTIME_MICROSOFT_WINDOWS_SECURITY_AUTHENTICATION_OAUTH_FEATURE_OAUTH_ENABLED_mismatch", "AlwaysEnabled")) +struct Feature_OAuth +{ + static constexpr bool IsEnabled() { return WINDOWSAPPRUNTIME_MICROSOFT_WINDOWS_SECURITY_AUTHENTICATION_OAUTH_FEATURE_OAUTH_ENABLED == 1; } +}; + +} // namespace Microsoft.Windows.Security.Authentication.OAuth + +#endif // defined(__cplusplus) diff --git a/dev/Common/TerminalVelocityFeatures-OAuth.xml b/dev/Common/TerminalVelocityFeatures-OAuth.xml new file mode 100644 index 0000000000..f4bc3578cc --- /dev/null +++ b/dev/Common/TerminalVelocityFeatures-OAuth.xml @@ -0,0 +1,20 @@ + + + + + + + + + + Feature_OAuth + OAuth for the WindowsAppRuntime + AlwaysEnabled + + Preview + Stable + + + \ No newline at end of file diff --git a/dev/OAuth/AuthFailure.cpp b/dev/OAuth/AuthFailure.cpp index 40c8887a2d..293da5993f 100644 --- a/dev/OAuth/AuthFailure.cpp +++ b/dev/OAuth/AuthFailure.cpp @@ -2,6 +2,7 @@ #include "common.h" #include "AuthFailure.h" + #include using namespace std::literals; diff --git a/dev/OAuth/AuthManager.cpp b/dev/OAuth/AuthManager.cpp index 7140bbde59..8160d5f590 100644 --- a/dev/OAuth/AuthManager.cpp +++ b/dev/OAuth/AuthManager.cpp @@ -1,15 +1,15 @@ -#include +#include #include "common.h" #include "AuthManager.h" -#include - #include "AuthRequestParams.h" #include "TokenFailure.h" #include "TokenRequestParams.h" #include "TokenRequestResult.h" #include "TokenResponse.h" +#include + using namespace winrt::Microsoft::Windows::Security::Authentication::OAuth; using namespace winrt::Windows::Data::Json; using namespace winrt::Windows::Foundation; @@ -21,6 +21,8 @@ namespace winrt::Microsoft::Windows::Security::Authentication::OAuth::factory_im IAsyncOperation AuthManager::InitiateAuthRequestAsync(const Uri& authEndpoint, const oauth::AuthRequestParams& params) { + THROW_HR_IF(E_NOTIMPL, !::Microsoft::Windows::Security::Authentication::OAuth::Feature_OAuth::IsEnabled()); + auto paramsImpl = winrt::get_self(params); auto asyncOp = winrt::make_self(paramsImpl); @@ -51,6 +53,8 @@ namespace winrt::Microsoft::Windows::Security::Authentication::OAuth::factory_im bool AuthManager::CompleteAuthRequest(const Uri& responseUri) { + THROW_HR_IF(E_NOTIMPL, !::Microsoft::Windows::Security::Authentication::OAuth::Feature_OAuth::IsEnabled()); + // We need to extract the state in order to find the original request winrt::hstring state; auto tryFindState = [&](const winrt::hstring& str) @@ -157,6 +161,8 @@ namespace winrt::Microsoft::Windows::Security::Authentication::OAuth::factory_im IAsyncOperation AuthManager::RequestTokenAsync(Uri tokenEndpoint, oauth::TokenRequestParams params, oauth::ClientAuthentication clientAuth) { + THROW_HR_IF(E_NOTIMPL, !::Microsoft::Windows::Security::Authentication::OAuth::Feature_OAuth::IsEnabled()); + auto paramsImpl = winrt::get_self(params); paramsImpl->finalize(); diff --git a/dev/OAuth/AuthRequestParams.cpp b/dev/OAuth/AuthRequestParams.cpp index a77e9c40ca..8ecc41ef00 100644 --- a/dev/OAuth/AuthRequestParams.cpp +++ b/dev/OAuth/AuthRequestParams.cpp @@ -2,6 +2,7 @@ #include "common.h" #include "AuthRequestParams.h" + #include using namespace winrt::Microsoft::Windows::Security::Authentication::OAuth; @@ -16,6 +17,7 @@ namespace winrt::Microsoft::Windows::Security::Authentication::OAuth::implementa m_responseType(responseType), m_clientId(clientId) { + THROW_HR_IF(E_NOTIMPL, !::Microsoft::Windows::Security::Authentication::OAuth::Feature_OAuth::IsEnabled()); } AuthRequestParams::AuthRequestParams(const winrt::hstring& responseType, const winrt::hstring& clientId, @@ -24,6 +26,7 @@ namespace winrt::Microsoft::Windows::Security::Authentication::OAuth::implementa m_clientId(clientId), m_redirectUri(redirectUri) { + THROW_HR_IF(E_NOTIMPL, !::Microsoft::Windows::Security::Authentication::OAuth::Feature_OAuth::IsEnabled()); } oauth::AuthRequestParams AuthRequestParams::CreateForAuthorizationCodeRequest(const winrt::hstring& clientId) diff --git a/dev/OAuth/AuthRequestResult.cpp b/dev/OAuth/AuthRequestResult.cpp index 57faf80d88..702d4112a3 100644 --- a/dev/OAuth/AuthRequestResult.cpp +++ b/dev/OAuth/AuthRequestResult.cpp @@ -1,12 +1,12 @@ #include #include "common.h" -#include "AuthRequestResult.h" -#include - #include "AuthFailure.h" +#include "AuthRequestResult.h" #include "AuthResponse.h" +#include + using namespace winrt::Microsoft::Windows::Security::Authentication::OAuth; using namespace winrt::Windows::Foundation; using namespace winrt::Windows::Foundation::Collections; diff --git a/dev/OAuth/AuthResponse.cpp b/dev/OAuth/AuthResponse.cpp index e4b36b8eb9..c793fe66be 100644 --- a/dev/OAuth/AuthResponse.cpp +++ b/dev/OAuth/AuthResponse.cpp @@ -2,6 +2,7 @@ #include "common.h" #include "AuthResponse.h" + #include using namespace std::literals; diff --git a/dev/OAuth/ClientAuthentication.cpp b/dev/OAuth/ClientAuthentication.cpp index 902e2d2d68..608ee16d9b 100644 --- a/dev/OAuth/ClientAuthentication.cpp +++ b/dev/OAuth/ClientAuthentication.cpp @@ -1,7 +1,8 @@ -#include +#include #include "common.h" #include "ClientAuthentication.h" + #include using namespace winrt::Microsoft::Windows::Security::Authentication::OAuth; @@ -12,9 +13,15 @@ using namespace winrt::Windows::Web::Http::Headers; namespace winrt::Microsoft::Windows::Security::Authentication::OAuth::implementation { + ClientAuthentication::ClientAuthentication() + { + THROW_HR_IF(E_NOTIMPL, !::Microsoft::Windows::Security::Authentication::OAuth::Feature_OAuth::IsEnabled()); + } + ClientAuthentication::ClientAuthentication(const HttpCredentialsHeaderValue& authorization) : m_authorization(authorization) { + THROW_HR_IF(E_NOTIMPL, !::Microsoft::Windows::Security::Authentication::OAuth::Feature_OAuth::IsEnabled()); } oauth::ClientAuthentication ClientAuthentication::CreateForBasicAuthorization(const winrt::hstring& clientId, diff --git a/dev/OAuth/ClientAuthentication.h b/dev/OAuth/ClientAuthentication.h index cc1fc35670..2773304dc2 100644 --- a/dev/OAuth/ClientAuthentication.h +++ b/dev/OAuth/ClientAuthentication.h @@ -7,7 +7,7 @@ namespace winrt::Microsoft::Windows::Security::Authentication::OAuth::implementa { struct ClientAuthentication : ClientAuthenticationT { - ClientAuthentication() = default; + ClientAuthentication(); ClientAuthentication(http::Headers::HttpCredentialsHeaderValue const& authorization); static oauth::ClientAuthentication CreateForBasicAuthorization(const winrt::hstring& clientId, diff --git a/dev/OAuth/OAuth.idl b/dev/OAuth/OAuth.idl index 4dea5b56aa..cb70da86ba 100644 --- a/dev/OAuth/OAuth.idl +++ b/dev/OAuth/OAuth.idl @@ -1,12 +1,14 @@ -// Copyright (c) Microsoft Corporation and Contributors. +// Copyright (c) Microsoft Corporation and Contributors. // Licensed under the MIT License. +#include + namespace Microsoft.Windows.Security.Authentication.OAuth { [contractversion(1)] apicontract OAuthContract {}; - [contract(OAuthContract, 1)] + [contract(OAuthContract, 1), feature(Feature_OAuth)] runtimeclass ClientAuthentication { ClientAuthentication(); @@ -24,7 +26,7 @@ namespace Microsoft.Windows.Security.Authentication.OAuth Windows.Foundation.Collections.IMap AdditionalHeaders { get; }; } - [contract(OAuthContract, 1)] + [contract(OAuthContract, 1), feature(Feature_OAuth)] static runtimeclass AuthManager { // Initiates an authorization request in the user's default browser as described by RFC 6749 section 3.1. The @@ -53,7 +55,7 @@ namespace Microsoft.Windows.Security.Authentication.OAuth // Correlates to the 'code_challenge_method' as described by section 4.3 of RFC 7636: Proof Key for Code Exchange by // OAuth Public Clients (https://www.rfc-editor.org/rfc/rfc7636.html#section-4.3) - [contract(OAuthContract, 1)] + [contract(OAuthContract, 1), feature(Feature_OAuth)] enum CodeChallengeMethodKind { // Suppresses the use of a code verifier. An error will be thrown if a code challenge string is set when this @@ -65,7 +67,7 @@ namespace Microsoft.Windows.Security.Authentication.OAuth Plain = 2, }; - [contract(OAuthContract, 1)] + [contract(OAuthContract, 1), feature(Feature_OAuth)] runtimeclass AuthRequestParams { // Construct with required parameters @@ -150,7 +152,7 @@ namespace Microsoft.Windows.Security.Authentication.OAuth Windows.Foundation.Collections.IMap AdditionalParams { get; }; } - [contract(OAuthContract, 1)] + [contract(OAuthContract, 1), feature(Feature_OAuth)] runtimeclass AuthResponse { // From the "state" parameter of the authorization response. This property will always be set because a state @@ -200,7 +202,7 @@ namespace Microsoft.Windows.Security.Authentication.OAuth Windows.Foundation.Collections.IMapView AdditionalParams { get; }; } - [contract(OAuthContract, 1)] + [contract(OAuthContract, 1), feature(Feature_OAuth)] runtimeclass AuthFailure { // From the "error" parameter of the error response. The value of this property will map to a well known string @@ -239,7 +241,7 @@ namespace Microsoft.Windows.Security.Authentication.OAuth Windows.Foundation.Collections.IMapView AdditionalParams { get; }; } - [contract(OAuthContract, 1)] + [contract(OAuthContract, 1), feature(Feature_OAuth)] runtimeclass AuthRequestResult { // The raw URI that was used to complete the request. @@ -252,7 +254,7 @@ namespace Microsoft.Windows.Security.Authentication.OAuth AuthFailure Failure { get; }; } - [contract(OAuthContract, 1)] + [contract(OAuthContract, 1), feature(Feature_OAuth)] runtimeclass TokenRequestParams { // Construct with required parameters @@ -357,7 +359,7 @@ namespace Microsoft.Windows.Security.Authentication.OAuth Windows.Foundation.Collections.IMap AdditionalParams { get; }; } - [contract(OAuthContract, 1)] + [contract(OAuthContract, 1), feature(Feature_OAuth)] runtimeclass TokenResponse { // From the "access_token" parameter of the token response. A required property that should always be set. @@ -397,7 +399,7 @@ namespace Microsoft.Windows.Security.Authentication.OAuth Windows.Foundation.Collections.IMapView AdditionalParams { get; }; } - [contract(OAuthContract, 1)] + [contract(OAuthContract, 1), feature(Feature_OAuth)] enum TokenFailureKind { // The server responded with an error response as described by RFC 6749 section 5.2. This means that the failure @@ -413,7 +415,7 @@ namespace Microsoft.Windows.Security.Authentication.OAuth InvalidResponse = 2, }; - [contract(OAuthContract, 1)] + [contract(OAuthContract, 1), feature(Feature_OAuth)] runtimeclass TokenFailure { // Indicates the type of failure that this object describes, which will indicate which properties might be set. @@ -447,7 +449,7 @@ namespace Microsoft.Windows.Security.Authentication.OAuth Windows.Foundation.Collections.IMapView AdditionalParams { get; }; } - [contract(OAuthContract, 1)] + [contract(OAuthContract, 1), feature(Feature_OAuth)] runtimeclass TokenRequestResult { // The raw HTTP response that was used to complete the request diff --git a/dev/OAuth/OAuth.vcxitems b/dev/OAuth/OAuth.vcxitems index 1a6c96beae..3f7dfc992f 100644 --- a/dev/OAuth/OAuth.vcxitems +++ b/dev/OAuth/OAuth.vcxitems @@ -1,4 +1,4 @@ - + $(MSBuildAllProjects);$(MSBuildThisFileFullPath) @@ -18,6 +18,9 @@ + + + @@ -82,4 +85,4 @@ - + \ No newline at end of file diff --git a/dev/OAuth/TokenFailure.cpp b/dev/OAuth/TokenFailure.cpp index b4164e2317..58fc77cb70 100644 --- a/dev/OAuth/TokenFailure.cpp +++ b/dev/OAuth/TokenFailure.cpp @@ -2,6 +2,7 @@ #include "common.h" #include "TokenFailure.h" + #include using namespace std::literals; diff --git a/dev/OAuth/TokenRequestParams.cpp b/dev/OAuth/TokenRequestParams.cpp index 48565611f9..5d8925568f 100644 --- a/dev/OAuth/TokenRequestParams.cpp +++ b/dev/OAuth/TokenRequestParams.cpp @@ -1,10 +1,10 @@ -#include +#include #include "common.h" +#include "AuthResponse.h" #include "TokenRequestParams.h" -#include -#include "AuthResponse.h" +#include using namespace winrt::Microsoft::Windows::Security::Authentication::OAuth; using namespace winrt::Windows::Foundation; @@ -12,7 +12,10 @@ using namespace Collections; namespace winrt::Microsoft::Windows::Security::Authentication::OAuth::implementation { - TokenRequestParams::TokenRequestParams(const winrt::hstring& grantType) : m_grantType(grantType) {} + TokenRequestParams::TokenRequestParams(const winrt::hstring& grantType) : m_grantType(grantType) + { + THROW_HR_IF(E_NOTIMPL, !::Microsoft::Windows::Security::Authentication::OAuth::Feature_OAuth::IsEnabled()); + } oauth::TokenRequestParams TokenRequestParams::CreateForAuthorizationCodeRequest( const oauth::AuthResponse& authResponse) diff --git a/dev/OAuth/TokenRequestResult.cpp b/dev/OAuth/TokenRequestResult.cpp index 670a156ad3..b2515f2570 100644 --- a/dev/OAuth/TokenRequestResult.cpp +++ b/dev/OAuth/TokenRequestResult.cpp @@ -1,12 +1,12 @@ #include #include "common.h" -#include "TokenRequestResult.h" -#include - #include "TokenFailure.h" +#include "TokenRequestResult.h" #include "TokenResponse.h" +#include + using namespace winrt::Microsoft::Windows::Security::Authentication::OAuth; using namespace winrt::Windows::Data::Json; using namespace winrt::Windows::Foundation; diff --git a/dev/OAuth/TokenResponse.cpp b/dev/OAuth/TokenResponse.cpp index 9b62e79100..35b6196756 100644 --- a/dev/OAuth/TokenResponse.cpp +++ b/dev/OAuth/TokenResponse.cpp @@ -2,6 +2,7 @@ #include "common.h" #include "TokenResponse.h" + #include using namespace winrt::Microsoft::Windows::Security::Authentication::OAuth; diff --git a/dev/OAuth/common.h b/dev/OAuth/common.h index 3753e749c5..c04d6f3416 100644 --- a/dev/OAuth/common.h +++ b/dev/OAuth/common.h @@ -9,6 +9,8 @@ #include #include +#include + namespace collections = winrt::Windows::Foundation::Collections; namespace crypto = winrt::Windows::Security::Cryptography; namespace foundation = winrt::Windows::Foundation; diff --git a/docs/Coding-Guidelines/Experimental.md b/docs/Coding-Guidelines/Experimental.md index 5a76bdeeb2..fc47bd5785 100644 --- a/docs/Coding-Guidelines/Experimental.md +++ b/docs/Coding-Guidelines/Experimental.md @@ -14,7 +14,7 @@ The implementation of experimental features in Windows App SDK makes heavy use o in different release channels. See the TerminalVelocity document for detailed information on how to implement an experimental feature using a specific technology (e.g. C++, IDL, WinRT). -Your should ensure that your feature's state is disabled in Preview and Stable release channels, and +You should ensure that your feature's state is disabled in Preview and Stable release channels, and enabled in the Experimental release channel. # Stripping experimental APIs from WinRT metadata (.winmd) diff --git a/test/OAuthTests/OAuthTests.cpp b/test/OAuthTests/OAuthTests.cpp index 469c91452f..b8997eb055 100644 --- a/test/OAuthTests/OAuthTests.cpp +++ b/test/OAuthTests/OAuthTests.cpp @@ -21,6 +21,8 @@ #include #include +#include + #include "OAuthTestValues.h" // NOTE: Thise files don't include verything they need, hence they are here last @@ -51,6 +53,12 @@ struct OAuthTests TEST_CLASS_SETUP(Setup) { + if (!::Microsoft::Windows::Security::Authentication::OAuth::Feature_OAuth::IsEnabled()) + { + Log::Result(TestResults::Skipped, L"OAuth API Features are not enabled."); + return true; + } + Test::Bootstrap::Setup(); Test::Packages::WapProj::AddPackage(Test::TAEF::GetDeploymentDir(), L"OAuthTestAppPackage", L".msix"); diff --git a/test/OAuthTests/OAuthTests.vcxproj b/test/OAuthTests/OAuthTests.vcxproj index 01d6783caf..c0dabfa349 100644 --- a/test/OAuthTests/OAuthTests.vcxproj +++ b/test/OAuthTests/OAuthTests.vcxproj @@ -66,6 +66,7 @@ true ..\..\dev\Detours; + ..\..\dev\Common; $(OutDir)\..\WindowsAppRuntime_DLL; $(OutDir)\..\WindowsAppRuntime_BootstrapDLL; %(AdditionalIncludeDirectories) From 6c7e5099aee7b5483e71d87036705a5f58a5648a Mon Sep 17 00:00:00 2001 From: Duncan Horn Date: Wed, 30 Nov 2022 14:29:51 -0800 Subject: [PATCH 6/9] Add projection --- WindowsAppRuntime.sln | 19 +++++++ build/CopyFilesToStagingDir.ps1 | 4 ++ ...ity.Authentication.OAuth.Projection.csproj | 50 +++++++++++++++++++ 3 files changed, 73 insertions(+) create mode 100644 dev/Projections/CS/Microsoft.Windows.Security.Authentication.OAuth/Microsoft.Windows.Security.Authentication.OAuth.Projection.csproj diff --git a/WindowsAppRuntime.sln b/WindowsAppRuntime.sln index 7d4e67f9e3..e36352d42c 100644 --- a/WindowsAppRuntime.sln +++ b/WindowsAppRuntime.sln @@ -433,6 +433,8 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "OAuthTestApp", "test\TestAp EndProject Project("{C7167F0D-BC9F-4E6E-AFE1-012C56B48DB5}") = "OAuthTestAppPackage", "test\TestApps\OAuthTestAppPackage\OAuthTestAppPackage.wapproj", "{C4454D2C-8024-41B8-BAC1-FC2E544C810F}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Windows.Security.Authentication.OAuth.Projection", "dev\Projections\CS\Microsoft.Windows.Security.Authentication.OAuth\Microsoft.Windows.Security.Authentication.OAuth.Projection.csproj", "{E7283533-E1C6-4843-B988-13D95BAB2B9A}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -1651,6 +1653,22 @@ Global {C4454D2C-8024-41B8-BAC1-FC2E544C810F}.Release|x86.ActiveCfg = Release|x86 {C4454D2C-8024-41B8-BAC1-FC2E544C810F}.Release|x86.Build.0 = Release|x86 {C4454D2C-8024-41B8-BAC1-FC2E544C810F}.Release|x86.Deploy.0 = Release|x86 + {E7283533-E1C6-4843-B988-13D95BAB2B9A}.Debug|Any CPU.ActiveCfg = Debug|x64 + {E7283533-E1C6-4843-B988-13D95BAB2B9A}.Debug|Any CPU.Build.0 = Debug|x64 + {E7283533-E1C6-4843-B988-13D95BAB2B9A}.Debug|ARM64.ActiveCfg = Debug|arm64 + {E7283533-E1C6-4843-B988-13D95BAB2B9A}.Debug|ARM64.Build.0 = Debug|arm64 + {E7283533-E1C6-4843-B988-13D95BAB2B9A}.Debug|x64.ActiveCfg = Debug|x64 + {E7283533-E1C6-4843-B988-13D95BAB2B9A}.Debug|x64.Build.0 = Debug|x64 + {E7283533-E1C6-4843-B988-13D95BAB2B9A}.Debug|x86.ActiveCfg = Debug|x86 + {E7283533-E1C6-4843-B988-13D95BAB2B9A}.Debug|x86.Build.0 = Debug|x86 + {E7283533-E1C6-4843-B988-13D95BAB2B9A}.Release|Any CPU.ActiveCfg = Release|x64 + {E7283533-E1C6-4843-B988-13D95BAB2B9A}.Release|Any CPU.Build.0 = Release|x64 + {E7283533-E1C6-4843-B988-13D95BAB2B9A}.Release|ARM64.ActiveCfg = Release|arm64 + {E7283533-E1C6-4843-B988-13D95BAB2B9A}.Release|ARM64.Build.0 = Release|arm64 + {E7283533-E1C6-4843-B988-13D95BAB2B9A}.Release|x64.ActiveCfg = Release|x64 + {E7283533-E1C6-4843-B988-13D95BAB2B9A}.Release|x64.Build.0 = Release|x64 + {E7283533-E1C6-4843-B988-13D95BAB2B9A}.Release|x86.ActiveCfg = Release|x86 + {E7283533-E1C6-4843-B988-13D95BAB2B9A}.Release|x86.Build.0 = Release|x86 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -1791,6 +1809,7 @@ Global {21651459-648E-475C-91DB-2BDE359C75A4} = {8630F7AA-2969-4DC9-8700-9B468C1DC21D} {077BDBFD-C1AA-49C8-BD62-7C14221C45F2} = {AC5FFC80-92FE-4933-BED2-EC5519AC4440} {C4454D2C-8024-41B8-BAC1-FC2E544C810F} = {AC5FFC80-92FE-4933-BED2-EC5519AC4440} + {E7283533-E1C6-4843-B988-13D95BAB2B9A} = {716C26A0-E6B0-4981-8412-D14A4D410531} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {4B3D7591-CFEC-4762-9A07-ABE99938FB77} diff --git a/build/CopyFilesToStagingDir.ps1 b/build/CopyFilesToStagingDir.ps1 index 73d84a35b4..ec091ce75e 100644 --- a/build/CopyFilesToStagingDir.ps1 +++ b/build/CopyFilesToStagingDir.ps1 @@ -48,6 +48,7 @@ PublishFile $FullBuildOutput\WindowsAppRuntime_DLL\StrippedWinMD\Microsoft.Windo PublishFile $FullBuildOutput\WindowsAppRuntime_DLL\StrippedWinMD\Microsoft.Windows.System.winmd $FullPublishDir\Microsoft.WindowsAppRuntime\ PublishFile $FullBuildOutput\WindowsAppRuntime_DLL\StrippedWinMD\Microsoft.Windows.System.Power.winmd $FullPublishDir\Microsoft.WindowsAppRuntime\ PublishFile $FullBuildOutput\WindowsAppRuntime_DLL\StrippedWinMD\Microsoft.Windows.Security.AccessControl.winmd $FullPublishDir\Microsoft.WindowsAppRuntime\ +PublishFile $FullBuildOutput\WindowsAppRuntime_DLL\StrippedWinMD\Microsoft.Windows.Security.Authentication.OAuth.winmd $FullPublishDir\Microsoft.WindowsAppRuntime\ PublishFile $FullBuildOutput\WindowsAppRuntime_DLL\MsixDynamicDependency.h $FullPublishDir\Microsoft.WindowsAppRuntime\ PublishFile $FullBuildOutput\WindowsAppRuntime_DLL\wil_msixdynamicdependency.h $FullPublishDir\Microsoft.WindowsAppRuntime\ PublishFile $FullBuildOutput\WindowsAppRuntime_DLL\Security.AccessControl.h $FullPublishDir\Microsoft.WindowsAppRuntime\ @@ -116,6 +117,8 @@ PublishFile $FullBuildOutput\Microsoft.Windows.System.Power.Projection\Microsoft PublishFile $FullBuildOutput\Microsoft.Windows.System.Power.Projection\Microsoft.Windows.System.Power.Projection.pdb $NugetDir\lib\net6.0-windows10.0.17763.0 PublishFile $FullBuildOutput\Microsoft.Windows.Security.AccessControl.Projection\Microsoft.Windows.Security.AccessControl.Projection.dll $NugetDir\lib\net6.0-windows10.0.17763.0 PublishFile $FullBuildOutput\Microsoft.Windows.Security.AccessControl.Projection\Microsoft.Windows.Security.AccessControl.Projection.pdb $NugetDir\lib\net6.0-windows10.0.17763.0 +PublishFile $FullBuildOutput\Microsoft.Windows.Security.Authentication.OAuth.Projection\Microsoft.Windows.Security.Authentication.OAuth.Projection.dll $NugetDir\lib\net6.0-windows10.0.17763.0 +PublishFile $FullBuildOutput\Microsoft.Windows.Security.Authentication.OAuth.Projection\Microsoft.Windows.Security.Authentication.OAuth.Projection.pdb $NugetDir\lib\net6.0-windows10.0.17763.0 # # Dynamic Dependency build overrides @@ -183,6 +186,7 @@ PublishFile $FullBuildOutput\WindowsAppRuntime_DLL\StrippedWinMD\Microsoft.Windo PublishFile $FullBuildOutput\WindowsAppRuntime_DLL\StrippedWinMD\Microsoft.Windows.System.winmd $NugetDir\lib\uap10.0 PublishFile $FullBuildOutput\WindowsAppRuntime_DLL\StrippedWinMD\Microsoft.Windows.System.Power.winmd $NugetDir\lib\uap10.0 PublishFile $FullBuildOutput\WindowsAppRuntime_DLL\StrippedWinMD\Microsoft.Windows.Security.AccessControl.winmd $NugetDir\lib\uap10.0 +PublishFile $FullBuildOutput\WindowsAppRuntime_DLL\StrippedWinMD\Microsoft.Windows.Security.Authentication.OAuth.winmd $NugetDir\lib\uap10.0 # # Bootstrap Auto-Initializer Files PublishFile $FullBuildOutput\WindowsAppRuntime_BootstrapDLL\MddBootstrapAutoInitializer.cpp $NugetDir\include diff --git a/dev/Projections/CS/Microsoft.Windows.Security.Authentication.OAuth/Microsoft.Windows.Security.Authentication.OAuth.Projection.csproj b/dev/Projections/CS/Microsoft.Windows.Security.Authentication.OAuth/Microsoft.Windows.Security.Authentication.OAuth.Projection.csproj new file mode 100644 index 0000000000..401aeaaa1a --- /dev/null +++ b/dev/Projections/CS/Microsoft.Windows.Security.Authentication.OAuth/Microsoft.Windows.Security.Authentication.OAuth.Projection.csproj @@ -0,0 +1,50 @@ + + + net6.0-windows10.0.17763.0 + 10.0.17763.0 + x64;x86;arm64 + AnyCPU + + + + true + true + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + Microsoft.Windows.Security.Authentication.OAuth + 10.0.17763.0 + 10.0.17763.$(CsWinRTDependencyWindowsSdkVersionSuffixPackageVersion) + false + + + + + pdbonly + true + + + + + + + + + $(OutDir)..\WindowsAppRuntime_DLL\StrippedWinMD\Microsoft.Windows.Security.Authentication.OAuth.winmd + true + + + + From daae6c92cd03d8cc18f322a572e08e5ba1f246a9 Mon Sep 17 00:00:00 2001 From: Duncan Horn Date: Wed, 30 Nov 2022 15:35:18 -0800 Subject: [PATCH 7/9] Complete merge --- .../WindowsAppSDK-SetupBuildEnvironment-Steps.yml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/build/AzurePipelinesTemplates/WindowsAppSDK-SetupBuildEnvironment-Steps.yml b/build/AzurePipelinesTemplates/WindowsAppSDK-SetupBuildEnvironment-Steps.yml index 85e9686094..5610d8602b 100644 --- a/build/AzurePipelinesTemplates/WindowsAppSDK-SetupBuildEnvironment-Steps.yml +++ b/build/AzurePipelinesTemplates/WindowsAppSDK-SetupBuildEnvironment-Steps.yml @@ -70,6 +70,14 @@ steps: arguments: -Path $(Build.SourcesDirectory)\dev\common\TerminalVelocityFeatures-EnvironmentManager.xml -Channel $(channel) -Language C++ -Namespace Microsoft.Windows.System -Output $(Build.SourcesDirectory)\dev\common\TerminalVelocityFeatures-EnvironmentManager.h workingDirectory: '$(Build.SourcesDirectory)' +- task: powershell@2 + displayName: 'Create OAuth TerminalVelocity features' + inputs: + targetType: filePath + filePath: tools\TerminalVelocity\Generate-TerminalVelocityFeatures.ps1 + arguments: -Path $(Build.SourcesDirectory)\dev\common\TerminalVelocityFeatures-OAuth.xml -Channel ${{ parameters.channel }} -Language C++ -Namespace Microsoft.Windows.Security.Authentication.OAuth -Output $(Build.SourcesDirectory)\dev\common\TerminalVelocityFeatures-OAuth.h + workingDirectory: '$(Build.SourcesDirectory)' + - task: powershell@2 name: UpdateTraceloggingConfig inputs: From d0014a04cbebc9fdadd907c2a5af73bb115271de Mon Sep 17 00:00:00 2001 From: Duncan Horn Date: Wed, 30 Nov 2022 16:24:39 -0800 Subject: [PATCH 8/9] More merge fixes --- WindowsAppRuntime.sln | 25 ++++++++++++++++++++----- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/WindowsAppRuntime.sln b/WindowsAppRuntime.sln index d1f06a0259..1cd17725a8 100644 --- a/WindowsAppRuntime.sln +++ b/WindowsAppRuntime.sln @@ -253,7 +253,6 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "ToastNotificationTests", "t {A7391725-4EF5-438F-8DE1-645423E46955} = {A7391725-4EF5-438F-8DE1-645423E46955} {B71E818A-882E-456A-87E5-4DE4A6602B99} = {B71E818A-882E-456A-87E5-4DE4A6602B99} {B73AD907-6164-4294-88FB-F3C9C10DA1F1} = {B73AD907-6164-4294-88FB-F3C9C10DA1F1} - {C422B090-F501-4204-8068-1084B0799405} = {C422B090-F501-4204-8068-1084B0799405} {D6A64926-4988-4C64-A5A8-2C14B1388696} = {D6A64926-4988-4C64-A5A8-2C14B1388696} {F76B776E-86F5-48C5-8FC7-D2795ECC9746} = {F76B776E-86F5-48C5-8FC7-D2795ECC9746} EndProjectSection @@ -301,7 +300,6 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "ToastNotificationsDemoApp", {A7391725-4EF5-438F-8DE1-645423E46955} = {A7391725-4EF5-438F-8DE1-645423E46955} {B71E818A-882E-456A-87E5-4DE4A6602B99} = {B71E818A-882E-456A-87E5-4DE4A6602B99} {B73AD907-6164-4294-88FB-F3C9C10DA1F1} = {B73AD907-6164-4294-88FB-F3C9C10DA1F1} - {C422B090-F501-4204-8068-1084B0799405} = {C422B090-F501-4204-8068-1084B0799405} EndProjectSection EndProject Project("{C7167F0D-BC9F-4E6E-AFE1-012C56B48DB5}") = "ToastNotificationsDemoAppPackage", "test\TestApps\ToastNotificationsDemoAppPackage\ToastNotificationsDemoAppPackage.wapproj", "{E695A08E-8735-41CD-AE55-A5B589BA297F}" @@ -411,6 +409,7 @@ EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "OAuth", "OAuth", "{4A3ACD67-5C57-474D-87BF-675676D7451A}" EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "OAuth", "dev\OAuth\OAuth.vcxitems", "{3E7FD510-8B66-40E7-A80B-780CB8972F83}" +EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "NotificationTests", "NotificationTests", "{1FDC307C-2DB7-4B40-8F18-F1057E9E0969}" EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "LRPTests", "test\LRPTests\LRPTests.vcxproj", "{978B013F-9B68-4B3E-8DA4-6F3BE4EB22B4}" @@ -421,10 +420,10 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "VersionInfo", "dev\VersionI EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "VersionInfoTests", "test\VersionInfo\VersionInfoTests.vcxproj", "{442FB943-1197-48FE-B3B6-8C1BCA1E81E4}" ProjectSection(ProjectDependencies) = postProject - {0B01DB78-F115-4C90-B28F-7819071303C6} = {0B01DB78-F115-4C90-B28F-7819071303C6} {34519337-9249-451E-B5A9-1ECACF9C3DA8} = {34519337-9249-451E-B5A9-1ECACF9C3DA8} - {5E2CC9D5-7C05-41D9-9DB5-EC5DF64BA1DC} = {5E2CC9D5-7C05-41D9-9DB5-EC5DF64BA1DC} {9C1A6C58-52D6-4514-9120-5C339C5DF4BE} = {9C1A6C58-52D6-4514-9120-5C339C5DF4BE} + {0B01DB78-F115-4C90-B28F-7819071303C6} = {0B01DB78-F115-4C90-B28F-7819071303C6} + {5E2CC9D5-7C05-41D9-9DB5-EC5DF64BA1DC} = {5E2CC9D5-7C05-41D9-9DB5-EC5DF64BA1DC} EndProjectSection EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "OAuthTests", "test\OAuthTests\OAuthTests.vcxproj", "{21651459-648E-475C-91DB-2BDE359C75A4}" @@ -1092,6 +1091,22 @@ Global {6539E9E1-BF36-40E5-86BC-070E99DB7B7B}.Release|x64.Build.0 = Release|x64 {6539E9E1-BF36-40E5-86BC-070E99DB7B7B}.Release|x86.ActiveCfg = Release|Win32 {6539E9E1-BF36-40E5-86BC-070E99DB7B7B}.Release|x86.Build.0 = Release|Win32 + {E977B1BD-00DC-4085-A105-E0A18E0183D7}.Debug|Any CPU.ActiveCfg = Debug|x64 + {E977B1BD-00DC-4085-A105-E0A18E0183D7}.Debug|Any CPU.Build.0 = Debug|x64 + {E977B1BD-00DC-4085-A105-E0A18E0183D7}.Debug|ARM64.ActiveCfg = Debug|ARM64 + {E977B1BD-00DC-4085-A105-E0A18E0183D7}.Debug|ARM64.Build.0 = Debug|ARM64 + {E977B1BD-00DC-4085-A105-E0A18E0183D7}.Debug|x64.ActiveCfg = Debug|x64 + {E977B1BD-00DC-4085-A105-E0A18E0183D7}.Debug|x64.Build.0 = Debug|x64 + {E977B1BD-00DC-4085-A105-E0A18E0183D7}.Debug|x86.ActiveCfg = Debug|Win32 + {E977B1BD-00DC-4085-A105-E0A18E0183D7}.Debug|x86.Build.0 = Debug|Win32 + {E977B1BD-00DC-4085-A105-E0A18E0183D7}.Release|Any CPU.ActiveCfg = Release|x64 + {E977B1BD-00DC-4085-A105-E0A18E0183D7}.Release|Any CPU.Build.0 = Release|x64 + {E977B1BD-00DC-4085-A105-E0A18E0183D7}.Release|ARM64.ActiveCfg = Release|ARM64 + {E977B1BD-00DC-4085-A105-E0A18E0183D7}.Release|ARM64.Build.0 = Release|ARM64 + {E977B1BD-00DC-4085-A105-E0A18E0183D7}.Release|x64.ActiveCfg = Release|x64 + {E977B1BD-00DC-4085-A105-E0A18E0183D7}.Release|x64.Build.0 = Release|x64 + {E977B1BD-00DC-4085-A105-E0A18E0183D7}.Release|x86.ActiveCfg = Release|Win32 + {E977B1BD-00DC-4085-A105-E0A18E0183D7}.Release|x86.Build.0 = Release|Win32 {4B30C685-8490-440F-9879-A75D45DAA361}.Debug|Any CPU.ActiveCfg = Debug|Win32 {4B30C685-8490-440F-9879-A75D45DAA361}.Debug|ARM64.ActiveCfg = Debug|ARM64 {4B30C685-8490-440F-9879-A75D45DAA361}.Debug|ARM64.Build.0 = Debug|ARM64 @@ -1806,7 +1821,6 @@ Global test\inc\inc.vcxitems*{08bc78e0-63c6-49a7-81b3-6afc3deac4de}*SharedItemsImports = 4 dev\PushNotifications\PushNotifications.vcxitems*{103c0c23-7ba8-4d44-a63c-83488e2e3a81}*SharedItemsImports = 9 test\inc\inc.vcxitems*{21651459-648e-475c-91db-2bde359c75a4}*SharedItemsImports = 4 - test\inc\inc.vcxitems*{2cd5cd9b-cf45-4fa7-9769-ee4e02426bf0}*SharedItemsImports = 4 dev\EnvironmentManager\API\Microsoft.Process.Environment.vcxitems*{2f3fad1b-d3df-4866-a3a3-c2c777d55638}*SharedItemsImports = 9 dev\OAuth\OAuth.vcxitems*{3e7fd510-8b66-40e7-a80b-780cb8972f83}*SharedItemsImports = 9 test\inc\inc.vcxitems*{412d023e-8635-4ad2-a0ea-e19e08d36915}*SharedItemsImports = 4 @@ -1837,5 +1851,6 @@ Global dev\VersionInfo\VersionInfo.vcxitems*{e3edec7f-a24e-4766-bb1d-6bdfba157c51}*SharedItemsImports = 9 dev\AppNotifications\AppNotificationBuilder\AppNotificationBuilder.vcxitems*{e49329f3-5196-4bba-b5c4-e11ce7efb07a}*SharedItemsImports = 9 test\inc\inc.vcxitems*{e5659a29-fe68-417b-9bc5-613073dd54df}*SharedItemsImports = 4 + test\inc\inc.vcxitems*{e977b1bd-00dc-4085-a105-e0a18e0183d7}*SharedItemsImports = 4 EndGlobalSection EndGlobal From a4f59c3d79f8a5db0ae4967ee49e48bceeb2c5df Mon Sep 17 00:00:00 2001 From: Duncan Horn Date: Thu, 1 Dec 2022 10:54:59 -0800 Subject: [PATCH 9/9] Remove dead references causing issues --- WindowsAppRuntime.sln | 28 -- .../AppNotification-Test-Constants.h | 7 - .../ToastNotificationTests.vcxproj | 254 ------------------ 3 files changed, 289 deletions(-) delete mode 100644 test/ToastNotificationTests/AppNotification-Test-Constants.h delete mode 100644 test/ToastNotificationTests/ToastNotificationTests.vcxproj diff --git a/WindowsAppRuntime.sln b/WindowsAppRuntime.sln index 1cd17725a8..0910c03970 100644 --- a/WindowsAppRuntime.sln +++ b/WindowsAppRuntime.sln @@ -246,17 +246,6 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Framework.Widgets", "test\D EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "DynamicDependencyLifetimeManagerShadow", "dev\DynamicDependency\DynamicDependencyLifetimeManagerShadow\DynamicDependencyLifetimeManagerShadow.vcxproj", "{6539E9E1-BF36-40E5-86BC-070E99DB7B7B}" EndProject -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "ToastNotificationTests", "test\ToastNotificationTests\ToastNotificationTests.vcxproj", "{E977B1BD-00DC-4085-A105-E0A18E0183D7}" - ProjectSection(ProjectDependencies) = postProject - {4B30C685-8490-440F-9879-A75D45DAA361} = {4B30C685-8490-440F-9879-A75D45DAA361} - {9C1A6C58-52D6-4514-9120-5C339C5DF4BE} = {9C1A6C58-52D6-4514-9120-5C339C5DF4BE} - {A7391725-4EF5-438F-8DE1-645423E46955} = {A7391725-4EF5-438F-8DE1-645423E46955} - {B71E818A-882E-456A-87E5-4DE4A6602B99} = {B71E818A-882E-456A-87E5-4DE4A6602B99} - {B73AD907-6164-4294-88FB-F3C9C10DA1F1} = {B73AD907-6164-4294-88FB-F3C9C10DA1F1} - {D6A64926-4988-4C64-A5A8-2C14B1388696} = {D6A64926-4988-4C64-A5A8-2C14B1388696} - {F76B776E-86F5-48C5-8FC7-D2795ECC9746} = {F76B776E-86F5-48C5-8FC7-D2795ECC9746} - EndProjectSection -EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "ToastNotificationsTestApp", "test\TestApps\ToastNotificationsTestApp\ToastNotificationsTestApp.vcxproj", "{4B30C685-8490-440F-9879-A75D45DAA361}" ProjectSection(ProjectDependencies) = postProject {9C1A6C58-52D6-4514-9120-5C339C5DF4BE} = {9C1A6C58-52D6-4514-9120-5C339C5DF4BE} @@ -1091,22 +1080,6 @@ Global {6539E9E1-BF36-40E5-86BC-070E99DB7B7B}.Release|x64.Build.0 = Release|x64 {6539E9E1-BF36-40E5-86BC-070E99DB7B7B}.Release|x86.ActiveCfg = Release|Win32 {6539E9E1-BF36-40E5-86BC-070E99DB7B7B}.Release|x86.Build.0 = Release|Win32 - {E977B1BD-00DC-4085-A105-E0A18E0183D7}.Debug|Any CPU.ActiveCfg = Debug|x64 - {E977B1BD-00DC-4085-A105-E0A18E0183D7}.Debug|Any CPU.Build.0 = Debug|x64 - {E977B1BD-00DC-4085-A105-E0A18E0183D7}.Debug|ARM64.ActiveCfg = Debug|ARM64 - {E977B1BD-00DC-4085-A105-E0A18E0183D7}.Debug|ARM64.Build.0 = Debug|ARM64 - {E977B1BD-00DC-4085-A105-E0A18E0183D7}.Debug|x64.ActiveCfg = Debug|x64 - {E977B1BD-00DC-4085-A105-E0A18E0183D7}.Debug|x64.Build.0 = Debug|x64 - {E977B1BD-00DC-4085-A105-E0A18E0183D7}.Debug|x86.ActiveCfg = Debug|Win32 - {E977B1BD-00DC-4085-A105-E0A18E0183D7}.Debug|x86.Build.0 = Debug|Win32 - {E977B1BD-00DC-4085-A105-E0A18E0183D7}.Release|Any CPU.ActiveCfg = Release|x64 - {E977B1BD-00DC-4085-A105-E0A18E0183D7}.Release|Any CPU.Build.0 = Release|x64 - {E977B1BD-00DC-4085-A105-E0A18E0183D7}.Release|ARM64.ActiveCfg = Release|ARM64 - {E977B1BD-00DC-4085-A105-E0A18E0183D7}.Release|ARM64.Build.0 = Release|ARM64 - {E977B1BD-00DC-4085-A105-E0A18E0183D7}.Release|x64.ActiveCfg = Release|x64 - {E977B1BD-00DC-4085-A105-E0A18E0183D7}.Release|x64.Build.0 = Release|x64 - {E977B1BD-00DC-4085-A105-E0A18E0183D7}.Release|x86.ActiveCfg = Release|Win32 - {E977B1BD-00DC-4085-A105-E0A18E0183D7}.Release|x86.Build.0 = Release|Win32 {4B30C685-8490-440F-9879-A75D45DAA361}.Debug|Any CPU.ActiveCfg = Debug|Win32 {4B30C685-8490-440F-9879-A75D45DAA361}.Debug|ARM64.ActiveCfg = Debug|ARM64 {4B30C685-8490-440F-9879-A75D45DAA361}.Debug|ARM64.Build.0 = Debug|ARM64 @@ -1851,6 +1824,5 @@ Global dev\VersionInfo\VersionInfo.vcxitems*{e3edec7f-a24e-4766-bb1d-6bdfba157c51}*SharedItemsImports = 9 dev\AppNotifications\AppNotificationBuilder\AppNotificationBuilder.vcxitems*{e49329f3-5196-4bba-b5c4-e11ce7efb07a}*SharedItemsImports = 9 test\inc\inc.vcxitems*{e5659a29-fe68-417b-9bc5-613073dd54df}*SharedItemsImports = 4 - test\inc\inc.vcxitems*{e977b1bd-00dc-4085-a105-e0a18e0183d7}*SharedItemsImports = 4 EndGlobalSection EndGlobal diff --git a/test/ToastNotificationTests/AppNotification-Test-Constants.h b/test/ToastNotificationTests/AppNotification-Test-Constants.h deleted file mode 100644 index e496d48b04..0000000000 --- a/test/ToastNotificationTests/AppNotification-Test-Constants.h +++ /dev/null @@ -1,7 +0,0 @@ -// Copyright (c) Microsoft Corporation and Contributors. -// Licensed under the MIT License. -#pragma once - -inline const winrt::hstring c_rawNotificationPayload = L""; -inline const std::chrono::seconds c_timeout = std::chrono::seconds(300); -inline IID c_toastComServerId = winrt::guid("1940dba9-0f64-4f0d-8a4b-5d207b812e61"); // Value from ToastNotificationsTestAppPackage ComActivator in appxmanifest. diff --git a/test/ToastNotificationTests/ToastNotificationTests.vcxproj b/test/ToastNotificationTests/ToastNotificationTests.vcxproj deleted file mode 100644 index b7503081a0..0000000000 --- a/test/ToastNotificationTests/ToastNotificationTests.vcxproj +++ /dev/null @@ -1,254 +0,0 @@ - - - - - - Debug - Win32 - - - Release - Win32 - - - Debug - x64 - - - Release - x64 - - - Debug - ARM64 - - - Release - ARM64 - - - - 16.0 - Win32Proj - {e977b1bd-00dc-4085-a105-e0a18e0183d7} - ToastNotificationTests - 10.0 - - - - DynamicLibrary - true - v143 - Unicode - - - DynamicLibrary - false - v143 - Unicode - - - DynamicLibrary - true - v143 - Unicode - - - DynamicLibrary - false - v143 - Unicode - - - DynamicLibrary - true - v143 - Unicode - false - - - DynamicLibrary - false - v143 - Unicode - false - - - - - - - - - - - - - - - - - - - - - - - - - - - - - %(AdditionalIncludeDirectories);$(OutDir)\..\WindowsAppRuntime_DLL;..\inc;$(OutDir)\..\WindowsAppRuntime_BootstrapDLL - WIN32;_DEBUG;TOASTNOTIFICATIONTESTS_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) - Use - pch.h - - - Windows - $(VCInstallDir)UnitTest\lib;%(AdditionalLibraryDirectories);$(OutDir)\..\WindowsAppRuntime_DLL - onecore.lib;onecoreuap.lib;Microsoft.WindowsAppRuntime.lib;wex.common.lib;wex.logger.lib;te.common.lib;%(AdditionalDependencies) - false - - - - - %(AdditionalIncludeDirectories);$(OutDir)\..\WindowsAppRuntime_DLL;..\inc;$(OutDir)\..\WindowsAppRuntime_BootstrapDLL - WIN32;NDEBUG;TOASTNOTIFICATIONTESTS_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) - true - Use - pch.h - - - Windows - $(VCInstallDir)UnitTest\lib;%(AdditionalLibraryDirectories);$(OutDir)\..\WindowsAppRuntime_DLL - onecore.lib;onecoreuap.lib;Microsoft.WindowsAppRuntime.lib;wex.common.lib;wex.logger.lib;te.common.lib;%(AdditionalDependencies) - false - - - - - %(AdditionalIncludeDirectories);$(OutDir)\..\WindowsAppRuntime_DLL;..\inc;$(OutDir)\..\WindowsAppRuntime_BootstrapDLL - _DEBUG;TOASTNOTIFICATIONTESTS_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) - true - Use - pch.h - - - Windows - $(VCInstallDir)UnitTest\lib;%(AdditionalLibraryDirectories);$(OutDir)\..\WindowsAppRuntime_DLL - onecore.lib;onecoreuap.lib;Microsoft.WindowsAppRuntime.lib;wex.common.lib;wex.logger.lib;te.common.lib;%(AdditionalDependencies) - false - - - - - %(AdditionalIncludeDirectories);$(OutDir)\..\WindowsAppRuntime_DLL;..\inc;$(OutDir)\..\WindowsAppRuntime_BootstrapDLL - NDEBUG;TOASTNOTIFICATIONTESTS_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) - Use - pch.h - - - Windows - $(VCInstallDir)UnitTest\lib;%(AdditionalLibraryDirectories);$(OutDir)\..\WindowsAppRuntime_DLL - onecore.lib;onecoreuap.lib;Microsoft.WindowsAppRuntime.lib;wex.common.lib;wex.logger.lib;te.common.lib;%(AdditionalDependencies) - false - - - - - Use - %(AdditionalIncludeDirectories);$(OutDir)\..\WindowsAppRuntime_DLL;..\inc;$(OutDir)\..\WindowsAppRuntime_BootstrapDLL - _DEBUG;%(PreprocessorDefinitions);;INLINE_TEST_METHOD_MARKUP - true - pch.h - - - Windows - $(VCInstallDir)UnitTest\lib;%(AdditionalLibraryDirectories);$(OutDir)\..\WindowsAppRuntime_DLL - onecore.lib;onecoreuap.lib;Microsoft.WindowsAppRuntime.lib;wex.common.lib;wex.logger.lib;te.common.lib;%(AdditionalDependencies) - - - - - Use - %(AdditionalIncludeDirectories);$(OutDir)\..\WindowsAppRuntime_DLL;..\inc;$(OutDir)\..\WindowsAppRuntime_BootstrapDLL - NDEBUG;%(PreprocessorDefinitions);;INLINE_TEST_METHOD_MARKUP - true - pch.h - - - Windows - $(VCInstallDir)UnitTest\lib;%(AdditionalLibraryDirectories);$(OutDir)\..\WindowsAppRuntime_DLL - onecore.lib;onecoreuap.lib;Microsoft.WindowsAppRuntime.lib;wex.common.lib;wex.logger.lib;te.common.lib;%(AdditionalDependencies) - - - - - - - - - - Create - Create - Create - Create - Create - Create - Create - Create - - - - - - - .Debug - _Debug - $(AppxPackageDir)\ToastNotificationsTestAppPackage_1.0.0.0_$(PlatformTarget)$(TestPkgDebugConfigName)_Test - $(TestPkgOutputPath)\ToastNotificationsTestAppPackage_1.0.0.0_$(PlatformTarget)$(TestPkgDebugConfigName).msix - - - - - - - - - $(OutDir)\..\WindowsAppRuntime_DLL\Microsoft.Windows.AppLifecycle.winmd - true - - - $(OutDir)\..\WindowsAppRuntime_DLL\Microsoft.Windows.AppNotifications.winmd - true - - - - - {bf3fced0-cadb-490a-93a7-4d90e1f45ab0} - - - {f76b776e-86f5-48c5-8fc7-d2795ecc9746} - - - - - - - - - - - This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. - - - - - - - - - - \ No newline at end of file