mirror of https://github.com/infosecn1nja/C3.git
Update O365 channels to use the changes from SimplifyOfficeChannels as well as WinHTTP lib
OneDrive now supports ordering of files - needs more testing Outlook works but needs to be commented and cleaned.dependabot/npm_and_yarn/Src/WebController/UI/websocket-extensions-0.1.4
parent
87151e172d
commit
0404a14fb0
|
@ -1,13 +1,17 @@
|
|||
#include "StdAfx.h"
|
||||
#include "OneDrive365RestFile.h"
|
||||
#include "Common/FSecure/CppTools/Utils.h"
|
||||
#include "Common/FSecure/Crypto/Base32.h"
|
||||
#include "Common/FSecure/Crypto/Base64.h"
|
||||
#include "Common/FSecure/CppTools/ScopeGuard.h"
|
||||
#include "Common/json/json.hpp"
|
||||
#include "Common/FSecure/CppTools/StringConversions.h"
|
||||
#include "Common/FSecure/WinHttp/HttpClient.h"
|
||||
#include "Common/FSecure/WinHttp/Constants.h"
|
||||
#include "Common/FSecure/WinHttp/Uri.h"
|
||||
|
||||
// Namespaces.
|
||||
using json = nlohmann::json;
|
||||
using namespace utility::conversions;
|
||||
using namespace FSecure::StringConversions;
|
||||
using namespace FSecure::WinHttp;
|
||||
|
||||
std::atomic<std::chrono::steady_clock::time_point> FSecure::C3::Interfaces::Channels::OneDrive365RestFile::s_TimePoint = std::chrono::steady_clock::now();
|
||||
|
||||
|
@ -15,31 +19,14 @@ std::atomic<std::chrono::steady_clock::time_point> FSecure::C3::Interfaces::Chan
|
|||
FSecure::C3::Interfaces::Channels::OneDrive365RestFile::OneDrive365RestFile(ByteView arguments)
|
||||
: m_InboundDirectionName{ arguments.Read<std::string>()}
|
||||
, m_OutboundDirectionName{ arguments.Read<std::string>()}
|
||||
, m_username{ arguments.Read<std::string>()}
|
||||
, m_password{ arguments.Read<std::string>()}
|
||||
, m_clientKey{ arguments.Read<std::string>()}
|
||||
{
|
||||
// Obtain proxy information and store it in the HTTP configuration.
|
||||
if (auto winProxy = WinTools::GetProxyConfiguration(); !winProxy.empty())
|
||||
m_HttpConfig.set_proxy(winProxy == OBF(L"auto") ? web::web_proxy::use_auto_discovery : web::web_proxy(winProxy));
|
||||
this->m_ProxyConfig = (winProxy == OBF(L"auto")) ? WebProxy(WebProxy::Mode::UseAutoDiscovery) : WebProxy(winProxy);
|
||||
|
||||
// Retrieve auth data.
|
||||
std::string username, password, clientKey, clientSecret;
|
||||
std::tie(username, password, clientKey, clientSecret) = arguments.Read<std::string, std::string, std::string, std::string>();
|
||||
m_Password = to_utf16string(password);
|
||||
FSecure::Utils::SecureMemzero(password.data(), password.size());
|
||||
|
||||
web::http::oauth2::experimental::oauth2_config oauth2Config(
|
||||
to_utf16string(std::move(clientKey)),
|
||||
to_utf16string(std::move(clientSecret)),
|
||||
OBF(L"https://login.windows.net/common/oauth2/v2.0/authorize"),
|
||||
OBF(L"https://login.windows.net/organizations/oauth2/v2.0/token"),
|
||||
OBF(L""),
|
||||
OBF(L"https://graph.microsoft.com/.default"),
|
||||
to_utf16string(username)
|
||||
);
|
||||
|
||||
// Set the above configuration in the HTTP configuration for cpprestsdk (it already includes proxy information from the code above).
|
||||
m_HttpConfig.set_oauth2(std::move(oauth2Config));
|
||||
|
||||
// For simplicity access token is not a configuration parameter. Refresh token will be used to generate first access token.
|
||||
RefreshAccessToken();
|
||||
}
|
||||
|
||||
|
@ -53,39 +40,50 @@ size_t FSecure::C3::Interfaces::Channels::OneDrive365RestFile::OnSendToChannel(B
|
|||
{
|
||||
// Construct the HTTP request
|
||||
auto URLwithFilename = OBF("https://graph.microsoft.com/v1.0/me/drive/root:/") + m_OutboundDirectionName + OBF("-") + FSecure::Utils::GenerateRandomString(20) + OBF(".txt") + OBF(":/content");
|
||||
web::http::client::http_client client(to_string_t(URLwithFilename), m_HttpConfig);
|
||||
web::http::http_request request(web::http::methods::PUT);
|
||||
HttpClient webClient(Convert<Utf16>(URLwithFilename), m_ProxyConfig);
|
||||
|
||||
// Data can be just sent as a text stream
|
||||
request.set_body(cppcodec::base32_crockford::encode(&data.front(), data.size()));
|
||||
request.headers().set_content_type(OBF(L"text/plain"));
|
||||
HttpRequest request;
|
||||
std::string auth = OBF("Bearer ") + m_token;
|
||||
request.SetHeader(Header::Authorization, Convert<Utf16>(auth));
|
||||
request.m_Method = Method::PUT;
|
||||
std::string dataBody = cppcodec::base64_rfc4648::encode(&data.front(), data.size());
|
||||
|
||||
pplx::task<void> task = client.request(request).then([&](web::http::http_response response)
|
||||
auto now = std::chrono::high_resolution_clock::now().time_since_epoch();
|
||||
auto int_ms = std::chrono::duration_cast<std::chrono::milliseconds>(now);
|
||||
std::chrono::duration<long, std::micro> int_usec = int_ms;
|
||||
|
||||
|
||||
json fileData;
|
||||
fileData[OBF("timestamp")] = now.count();
|
||||
|
||||
fileData[OBF("data")] = dataBody;
|
||||
std::string body = fileData.dump();
|
||||
|
||||
request.SetData(OBF(L"text/plain"), { body.begin(), body.end() });
|
||||
|
||||
auto resp = webClient.Request(request);
|
||||
if (resp.GetStatusCode() != StatusCode::OK && resp.GetStatusCode() != StatusCode::Created)
|
||||
{
|
||||
if (response.status_code() == web::http::status_codes::Created)
|
||||
return;
|
||||
|
||||
if (response.status_code() == web::http::status_codes::TooManyRequests) // break and set sleep time.
|
||||
if (resp.GetStatusCode() == StatusCode::TooManyRequests) // break and set sleep time.
|
||||
{
|
||||
s_TimePoint = std::chrono::steady_clock::now() + std::chrono::seconds{ stoul(response.headers().find(OBF(L"Retry-After"))->second) };
|
||||
s_TimePoint = std::chrono::steady_clock::now() + FSecure::Utils::GenerateRandomValue(10s, 20s);
|
||||
throw std::runtime_error{ OBF("Too many requests") };
|
||||
}
|
||||
if(response.status_code() == web::http::status_codes::Unauthorized)
|
||||
if (resp.GetStatusCode() == StatusCode::Unauthorized)
|
||||
{
|
||||
RefreshAccessToken();
|
||||
throw std::runtime_error{ OBF("HTTP 401 - Token being refreshed") };
|
||||
|
||||
}
|
||||
if (response.status_code() == web::http::status_codes::BadRequest)
|
||||
if (resp.GetStatusCode() == StatusCode::BadRequest)
|
||||
{
|
||||
RefreshAccessToken();
|
||||
throw std::runtime_error{ OBF("Bad Request") };
|
||||
}
|
||||
|
||||
throw std::runtime_error{ OBF("Non 200 http response.") + std::to_string(response.status_code()) };
|
||||
});
|
||||
throw std::runtime_error{ OBF("Non 200 http response.") + std::to_string(resp.GetStatusCode()) };
|
||||
}
|
||||
|
||||
task.wait();
|
||||
return data.size();
|
||||
}
|
||||
catch (std::exception& exception)
|
||||
|
@ -96,12 +94,12 @@ size_t FSecure::C3::Interfaces::Channels::OneDrive365RestFile::OnSendToChannel(B
|
|||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
FSecure::ByteVector FSecure::C3::Interfaces::Channels::OneDrive365RestFile::OnReceiveFromChannel()
|
||||
std::vector<FSecure::ByteVector> FSecure::C3::Interfaces::Channels::OneDrive365RestFile::OnReceiveFromChannel()
|
||||
{
|
||||
if (s_TimePoint.load() > std::chrono::steady_clock::now())
|
||||
std::this_thread::sleep_until(s_TimePoint.load() + FSecure::Utils::GenerateRandomValue(m_MinUpdateDelay, m_MaxUpdateDelay));
|
||||
|
||||
ByteVector packet;
|
||||
std::vector<ByteVector> packets;
|
||||
try
|
||||
{
|
||||
// Construct request to get files.
|
||||
|
@ -109,111 +107,121 @@ FSecure::ByteVector FSecure::C3::Interfaces::Channels::OneDrive365RestFile::OnRe
|
|||
// However, this function does not appear to update in real time to reflect the status of what is actually in folders.
|
||||
// Therefore, we need to use a standard directory listing, and manually check the filenames to determine which files to request.
|
||||
// This directory listing fetches up to 1000 files.
|
||||
web::http::client::http_client client(OBF(L"https://graph.microsoft.com/v1.0/me/drive/root/children?top=1000"), m_HttpConfig);
|
||||
web::http::http_request request(web::http::methods::GET);
|
||||
HttpClient webClient(OBF(L"https://graph.microsoft.com/v1.0/me/drive/root/children?top=1000"), m_ProxyConfig);
|
||||
HttpRequest request;
|
||||
std::string auth = OBF("Bearer ") + m_token;
|
||||
request.SetHeader(Header::Authorization, Convert<Utf16>(auth));
|
||||
|
||||
pplx::task<void> task = client.request(request).then([&](web::http::http_response response)
|
||||
auto resp = webClient.Request(request);
|
||||
|
||||
if (resp.GetStatusCode() != StatusCode::OK)
|
||||
{
|
||||
if (response.status_code() == web::http::status_codes::OK) // ==200
|
||||
return response.extract_string();
|
||||
|
||||
if (response.status_code() == web::http::status_codes::TooManyRequests) // break and set sleep time.
|
||||
if (resp.GetStatusCode() == StatusCode::TooManyRequests)
|
||||
{
|
||||
s_TimePoint = std::chrono::steady_clock::now() + std::chrono::seconds{ stoul(response.headers().find(OBF(L"Retry-After"))->second) };
|
||||
s_TimePoint = std::chrono::steady_clock::now() + FSecure::Utils::GenerateRandomValue(10s, 20s);
|
||||
throw std::runtime_error{ OBF("Too many requests") };
|
||||
}
|
||||
if(response.status_code() == web::http::status_codes::Unauthorized)
|
||||
{
|
||||
RefreshAccessToken();
|
||||
throw std::runtime_error{ OBF("HTTP 401 - Token being refreshed") };
|
||||
|
||||
if (resp.GetStatusCode() == StatusCode::Unauthorized)
|
||||
{
|
||||
RefreshAccessToken();
|
||||
throw std::runtime_error{ OBF("HTTP 401 - Token being refreshed") };
|
||||
}
|
||||
if (response.status_code() == web::http::status_codes::BadRequest)
|
||||
|
||||
if (resp.GetStatusCode() == StatusCode::BadRequest)
|
||||
{
|
||||
RefreshAccessToken();
|
||||
throw std::runtime_error{ OBF("Bad Request") };
|
||||
}
|
||||
|
||||
throw std::runtime_error{ OBF("Non 200 http response.") + std::to_string(response.status_code()) };
|
||||
throw std::runtime_error{ OBF("Non 200 http response.") + std::to_string(resp.GetStatusCode()) };
|
||||
}
|
||||
|
||||
})
|
||||
.then([&](pplx::task<std::wstring> taskData)
|
||||
json taskDataAsJSON;
|
||||
try
|
||||
{
|
||||
// Gracefully handle situation where there's an empty JSON value (e.g., a failed request)
|
||||
if (taskData.get().empty())
|
||||
return;
|
||||
taskDataAsJSON = json::parse(resp.GetData());
|
||||
}
|
||||
catch (json::parse_error)
|
||||
{
|
||||
Log({ OBF("Failed to parse the list of received files."), LogMessage::Severity::Error });
|
||||
return {};
|
||||
}
|
||||
|
||||
// Convert response (as string_t to utf8) and parse.
|
||||
json taskDataAsJSON;
|
||||
//first iterate over the json and populate an array of the files we want.
|
||||
std::vector<json> elements;
|
||||
for (auto& element : taskDataAsJSON.at(OBF("value")))
|
||||
{
|
||||
// Obtain subject and task ID.
|
||||
std::string filename = element.at(OBF("name")).get<std::string>();
|
||||
std::string id = element.at(OBF("id")).get<std::string>();
|
||||
|
||||
|
||||
// Verify that the full subject and ID were obtained. If not, ignore.
|
||||
if (filename.empty() || id.empty())
|
||||
continue;
|
||||
|
||||
// Check the direction component is at the start of name.
|
||||
if (filename.find(m_InboundDirectionName))
|
||||
continue;
|
||||
|
||||
//download the file
|
||||
std::string downloadUrl = element.at(OBF("@microsoft.graph.downloadUrl")).get<std::string>();
|
||||
HttpClient webClientFile(Convert<Utf16>(downloadUrl), m_ProxyConfig);
|
||||
HttpRequest fileRequest;
|
||||
|
||||
fileRequest.SetHeader(Header::Authorization, Convert<Utf16>(auth));
|
||||
|
||||
auto fileResp = webClientFile.Request(fileRequest);
|
||||
std::string fileAsString;
|
||||
|
||||
if (fileResp.GetStatusCode() == StatusCode::OK)
|
||||
{
|
||||
auto data = fileResp.GetData();
|
||||
fileAsString = std::string{ data.begin(), data.end() }; //We discussed bv.Read<std::string>() but it wouldn't work here?
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
Log({ OBF("Error request download url, got HTTP code ") + std::to_string(fileResp.GetStatusCode()), LogMessage::Severity::Error });
|
||||
return {}; //or continue??
|
||||
}
|
||||
json j = json::parse(fileAsString);
|
||||
j[OBF("id")] = id;
|
||||
elements.push_back(j);
|
||||
}
|
||||
|
||||
//now sort and re-iterate over them.
|
||||
std::sort(elements.begin(), elements.end(), [](auto const& a, auto const& b) -> bool { return a[OBF("timestamp")] < b[OBF("timestamp")]; });
|
||||
|
||||
for(auto &element : elements)
|
||||
{
|
||||
std::string id = element.at(OBF("id")).get<std::string>();
|
||||
std::string base64Data = element.at(OBF("data")).get<std::string>();
|
||||
try
|
||||
{
|
||||
taskDataAsJSON = json::parse(to_utf8string(taskData.get()));
|
||||
packets.push_back(cppcodec::base64_rfc4648::decode(base64Data));
|
||||
RemoveFile(id);
|
||||
}
|
||||
catch (json::parse_error)
|
||||
catch (const cppcodec::parse_error& exception)
|
||||
{
|
||||
Log({ OBF("Failed to parse the list of received files."), LogMessage::Severity::Error });
|
||||
return;
|
||||
Log({ OBF("Error decoding task #") + id + OBF(" : ") + exception.what(), LogMessage::Severity::Error });
|
||||
return {};
|
||||
}
|
||||
|
||||
for (auto& element : taskDataAsJSON.at(OBF("value")))
|
||||
catch (std::exception& exception)
|
||||
{
|
||||
// Obtain subject and task ID.
|
||||
std::string filename = element.at(OBF("name")).get<std::string>();
|
||||
std::string id = element.at(OBF("id")).get<std::string>();
|
||||
|
||||
// Verify that the full subject and ID were obtained. If not, ignore.
|
||||
if (filename.empty() || id.empty())
|
||||
continue;
|
||||
|
||||
// Check the direction component is at the start of name.
|
||||
if (filename.find(m_InboundDirectionName))
|
||||
continue;
|
||||
|
||||
web::http::client::http_client clientFile(to_string_t(element.at(OBF("@microsoft.graph.downloadUrl")).get<std::string>()), m_HttpConfig);
|
||||
std::string fileAsString;
|
||||
|
||||
pplx::task<void> fileRequest = clientFile.request({ web::http::methods::GET }).then([this, &fileAsString](web::http::http_response response)
|
||||
{
|
||||
if (response.status_code() == web::http::status_codes::OK) // ==200
|
||||
fileAsString = to_utf8string(response.extract_string().get());
|
||||
});
|
||||
|
||||
try
|
||||
{
|
||||
fileRequest.wait();
|
||||
}
|
||||
catch (const web::http::http_exception& exception)
|
||||
{
|
||||
Log({ OBF_SEC("Caught a HTTP exception during OnReceive(): ") +exception.what(), LogMessage::Severity::Warning });
|
||||
continue;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
packet = cppcodec::base32_crockford::decode(fileAsString);
|
||||
SCOPE_GUARD( RemoveFile(id); );
|
||||
return;
|
||||
|
||||
}
|
||||
catch (const cppcodec::parse_error& exception)
|
||||
{
|
||||
Log({ OBF("Error decoding task #") + id + OBF(" : ") + exception.what(), LogMessage::Severity::Error });
|
||||
}
|
||||
catch (std::exception& exception)
|
||||
{
|
||||
Log({ OBF("Caught a std::exception when processing file #") + id + OBF(" : ") + exception.what(), LogMessage::Severity::Error });
|
||||
}
|
||||
Log({ OBF("Caught a std::exception when processing file #") + id + OBF(" : ") + exception.what(), LogMessage::Severity::Error });
|
||||
return {};
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
task.wait();
|
||||
}
|
||||
catch (std::exception& exception)
|
||||
{
|
||||
Log({ OBF_SEC("Caught a std::exception when running OnReceive(): ") + exception.what(), LogMessage::Severity::Warning });
|
||||
}
|
||||
|
||||
return packet;
|
||||
return packets;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
@ -221,25 +229,24 @@ FSecure::ByteVector FSecure::C3::Interfaces::Channels::OneDrive365RestFile::Remo
|
|||
{
|
||||
try
|
||||
{
|
||||
// Construct request.
|
||||
auto client = web::http::client::http_client{ OBF(L"https://graph.microsoft.com/v1.0/me/drive/root/children"), m_HttpConfig };
|
||||
pplx::task<void> task = client.request({ web::http::methods::GET }).then([this](web::http::http_response response)
|
||||
HttpClient webClient(OBF(L"https://graph.microsoft.com/v1.0/me/drive/root/children"), m_ProxyConfig);
|
||||
HttpRequest request;
|
||||
std::string auth = OBF("Bearer ") + m_token;
|
||||
request.SetHeader(Header::Authorization, Convert<Utf16>(auth));
|
||||
|
||||
auto resp = webClient.Request(request);
|
||||
|
||||
if (resp.GetStatusCode() != StatusCode::OK)
|
||||
{
|
||||
if (response.status_code() != web::http::status_codes::OK)
|
||||
{
|
||||
Log({ OBF("RemoveAllFiles() Error. Files could not be deleted. Confirm access and refresh tokens are correct."), LogMessage::Severity::Error });
|
||||
return;
|
||||
}
|
||||
Log({ OBF("RemoveAllFiles() Error. Files could not be deleted. Confirm access and refresh tokens are correct."), LogMessage::Severity::Error });
|
||||
return {};
|
||||
}
|
||||
|
||||
// Parse response (list of tasks)
|
||||
auto taskDataAsJSON = nlohmann::json::parse(to_utf8string(response.extract_string().get()));
|
||||
auto taskDataAsJSON = nlohmann::json::parse(resp.GetData());
|
||||
|
||||
// For each task (under the "value" key), extract the ID, and send a request to delete the task.
|
||||
for (auto& element : taskDataAsJSON.at(OBF("value")))
|
||||
RemoveFile(element.at(OBF("id")).get<std::string>());
|
||||
});
|
||||
|
||||
task.wait();
|
||||
// For each task (under the "value" key), extract the ID, and send a request to delete the task.
|
||||
for (auto& element : taskDataAsJSON.at(OBF("value")))
|
||||
RemoveFile(element.at(OBF("id")).get<std::string>());
|
||||
}
|
||||
catch (std::exception& exception)
|
||||
{
|
||||
|
@ -254,42 +261,37 @@ void FSecure::C3::Interfaces::Channels::OneDrive365RestFile::RefreshAccessToken(
|
|||
{
|
||||
try
|
||||
{
|
||||
auto oa2 = m_HttpConfig.oauth2();
|
||||
auto client = web::http::client::http_client(oa2->token_endpoint(), m_HttpConfig);
|
||||
auto request = web::http::http_request(web::http::methods::POST);
|
||||
request.headers().set_content_type(OBF(L"application/x-www-form-urlencoded"));
|
||||
//Token endpoint
|
||||
HttpClient webClient(OBF(L"https://login.windows.net/organizations/oauth2/v2.0/token"), m_ProxyConfig);
|
||||
|
||||
HttpRequest request; // default request is GET
|
||||
request.m_Method = Method::POST;
|
||||
request.SetHeader(Header::ContentType, OBF(L"application/x-www-form-urlencoded; charset=utf-16"));
|
||||
json data;
|
||||
|
||||
auto requestBody = ""s;
|
||||
requestBody += OBF("grant_type=password");
|
||||
requestBody += OBF("&scope=");
|
||||
requestBody += to_utf8string(oa2->scope());
|
||||
requestBody += OBF("&scope=files.readwrite.all");
|
||||
requestBody += OBF("&username=");
|
||||
requestBody += to_utf8string(oa2->user_agent());
|
||||
requestBody += m_username;
|
||||
requestBody += OBF("&password=");
|
||||
requestBody += to_utf8string(*m_Password.decrypt());
|
||||
requestBody += m_password;
|
||||
//requestBody += to_utf8string(*m_Password.decrypt());
|
||||
requestBody += OBF("&client_id=");
|
||||
requestBody += to_utf8string(oa2->client_key());
|
||||
if (!oa2->client_secret().empty())
|
||||
{
|
||||
requestBody += OBF("&client_secret=");
|
||||
requestBody += to_utf8string(oa2->client_secret());
|
||||
}
|
||||
requestBody += m_clientKey;
|
||||
|
||||
request.set_body(requestBody);
|
||||
|
||||
|
||||
request.SetData(ContentType::ApplicationXWwwFormUrlencoded, { requestBody.begin(), requestBody.end() });
|
||||
FSecure::Utils::SecureMemzero(requestBody.data(), requestBody.size());
|
||||
pplx::task<void> task = client.request(request).then([&](web::http::http_response response)
|
||||
{
|
||||
if (response.status_code() != web::http::status_codes::OK)
|
||||
throw std::runtime_error{ OBF("Refresh access token request - non-200 status code was received: ") + std::to_string(response.status_code()) };
|
||||
auto resp = webClient.Request(request);
|
||||
|
||||
// If successful, parse the useful information from the response.
|
||||
auto taskDataAsJSON = nlohmann::json::parse(to_utf8string(response.extract_string().get()));
|
||||
if (resp.GetStatusCode() == StatusCode::OK)
|
||||
data = json::parse(resp.GetData());
|
||||
else
|
||||
throw std::runtime_error{ OBF("Refresh access token request - non-200 status code was received: ") + std::to_string(resp.GetStatusCode()) };
|
||||
|
||||
auto tokenCopy = oa2->token();
|
||||
tokenCopy.set_access_token(to_string_t(taskDataAsJSON.at(OBF("access_token")).get<std::string>()));
|
||||
tokenCopy.set_expires_in(taskDataAsJSON.at(OBF("expires_in")).get<std::int64_t>());
|
||||
oa2->set_token(tokenCopy);
|
||||
});
|
||||
task.wait();
|
||||
m_token = data[OBF("access_token")].get<std::string>();
|
||||
}
|
||||
catch (std::exception& exception)
|
||||
{
|
||||
|
@ -297,19 +299,23 @@ void FSecure::C3::Interfaces::Channels::OneDrive365RestFile::RefreshAccessToken(
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
void FSecure::C3::Interfaces::Channels::OneDrive365RestFile::RemoveFile(std::string const& id)
|
||||
{
|
||||
// There is a minor logic flaw in this part of the code, as it assumes the access token is still valid, which may not be the case.
|
||||
auto URLwithID = OBF("https://graph.microsoft.com/v1.0/me/drive/items/") + id;
|
||||
auto client = web::http::client::http_client{ to_string_t(URLwithID), m_HttpConfig };
|
||||
auto task = client.request({ web::http::methods::DEL }).then([&](web::http::http_response response)
|
||||
{
|
||||
if (response.status_code() > 205)
|
||||
Log({ OBF("RemoveFile() Error. Task could not be deleted. HTTP response:") + std::to_string(response.status_code()), LogMessage::Severity::Error });
|
||||
});
|
||||
std::wstring url = OBF(L"https://graph.microsoft.com/v1.0/me/drive/items/") + Convert<Utf16>(id);
|
||||
HttpClient webClient(url, m_ProxyConfig);
|
||||
HttpRequest request;
|
||||
|
||||
task.wait();
|
||||
std::string auth = OBF("Bearer ") + m_token;
|
||||
request.SetHeader(Header::Authorization, Convert<Utf16>(auth));
|
||||
request.m_Method = Method::DEL;
|
||||
auto resp = webClient.Request(request);
|
||||
|
||||
if (resp.GetStatusCode() > 205)
|
||||
{
|
||||
Log({ OBF("RemoveFile() Error. Task could not be deleted. HTTP response:") + std::to_string(resp.GetStatusCode()), LogMessage::Severity::Error });
|
||||
}
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
@ -353,26 +359,21 @@ FSecure::ByteView FSecure::C3::Interfaces::Channels::OneDrive365RestFile::GetCap
|
|||
],
|
||||
{
|
||||
"type": "string",
|
||||
"name": "Username",
|
||||
"name": "username",
|
||||
"min": 1,
|
||||
"description": "User with Office 365 subscription."
|
||||
"description": "The O365 user"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"name": "Password",
|
||||
"name": "password",
|
||||
"min": 1,
|
||||
"description": "User password."
|
||||
"description": "The user's password"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"name": "Client key",
|
||||
"name": "Client Key/ID",
|
||||
"min": 1,
|
||||
"description": "Identifies the application (e.g. a GUID). User, or user admin must give consent for application to work in user context."
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"name": "Client secret",
|
||||
"description": "Leave empty if not required."
|
||||
"description": "The GUID of the registered application."
|
||||
}
|
||||
]
|
||||
},
|
||||
|
@ -388,3 +389,4 @@ FSecure::ByteView FSecure::C3::Interfaces::Channels::OneDrive365RestFile::GetCap
|
|||
}
|
||||
)_";
|
||||
}
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
#pragma once
|
||||
|
||||
#include "Common/CppRestSdk/include/cpprest/http_client.h" //< For CppRestSdk.
|
||||
|
||||
#include "Common/FSecure/WinHttp/WebProxy.h"
|
||||
#include "Common/FSecure/WinHttp/Constants.h" //< For CppRestSdk.
|
||||
namespace FSecure::C3::Interfaces::Channels
|
||||
{
|
||||
/// Implementation of the OneDrive 365 REST file Channel.
|
||||
|
@ -19,7 +19,7 @@ namespace FSecure::C3::Interfaces::Channels
|
|||
|
||||
/// Reads a single C3 packet from Channel.
|
||||
/// @return packet retrieved from Channel.
|
||||
ByteVector OnReceiveFromChannel();
|
||||
std::vector<ByteVector> OnReceiveFromChannel();
|
||||
|
||||
/// Processes internal (C3 API) Command.
|
||||
/// @param command a buffer containing whole command and it's parameters.
|
||||
|
@ -32,7 +32,7 @@ namespace FSecure::C3::Interfaces::Channels
|
|||
|
||||
/// Values used as default for channel jitter. 30 ms if unset. Current jitter value can be changed at runtime.
|
||||
/// Set long delay otherwise O365 rate limit will heavily impact channel.
|
||||
constexpr static std::chrono::milliseconds s_MinUpdateDelay = 3500ms, s_MaxUpdateDelay = 6500ms;
|
||||
constexpr static std::chrono::milliseconds s_MinUpdateDelay = 1000ms, s_MaxUpdateDelay = 1000ms;
|
||||
|
||||
protected:
|
||||
/// Removes all file from server.
|
||||
|
@ -51,11 +51,12 @@ namespace FSecure::C3::Interfaces::Channels
|
|||
/// In/Out names on the server.
|
||||
std::string m_InboundDirectionName, m_OutboundDirectionName;
|
||||
|
||||
/// Stores HTTP configuration (proxy, OAuth, etc).
|
||||
web::http::client::http_client_config m_HttpConfig;
|
||||
/// Username, password, client key and token for authentication.
|
||||
std::string m_username, m_password, m_clientKey, m_token;
|
||||
|
||||
/// Store any relevant proxy info
|
||||
WinHttp::WebProxy m_ProxyConfig;
|
||||
|
||||
/// Password for user with o365 subscription.
|
||||
web::details::win32_encryption m_Password;
|
||||
|
||||
/// Used to delay every channel instance in case of server rate limit.
|
||||
/// Set using information from 429 Too Many Requests header.
|
||||
|
|
|
@ -3,10 +3,15 @@
|
|||
#include "Common/FSecure/Crypto/Base64.h"
|
||||
#include "Common/FSecure/CppTools/ScopeGuard.h"
|
||||
#include "Common/json/json.hpp"
|
||||
#include "Common/FSecure/CppTools/StringConversions.h"
|
||||
#include "Common/FSecure/WinHttp/HttpClient.h"
|
||||
#include "Common/FSecure/WinHttp/Constants.h"
|
||||
#include "Common/FSecure/WinHttp/Uri.h"
|
||||
|
||||
// Namespaces.
|
||||
using json = nlohmann::json;
|
||||
using namespace utility::conversions;
|
||||
using namespace FSecure::StringConversions;
|
||||
using namespace FSecure::WinHttp;
|
||||
|
||||
std::atomic<std::chrono::steady_clock::time_point> FSecure::C3::Interfaces::Channels::Outlook365RestTask::s_TimePoint = std::chrono::steady_clock::now();
|
||||
|
||||
|
@ -14,31 +19,14 @@ std::atomic<std::chrono::steady_clock::time_point> FSecure::C3::Interfaces::Chan
|
|||
FSecure::C3::Interfaces::Channels::Outlook365RestTask::Outlook365RestTask(ByteView arguments)
|
||||
: m_InboundDirectionName{ arguments.Read<std::string>() }
|
||||
, m_OutboundDirectionName{ arguments.Read<std::string>() }
|
||||
, m_username{ arguments.Read<std::string>() }
|
||||
, m_Password{ arguments.Read<std::string>() }
|
||||
, m_clientKey{ arguments.Read<std::string>() }
|
||||
{
|
||||
// Obtain proxy information and store it in the HTTP configuration.
|
||||
if (auto winProxy = WinTools::GetProxyConfiguration(); !winProxy.empty())
|
||||
m_HttpConfig.set_proxy(winProxy == OBF(L"auto") ? web::web_proxy::use_auto_discovery : web::web_proxy(winProxy));
|
||||
this->m_ProxyConfig = (winProxy == OBF(L"auto")) ? WebProxy(WebProxy::Mode::UseAutoDiscovery) : WebProxy(winProxy);
|
||||
|
||||
// Retrieve auth data.
|
||||
std::string username, password, clientKey, clientSecret;
|
||||
std::tie(username, password, clientKey, clientSecret) = arguments.Read<std::string, std::string, std::string, std::string>();
|
||||
m_Password = to_utf16string(password);
|
||||
FSecure::Utils::SecureMemzero(password.data(), password.size());
|
||||
|
||||
web::http::oauth2::experimental::oauth2_config oauth2Config(
|
||||
to_utf16string(std::move(clientKey)),
|
||||
to_utf16string(std::move(clientSecret)),
|
||||
OBF(L"https://login.windows.net/common/oauth2/v2.0/authorize"),
|
||||
OBF(L"https://login.windows.net/organizations/oauth2/v2.0/token"),
|
||||
OBF(L""),
|
||||
OBF(L"https://outlook.office365.com/.default"),
|
||||
to_utf16string(username)
|
||||
);
|
||||
|
||||
// Set the above configuration in the HTTP configuration for cpprestsdk (it already includes proxy information from the code above).
|
||||
m_HttpConfig.set_oauth2(std::move(oauth2Config));
|
||||
|
||||
// For simplicity access token is not a configuration parameter. Refresh token will be used to generate first access token.
|
||||
RefreshAccessToken();
|
||||
}
|
||||
|
||||
|
@ -51,40 +39,44 @@ size_t FSecure::C3::Interfaces::Channels::Outlook365RestTask::OnSendToChannel(By
|
|||
try
|
||||
{
|
||||
// Construct the HTTP request
|
||||
web::http::client::http_client client(OBF(L"https://outlook.office.com/api/v2.0/me/tasks"), m_HttpConfig);
|
||||
web::http::http_request request(web::http::methods::POST);
|
||||
HttpClient webClient(OBF(L"https://outlook.office.com/api/v2.0/me/tasks"), m_ProxyConfig);
|
||||
|
||||
HttpRequest request; // default request is GET
|
||||
std::string auth = "Bearer " + m_token;
|
||||
request.SetHeader(Header::Authorization, Convert<Utf16>(auth));
|
||||
request.m_Method = Method::POST;
|
||||
|
||||
// For the JSON body, take a simple approach and use only the required fields.
|
||||
json jsonBody;
|
||||
jsonBody[OBF("Subject")] = m_OutboundDirectionName;
|
||||
jsonBody[OBF("Body")][OBF("Content")] = cppcodec::base64_rfc4648::encode(&data.front(), data.size());
|
||||
jsonBody[OBF("Body")][OBF("ContentType")] = OBF("Text");
|
||||
std::string body = jsonBody.dump();
|
||||
request.SetData(ContentType::ApplicationJson, { body.begin(), body.end() });
|
||||
request.m_ContentType = L"application/json";
|
||||
|
||||
request.set_body(to_string_t(jsonBody.dump()));
|
||||
request.headers().set_content_type(OBF(L"application/json"));
|
||||
auto resp = webClient.Request(request);
|
||||
|
||||
web::http::http_response response = client.request(request).get();
|
||||
|
||||
if (response.status_code() != web::http::status_codes::Created)
|
||||
if (resp.GetStatusCode() != StatusCode::OK && resp.GetStatusCode() != StatusCode::Created)
|
||||
{
|
||||
if (response.status_code() == web::http::status_codes::TooManyRequests) // break and set sleep time.
|
||||
if (resp.GetStatusCode() == StatusCode::TooManyRequests) // break and set sleep time.
|
||||
{
|
||||
s_TimePoint = std::chrono::steady_clock::now() + FSecure::Utils::GenerateRandomValue(10s, 20s);
|
||||
throw std::runtime_error{ OBF("Too many requests") };
|
||||
}
|
||||
if (response.status_code() == web::http::status_codes::Unauthorized)
|
||||
if (resp.GetStatusCode() == StatusCode::Unauthorized)
|
||||
{
|
||||
RefreshAccessToken();
|
||||
throw std::runtime_error{ OBF("HTTP 401 - Token being refreshed") };
|
||||
|
||||
}
|
||||
if (response.status_code() == web::http::status_codes::BadRequest)
|
||||
if (resp.GetStatusCode() == StatusCode::BadRequest)
|
||||
{
|
||||
RefreshAccessToken();
|
||||
throw std::runtime_error{ OBF("Bad Request") };
|
||||
}
|
||||
|
||||
throw std::runtime_error{ OBF("Non 200 http response.") + std::to_string(response.status_code()) };
|
||||
throw std::runtime_error{ OBF("Non 200 http response.") + std::to_string(resp.GetStatusCode()) };
|
||||
}
|
||||
|
||||
return data.size();
|
||||
|
@ -94,6 +86,8 @@ size_t FSecure::C3::Interfaces::Channels::Outlook365RestTask::OnSendToChannel(By
|
|||
Log({ OBF_SEC("Caught a std::exception when running OnSend(): ") + exception.what(), LogMessage::Severity::Warning });
|
||||
return 0u;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
@ -113,48 +107,50 @@ std::vector<FSecure::ByteVector> FSecure::C3::Interfaces::Channels::Outlook365Re
|
|||
URLwithInboundDirection += OBF("&filter=startswith(Subject,'"); // filter by subject
|
||||
URLwithInboundDirection += m_InboundDirectionName; // subject should contain m_InboundDirectionName
|
||||
URLwithInboundDirection += OBF("')&orderby=CreatedDateTime"); // order by creation date (oldest first)
|
||||
web::http::client::http_client client(to_string_t(URLwithInboundDirection), m_HttpConfig);
|
||||
web::http::http_request request(web::http::methods::GET);
|
||||
|
||||
web::http::http_response response = client.request(request).get();
|
||||
HttpClient webClient(Convert<Utf16>(URLwithInboundDirection), m_ProxyConfig);
|
||||
|
||||
if (response.status_code() != web::http::status_codes::OK) // ==200
|
||||
HttpRequest request; // default request is GET
|
||||
std::string auth = "Bearer " + m_token;
|
||||
request.SetHeader(Header::Authorization, Convert<Utf16>(auth));
|
||||
auto resp = webClient.Request(request);
|
||||
|
||||
if (resp.GetStatusCode() != StatusCode::OK)
|
||||
{
|
||||
if (response.status_code() == web::http::status_codes::TooManyRequests) // break and set sleep time.
|
||||
if (resp.GetStatusCode() == StatusCode::TooManyRequests)
|
||||
{
|
||||
s_TimePoint = std::chrono::steady_clock::now() + std::chrono::seconds{ stoul(response.headers().find(OBF(L"Retry-After"))->second) };
|
||||
s_TimePoint = std::chrono::steady_clock::now() + FSecure::Utils::GenerateRandomValue(10s, 20s);
|
||||
throw std::runtime_error{ OBF("Too many requests") };
|
||||
}
|
||||
|
||||
if (response.status_code() == web::http::status_codes::Unauthorized)
|
||||
if (resp.GetStatusCode() == StatusCode::Unauthorized)
|
||||
{
|
||||
RefreshAccessToken();
|
||||
throw std::runtime_error{ OBF("HTTP 401 - Token being refreshed") };
|
||||
|
||||
}
|
||||
|
||||
if (response.status_code() == web::http::status_codes::BadRequest)
|
||||
if (resp.GetStatusCode() == StatusCode::BadRequest)
|
||||
{
|
||||
RefreshAccessToken();
|
||||
throw std::runtime_error{ OBF("Bad Request") };
|
||||
}
|
||||
|
||||
throw std::runtime_error{ OBF("Non 200 http response.") + std::to_string(response.status_code()) };
|
||||
throw std::runtime_error{ OBF("Non 200 http response.") + std::to_string(resp.GetStatusCode()) };
|
||||
}
|
||||
|
||||
std::string responseString = response.extract_utf8string().get();
|
||||
|
||||
auto responseData = resp.GetData();
|
||||
// Gracefully handle situation where there's an empty JSON value (e.g., a failed request)
|
||||
if (responseString.empty())
|
||||
if (responseData.size() == 0)
|
||||
return {};
|
||||
|
||||
// Convert response (as string_t to utf8) and parse.
|
||||
json taskDataAsJSON;
|
||||
try
|
||||
{
|
||||
taskDataAsJSON = json::parse(responseString);
|
||||
taskDataAsJSON = json::parse(responseData);
|
||||
}
|
||||
catch (json::parse_error&)
|
||||
catch (json::parse_error& err)
|
||||
{
|
||||
Log({ OBF("Failed to parse the list of received tasks."), LogMessage::Severity::Error });
|
||||
return {};
|
||||
|
@ -202,21 +198,26 @@ std::vector<FSecure::ByteVector> FSecure::C3::Interfaces::Channels::Outlook365Re
|
|||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
FSecure::ByteVector FSecure::C3::Interfaces::Channels::Outlook365RestTask::RemoveAllTasks(ByteView)
|
||||
{
|
||||
|
||||
try
|
||||
{
|
||||
// Construct request. One minor limitation of this is that it will remove 1000 tasks only (the maximum of "top"). This could be paged.
|
||||
auto client = web::http::client::http_client{ OBF(L"https://outlook.office.com/api/v2.0/me/tasks?$top=1000"), m_HttpConfig };
|
||||
HttpClient webClient(OBF(L"https://outlook.office.com/api/v2.0/me/tasks?$top=1000"), m_ProxyConfig);
|
||||
|
||||
web::http::http_response response = client.request({ web::http::methods::GET }).get();
|
||||
HttpRequest request; // default request is GET
|
||||
std::string auth = "Bearer " + m_token;
|
||||
request.SetHeader(Header::Authorization, Convert<Utf16>(auth));
|
||||
auto resp = webClient.Request(request);
|
||||
|
||||
if (response.status_code() != web::http::status_codes::OK)
|
||||
|
||||
if (resp.GetStatusCode() != StatusCode::OK)
|
||||
{
|
||||
Log({ OBF("RemoveAllFiles() Error. Files could not be deleted. Confirm access and refresh tokens are correct."), LogMessage::Severity::Error });
|
||||
return {};
|
||||
}
|
||||
|
||||
// Parse response (list of tasks)
|
||||
auto taskDataAsJSON = nlohmann::json::parse(to_utf8string(response.extract_utf8string().get()));
|
||||
auto taskDataAsJSON = nlohmann::json::parse(resp.GetData());
|
||||
|
||||
// For each task (under the "value" key), extract the ID, and send a request to delete the task.
|
||||
for (auto& element : taskDataAsJSON.at(OBF("value")))
|
||||
|
@ -233,13 +234,21 @@ FSecure::ByteVector FSecure::C3::Interfaces::Channels::Outlook365RestTask::Remov
|
|||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
void FSecure::C3::Interfaces::Channels::Outlook365RestTask::RemoveTask(std::string const& id)
|
||||
{
|
||||
|
||||
// There is a minor logic flaw in this part of the code, as it assumes the access token is still valid, which may not be the case.
|
||||
auto URLwithID = OBF("https://outlook.office.com/api/v2.0/me/tasks('") + id + OBF("')");
|
||||
auto client = web::http::client::http_client{ to_string_t(URLwithID), m_HttpConfig };
|
||||
web::http::http_response response = client.request({ web::http::methods::DEL }).get();
|
||||
HttpClient webClient(Convert<Utf16>(URLwithID), m_ProxyConfig);
|
||||
|
||||
if (response.status_code() > 205)
|
||||
Log({ OBF("RemoveTask() Error. Task could not be deleted. HTTP response:") + std::to_string(response.status_code()), LogMessage::Severity::Error });
|
||||
HttpRequest request; // default request is GET
|
||||
std::string auth = "Bearer " + m_token;
|
||||
request.SetHeader(Header::Authorization, Convert<Utf16>(auth));
|
||||
request.m_Method = Method::DEL;
|
||||
auto resp = webClient.Request(request);
|
||||
|
||||
if (resp.GetStatusCode() > 205)
|
||||
Log({ OBF("RemoveTask() Error. Task could not be deleted. HTTP response:") + std::to_string(resp.GetStatusCode()), LogMessage::Severity::Error });
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
@ -247,41 +256,34 @@ void FSecure::C3::Interfaces::Channels::Outlook365RestTask::RefreshAccessToken()
|
|||
{
|
||||
try
|
||||
{
|
||||
auto oa2 = m_HttpConfig.oauth2();
|
||||
auto client = web::http::client::http_client(oa2->token_endpoint(), m_HttpConfig);
|
||||
auto request = web::http::http_request(web::http::methods::POST);
|
||||
request.headers().set_content_type(OBF(L"application/x-www-form-urlencoded"));
|
||||
//Token endpoint
|
||||
HttpClient webClient(Convert<Utf16>("https://login.windows.net/organizations/oauth2/v2.0/token"), m_ProxyConfig);
|
||||
|
||||
HttpRequest request; // default request is GET
|
||||
request.m_Method = Method::POST;
|
||||
request.SetHeader(Header::ContentType, L"application/x-www-form-urlencoded; charset=utf-16");//ContentType::ApplicationXWwwFormUrlencoded);
|
||||
json data;
|
||||
|
||||
auto requestBody = ""s;
|
||||
requestBody += OBF("grant_type=password");
|
||||
requestBody += OBF("&scope=");
|
||||
requestBody += to_utf8string(oa2->scope());
|
||||
requestBody += OBF("&scope=https://outlook.office365.com/.default");
|
||||
requestBody += OBF("&username=");
|
||||
requestBody += to_utf8string(oa2->user_agent());
|
||||
requestBody += m_username;
|
||||
requestBody += OBF("&password=");
|
||||
requestBody += to_utf8string(*m_Password.decrypt());
|
||||
requestBody += m_Password;
|
||||
requestBody += OBF("&client_id=");
|
||||
requestBody += to_utf8string(oa2->client_key());
|
||||
if (!oa2->client_secret().empty())
|
||||
{
|
||||
requestBody += OBF("&client_secret=");
|
||||
requestBody += to_utf8string(oa2->client_secret());
|
||||
}
|
||||
requestBody += m_clientKey;
|
||||
|
||||
request.set_body(requestBody);
|
||||
request.SetData(ContentType::ApplicationXWwwFormUrlencoded, { requestBody.begin(), requestBody.end() });
|
||||
FSecure::Utils::SecureMemzero(requestBody.data(), requestBody.size());
|
||||
auto resp = webClient.Request(request);
|
||||
|
||||
web::http::http_response response = client.request(request).get();
|
||||
if (resp.GetStatusCode() == StatusCode::OK)
|
||||
data = json::parse(resp.GetData());
|
||||
else
|
||||
throw std::runtime_error{ OBF("Refresh access token request - non-200 status code was received: ") + std::to_string(resp.GetStatusCode()) };
|
||||
|
||||
if (response.status_code() != web::http::status_codes::OK)
|
||||
throw std::runtime_error{ OBF("Refresh access token request - non-200 status code was received: ") + std::to_string(response.status_code()) };
|
||||
|
||||
// If successful, parse the useful information from the response.
|
||||
auto taskDataAsJSON = nlohmann::json::parse(to_utf8string(response.extract_utf8string().get()));
|
||||
|
||||
auto tokenCopy = oa2->token();
|
||||
tokenCopy.set_access_token(to_string_t(taskDataAsJSON.at(OBF("access_token")).get<std::string>()));
|
||||
tokenCopy.set_expires_in(taskDataAsJSON.at(OBF("expires_in")).get<std::int64_t>());
|
||||
oa2->set_token(tokenCopy);
|
||||
m_token = data["access_token"].get<std::string>();
|
||||
}
|
||||
catch (std::exception& exception)
|
||||
{
|
||||
|
@ -330,13 +332,13 @@ FSecure::ByteView FSecure::C3::Interfaces::Channels::Outlook365RestTask::GetCapa
|
|||
{
|
||||
"type": "string",
|
||||
"name": "Username",
|
||||
"min": 1,
|
||||
"min": 0,
|
||||
"description": "User with Office 365 subscription."
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"name": "Password",
|
||||
"min": 1,
|
||||
"min": 0,
|
||||
"description": "User password."
|
||||
},
|
||||
{
|
||||
|
@ -344,11 +346,6 @@ FSecure::ByteView FSecure::C3::Interfaces::Channels::Outlook365RestTask::GetCapa
|
|||
"name": "Client key",
|
||||
"min": 1,
|
||||
"description": "Identifies the application (e.g. a GUID). User, or user admin must give consent for application to work in user context."
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"name": "Client secret",
|
||||
"description": "Leave empty if not required."
|
||||
}
|
||||
]
|
||||
},
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
#pragma once
|
||||
|
||||
#include "Common/CppRestSdk/include/cpprest/http_client.h" //< For CppRestSdk.
|
||||
#include "Common/FSecure/WinHttp/WebProxy.h"
|
||||
#include "Common/FSecure/WinHttp/Constants.h" //< For CppRestSdk.
|
||||
|
||||
namespace FSecure::C3::Interfaces::Channels
|
||||
{
|
||||
|
@ -32,7 +33,7 @@ namespace FSecure::C3::Interfaces::Channels
|
|||
|
||||
/// Values used as default for channel jitter. 30 ms if unset. Current jitter value can be changed at runtime.
|
||||
/// Set long delay otherwise O365 rate limit will heavily impact channel.
|
||||
constexpr static std::chrono::milliseconds s_MinUpdateDelay = 3500ms, s_MaxUpdateDelay = 6500ms;
|
||||
constexpr static std::chrono::milliseconds s_MinUpdateDelay = 1000ms, s_MaxUpdateDelay = 1000ms;
|
||||
|
||||
protected:
|
||||
|
||||
|
@ -53,13 +54,14 @@ namespace FSecure::C3::Interfaces::Channels
|
|||
std::string m_InboundDirectionName, m_OutboundDirectionName;
|
||||
|
||||
/// Stores HTTP configuration (proxy, OAuth, etc).
|
||||
web::http::client::http_client_config m_HttpConfig;
|
||||
|
||||
/// Password for user with o365 subscription.
|
||||
web::details::win32_encryption m_Password;
|
||||
|
||||
WinHttp::WebProxy m_ProxyConfig;
|
||||
std::string m_username;
|
||||
std::string m_Password;
|
||||
std::string m_clientKey;
|
||||
std::string m_token;
|
||||
/// Used to delay every channel instance in case of server rate limit.
|
||||
/// Set using information from 429 Too Many Requests header.
|
||||
static std::atomic<std::chrono::steady_clock::time_point> s_TimePoint;
|
||||
};
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue