mirror of https://github.com/infosecn1nja/C3.git
Move onedrive/outlook common methods to Office365.h
parent
74caf37f10
commit
f4c3313c74
|
@ -52,6 +52,7 @@
|
|||
<ClInclude Include="$(MSBuildThisFileDirectory)CppCodec\base32_default_crockford.hpp" />
|
||||
<ClInclude Include="$(MSBuildThisFileDirectory)CppCodec\base64_default_rfc4648.hpp" />
|
||||
<ClInclude Include="$(MSBuildThisFileDirectory)CppRestSdk\include\cpprest\http_client.h" />
|
||||
<ClInclude Include="$(MSBuildThisFileDirectory)FSecure\C3\Interfaces\Channels\Office365.h" />
|
||||
<ClInclude Include="$(MSBuildThisFileDirectory)FSecure\C3\Interfaces\Channels\OneDrive365RestFile.h" />
|
||||
<ClInclude Include="$(MSBuildThisFileDirectory)FSecure\C3\Interfaces\Channels\Outlook365RestTask.h" />
|
||||
<ClInclude Include="$(MSBuildThisFileDirectory)FSecure\CppTools\ByteConverter\ByteArray.h" />
|
||||
|
|
|
@ -107,5 +107,6 @@
|
|||
<ClInclude Include="$(MSBuildThisFileDirectory)FSecure\C3\Interfaces\Peripherals\Grunt.h" />
|
||||
<ClInclude Include="$(MSBuildThisFileDirectory)FSecure\C3\Interfaces\Peripherals\Mock.h" />
|
||||
<ClInclude Include="$(MSBuildThisFileDirectory)FSecure\Crypto\String.h" />
|
||||
<ClInclude Include="$(MSBuildThisFileDirectory)FSecure\C3\Interfaces\Channels\Office365.h" />
|
||||
</ItemGroup>
|
||||
</Project>
|
|
@ -0,0 +1,233 @@
|
|||
#pragma once
|
||||
|
||||
#include "Common/FSecure/WinHttp/HttpClient.h"
|
||||
#include "Common/FSecure/WinHttp/HttpRequest.h"
|
||||
#include "Common/FSecure/WinHttp/WebProxy.h"
|
||||
#include "Common/FSecure/WinHttp/Constants.h"
|
||||
#include "Common/FSecure/Crypto/String.h"
|
||||
|
||||
namespace FSecure::C3::Interfaces::Channels
|
||||
{
|
||||
/// Abstract of using Office365 API
|
||||
template <typename Derived>
|
||||
class Office365
|
||||
{
|
||||
public:
|
||||
/// Public constructor.
|
||||
/// @param arguments factory arguments.
|
||||
Office365(ByteView arguments)
|
||||
: m_InboundDirectionName{ arguments.Read<std::string>() }
|
||||
, m_OutboundDirectionName{ arguments.Read<std::string>() }
|
||||
, m_Username{ arguments.Read<SecureString>() }
|
||||
, m_Password{ arguments.Read<SecureString>() }
|
||||
, m_ClientKey{ arguments.Read<SecureString>() }
|
||||
{
|
||||
FSecure::Utils::DisallowChars({ m_InboundDirectionName, m_OutboundDirectionName }, OBF(R"(;/?:@&=+$,)"));
|
||||
|
||||
// Obtain proxy information and store it in the HTTP configuration.
|
||||
if (auto winProxy = WinTools::GetProxyConfiguration(); !winProxy.empty())
|
||||
this->m_ProxyConfig = (winProxy == OBF(L"auto")) ? WebProxy(WebProxy::Mode::UseAutoDiscovery) : WebProxy(winProxy);
|
||||
|
||||
RefreshAccessToken();
|
||||
}
|
||||
|
||||
/// Get channel capability.
|
||||
/// @returns ByteView view of channel capability.
|
||||
static ByteView GetCapability();
|
||||
|
||||
protected:
|
||||
/// Remove one file from server.
|
||||
/// @param id of task.
|
||||
void RemoveFile(std::string const& id)
|
||||
{
|
||||
auto webClient = HttpClient{ Convert<Utf16>(Derived::ItemEndpont.Decrypt() + SecureString{id}), m_ProxyConfig };
|
||||
auto request = CreateAuthRequest(Method::DEL);
|
||||
auto resp = webClient.Request(request);
|
||||
|
||||
if (resp.GetStatusCode() > 205)
|
||||
throw std::runtime_error{ OBF("RemoveFile() Error. Task ") + id + OBF(" could not be deleted. HTTP response:") + std::to_string(resp.GetStatusCode()) };
|
||||
}
|
||||
|
||||
/// Removes all file from server.
|
||||
/// @param ByteView unused.
|
||||
/// @returns ByteVector empty vector.
|
||||
void RemoveAllFiles()
|
||||
{
|
||||
auto fileList = ListData();
|
||||
for (auto& element : fileList.at(OBF("value")))
|
||||
RemoveFile(element.at(OBF("id")).get<std::string>());
|
||||
}
|
||||
|
||||
/// Requests a new access token using the refresh token
|
||||
/// @throws std::exception if token cannot be refreshed.
|
||||
void RefreshAccessToken()
|
||||
{
|
||||
try
|
||||
{
|
||||
//Token endpoint
|
||||
auto webClient = HttpClient{ Convert<Utf16>(Derived::TokenEndpoit.Decrypt()), m_ProxyConfig };
|
||||
|
||||
auto request = HttpRequest{ Method::POST };
|
||||
request.SetHeader(Header::ContentType, OBF(L"application/x-www-form-urlencoded; charset=utf-16"));
|
||||
|
||||
auto requestBody = SecureString{};
|
||||
requestBody += OBF("grant_type=password");
|
||||
requestBody += OBF("&scope=");
|
||||
requestBody += Derived::Scope.Decrypt();
|
||||
requestBody += OBF("&username=");
|
||||
requestBody += m_Username.Decrypt();
|
||||
requestBody += OBF("&password=");
|
||||
requestBody += m_Password.Decrypt();
|
||||
requestBody += OBF("&client_id=");
|
||||
requestBody += m_ClientKey.Decrypt();
|
||||
|
||||
request.SetData(ContentType::ApplicationXWwwFormUrlencoded, { requestBody.begin(), requestBody.end() });
|
||||
auto resp = webClient.Request(request);
|
||||
EvaluateResponse(resp, false);
|
||||
|
||||
auto data = json::parse(resp.GetData());
|
||||
m_Token = data[OBF("access_token")].get<std::string>();
|
||||
}
|
||||
catch (std::exception & exception)
|
||||
{
|
||||
throw std::runtime_error{ OBF_STR("Cannot refresh token: ") + exception.what() };
|
||||
}
|
||||
}
|
||||
|
||||
/// Check if request was successful.
|
||||
/// @throws std::exception describing incorrect response if occurred.
|
||||
void EvaluateResponse(WinHttp::HttpResponse const& resp, bool tryRefreshingToken = true)
|
||||
{
|
||||
|
||||
if (resp.GetStatusCode() == StatusCode::OK || resp.GetStatusCode() == StatusCode::Created)
|
||||
return;
|
||||
|
||||
if (resp.GetStatusCode() == StatusCode::TooManyRequests) // break and set sleep time.
|
||||
{
|
||||
auto retryAfterHeader = resp.GetHeader(Header::RetryAfter);
|
||||
auto delay = !retryAfterHeader.empty() ? stoul(retryAfterHeader) : FSecure::Utils::GenerateRandomValue(10, 20);
|
||||
s_TimePoint = std::chrono::steady_clock::now() + std::chrono::seconds{ delay };
|
||||
throw std::runtime_error{ OBF("Too many requests") };
|
||||
}
|
||||
|
||||
if (resp.GetStatusCode() == StatusCode::Unauthorized)
|
||||
{
|
||||
if (tryRefreshingToken)
|
||||
RefreshAccessToken();
|
||||
throw std::runtime_error{ OBF("HTTP 401 - Token being refreshed") };
|
||||
}
|
||||
|
||||
if (resp.GetStatusCode() == StatusCode::BadRequest)
|
||||
throw std::runtime_error{ OBF("Bad Request") };
|
||||
|
||||
throw std::runtime_error{ OBF("Non 200 http response.") + std::to_string(resp.GetStatusCode()) };
|
||||
}
|
||||
|
||||
/// Create request using internally stored token.
|
||||
/// @param method, request type.
|
||||
WinHttp::HttpRequest CreateAuthRequest(WinHttp::Method method = WinHttp::Method::GET)
|
||||
{
|
||||
auto request = HttpRequest{ method };
|
||||
request.SetHeader(Header::Authorization, Convert<Utf16>(OBF_SEC("Bearer ") + m_Token.Decrypt()));
|
||||
return request;
|
||||
}
|
||||
|
||||
/// Server will reject request send before s_TimePoint. This method will await to this point plus extra random delay.
|
||||
/// @param min lower limit of random delay.
|
||||
/// @param max upper limit of random delay.
|
||||
void RateLimitDelay(std::chrono::milliseconds min, std::chrono::milliseconds max)
|
||||
{
|
||||
if (s_TimePoint.load() > std::chrono::steady_clock::now())
|
||||
std::this_thread::sleep_until(s_TimePoint.load() + FSecure::Utils::GenerateRandomValue(min, max));
|
||||
}
|
||||
|
||||
/// List files on server.
|
||||
/// @param filter flags to filter data from server.
|
||||
/// @return json server response.
|
||||
json ListData(std::string_view filter = {})
|
||||
{
|
||||
auto webClient = HttpClient{ Convert<Utf16>(Derived::ListEndpoint.Decrypt() + SecureString{ filter }), m_ProxyConfig };
|
||||
auto request = CreateAuthRequest();
|
||||
auto resp = webClient.Request(request);
|
||||
EvaluateResponse(resp);
|
||||
|
||||
return json::parse(resp.GetData());
|
||||
}
|
||||
|
||||
/// In/Out names on the server.
|
||||
std::string m_InboundDirectionName, m_OutboundDirectionName;
|
||||
|
||||
/// Username, password, client key and token for authentication.
|
||||
Crypto::String m_Username, m_Password, m_ClientKey, m_Token;
|
||||
|
||||
/// Store any relevant proxy info
|
||||
WinHttp::WebProxy m_ProxyConfig;
|
||||
|
||||
/// 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;
|
||||
};
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
template <typename Derived>
|
||||
std::atomic<std::chrono::steady_clock::time_point> FSecure::C3::Interfaces::Channels::Office365<Derived>::s_TimePoint = std::chrono::steady_clock::now();
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
template <typename Derived>
|
||||
FSecure::ByteView FSecure::C3::Interfaces::Channels::Office365<Derived>::GetCapability()
|
||||
{
|
||||
return R"_(
|
||||
{
|
||||
"create":
|
||||
{
|
||||
"arguments":
|
||||
[
|
||||
[
|
||||
{
|
||||
"type": "string",
|
||||
"name": "Input ID",
|
||||
"min": 4,
|
||||
"randomize": true,
|
||||
"description": "Used to distinguish packets for the channel"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"name": "Output ID",
|
||||
"min": 4,
|
||||
"randomize": true,
|
||||
"description": "Used to distinguish packets from the channel"
|
||||
}
|
||||
],
|
||||
{
|
||||
"type": "string",
|
||||
"name": "username",
|
||||
"min": 1,
|
||||
"description": "The O365 user"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"name": "password",
|
||||
"min": 1,
|
||||
"description": "The user's password"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"name": "Client Key/ID",
|
||||
"min": 1,
|
||||
"description": "The GUID of the registered application."
|
||||
}
|
||||
]
|
||||
},
|
||||
"commands":
|
||||
[
|
||||
{
|
||||
"name": "Clear channel",
|
||||
"id": 0,
|
||||
"description": "Clearing old files from server. May increase bandwidth",
|
||||
"arguments": []
|
||||
}
|
||||
]
|
||||
}
|
||||
)_";
|
||||
}
|
|
@ -13,35 +13,22 @@ using json = nlohmann::json;
|
|||
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();
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
FSecure::C3::Interfaces::Channels::OneDrive365RestFile::OneDrive365RestFile(ByteView arguments)
|
||||
: m_InboundDirectionName{ arguments.Read<std::string>() }
|
||||
, m_OutboundDirectionName{ arguments.Read<std::string>() }
|
||||
, m_Username{ arguments.Read<SecureString>() }
|
||||
, m_Password{ arguments.Read<SecureString>() }
|
||||
, m_ClientKey{ arguments.Read<SecureString>() }
|
||||
{
|
||||
FSecure::Utils::DisallowChars({ m_InboundDirectionName, m_OutboundDirectionName }, OBF(R"(;/?:@&=+$,)"));
|
||||
|
||||
// Obtain proxy information and store it in the HTTP configuration.
|
||||
if (auto winProxy = WinTools::GetProxyConfiguration(); !winProxy.empty())
|
||||
this->m_ProxyConfig = (winProxy == OBF(L"auto")) ? WebProxy(WebProxy::Mode::UseAutoDiscovery) : WebProxy(winProxy);
|
||||
|
||||
RefreshAccessToken();
|
||||
}
|
||||
FSecure::Crypto::String FSecure::C3::Interfaces::Channels::OneDrive365RestFile::RootEndpoint = OBF("https://graph.microsoft.com/v1.0/me/drive/root:/");
|
||||
FSecure::Crypto::String FSecure::C3::Interfaces::Channels::OneDrive365RestFile::ItemEndpont = OBF("https://graph.microsoft.com/v1.0/me/drive/items/");
|
||||
FSecure::Crypto::String FSecure::C3::Interfaces::Channels::OneDrive365RestFile::ListEndpoint = OBF("https://graph.microsoft.com/v1.0/me/drive/root/children");
|
||||
FSecure::Crypto::String FSecure::C3::Interfaces::Channels::OneDrive365RestFile::TokenEndpoit = OBF("https://login.windows.net/organizations/oauth2/v2.0/token");
|
||||
FSecure::Crypto::String FSecure::C3::Interfaces::Channels::OneDrive365RestFile::Scope = OBF("files.readwrite.all");
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
size_t FSecure::C3::Interfaces::Channels::OneDrive365RestFile::OnSendToChannel(ByteView data)
|
||||
{
|
||||
if (s_TimePoint.load() > std::chrono::steady_clock::now())
|
||||
std::this_thread::sleep_until(s_TimePoint.load() + FSecure::Utils::GenerateRandomValue(m_MinUpdateDelay, m_MaxUpdateDelay));
|
||||
RateLimitDelay(m_MinUpdateDelay, m_MaxUpdateDelay);
|
||||
|
||||
try
|
||||
{
|
||||
// Construct the HTTP request
|
||||
auto URLwithFilename = OBF("https://graph.microsoft.com/v1.0/me/drive/root:/") + m_OutboundDirectionName + OBF("-") + FSecure::Utils::GenerateRandomString(20) + OBF(".json") + OBF(":/content");
|
||||
auto URLwithFilename = RootEndpoint.Decrypt().c_str() + m_OutboundDirectionName + OBF("-") + FSecure::Utils::GenerateRandomString(20) + OBF(".json") + OBF(":/content");
|
||||
auto webClient = HttpClient{ Convert<Utf16>(URLwithFilename), m_ProxyConfig };
|
||||
auto request = CreateAuthRequest(Method::PUT);
|
||||
|
||||
|
@ -52,7 +39,7 @@ size_t FSecure::C3::Interfaces::Channels::OneDrive365RestFile::OnSendToChannel(B
|
|||
fileData[OBF("data")] = cppcodec::base64_rfc4648::encode(&data.front(), chunkSize);
|
||||
|
||||
auto body = fileData.dump();
|
||||
request.SetData(OBF(L"text/plain"), { body.begin(), body.end() });
|
||||
request.SetData(ContentType::TextPlain, { body.begin(), body.end() });
|
||||
EvaluateResponse(webClient.Request(request));
|
||||
|
||||
return chunkSize;
|
||||
|
@ -67,25 +54,20 @@ size_t FSecure::C3::Interfaces::Channels::OneDrive365RestFile::OnSendToChannel(B
|
|||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
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));
|
||||
RateLimitDelay(m_MinUpdateDelay, m_MaxUpdateDelay);
|
||||
|
||||
auto packets = std::vector<ByteVector>{};
|
||||
try
|
||||
{
|
||||
auto webClient = HttpClient{ OBF(L"https://graph.microsoft.com/v1.0/me/drive/root/children?top=1000&filter=startswith(name,'") + Convert<Utf16>(m_InboundDirectionName) + OBF(L"')"), m_ProxyConfig };
|
||||
auto request = CreateAuthRequest(); HttpRequest{};
|
||||
auto resp = webClient.Request(request);
|
||||
EvaluateResponse(resp);
|
||||
|
||||
auto taskDataAsJSON = json::parse(resp.GetData());
|
||||
auto fileList = ListData(OBF("?top=1000&filter=startswith(name,'") + m_InboundDirectionName + OBF("')"));
|
||||
|
||||
// First iterate over the json and populate an array of the files we want.
|
||||
auto elements = std::vector<json>{};
|
||||
for (auto& element : taskDataAsJSON.at(OBF("value")))
|
||||
for (auto& element : fileList.at(OBF("value")))
|
||||
{
|
||||
//download the file
|
||||
auto webClientFile = HttpClient{ Convert<Utf16>(element.at(OBF("@microsoft.graph.downloadUrl")).get<std::string>()), m_ProxyConfig };
|
||||
auto request = CreateAuthRequest();
|
||||
auto resp = webClientFile.Request(request);
|
||||
EvaluateResponse(resp);
|
||||
|
||||
|
@ -114,82 +96,6 @@ std::vector<FSecure::ByteVector> FSecure::C3::Interfaces::Channels::OneDrive365R
|
|||
return packets;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
void FSecure::C3::Interfaces::Channels::OneDrive365RestFile::RemoveFile(std::string const& id)
|
||||
{
|
||||
auto webClient = HttpClient{ OBF(L"https://graph.microsoft.com/v1.0/me/drive/items/") + Convert<Utf16>(id), m_ProxyConfig };
|
||||
auto request = CreateAuthRequest(Method::DEL);
|
||||
auto resp = webClient.Request(request);
|
||||
|
||||
if (resp.GetStatusCode() > 205)
|
||||
throw std::runtime_error{ OBF("RemoveFile() Error. Task ") + id + OBF(" could not be deleted. HTTP response:") + std::to_string(resp.GetStatusCode()) };
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
FSecure::ByteVector FSecure::C3::Interfaces::Channels::OneDrive365RestFile::RemoveAllFiles(ByteView)
|
||||
{
|
||||
auto webClient = HttpClient{ OBF(L"https://graph.microsoft.com/v1.0/me/drive/root/children"), m_ProxyConfig };
|
||||
auto request = CreateAuthRequest();
|
||||
auto resp = webClient.Request(request);
|
||||
try
|
||||
{
|
||||
EvaluateResponse(resp);
|
||||
}
|
||||
catch (const std::exception & exception)
|
||||
{
|
||||
Log({ OBF_SEC("Caught a std::exception when running RemoveAllFiles(): ") + exception.what(), LogMessage::Severity::Error });
|
||||
return {};
|
||||
}
|
||||
|
||||
// For each task (under the "value" key), extract the ID, and send a request to delete the task.
|
||||
auto taskDataAsJSON = nlohmann::json::parse(resp.GetData());
|
||||
for (auto& element : taskDataAsJSON.at(OBF("value")))
|
||||
try
|
||||
{
|
||||
RemoveFile(element.at(OBF("id")).get<std::string>());
|
||||
}
|
||||
catch (const std::exception& exception)
|
||||
{
|
||||
Log({ OBF_SEC("Caught a std::exception when running RemoveAllFiles(): ") + exception.what(), LogMessage::Severity::Error });
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
void FSecure::C3::Interfaces::Channels::OneDrive365RestFile::RefreshAccessToken()
|
||||
{
|
||||
try
|
||||
{
|
||||
//Token endpoint
|
||||
auto webClient = HttpClient{ OBF(L"https://login.windows.net/organizations/oauth2/v2.0/token"), m_ProxyConfig };
|
||||
|
||||
auto request = HttpRequest{ Method::POST };
|
||||
request.SetHeader(Header::ContentType, OBF(L"application/x-www-form-urlencoded; charset=utf-16"));
|
||||
|
||||
auto requestBody = SecureString{};
|
||||
requestBody += OBF("grant_type=password");
|
||||
requestBody += OBF("&scope=files.readwrite.all");
|
||||
requestBody += OBF("&username=");
|
||||
requestBody += m_Username.Decrypt();
|
||||
requestBody += OBF("&password=");
|
||||
requestBody += m_Password.Decrypt();
|
||||
requestBody += OBF("&client_id=");
|
||||
requestBody += m_ClientKey.Decrypt();
|
||||
|
||||
request.SetData(ContentType::ApplicationXWwwFormUrlencoded, { requestBody.begin(), requestBody.end() });
|
||||
auto resp = webClient.Request(request);
|
||||
EvaluateResponse(resp, false);
|
||||
|
||||
auto data = json::parse(resp.GetData());
|
||||
m_Token = data[OBF("access_token")].get<std::string>();
|
||||
}
|
||||
catch (std::exception& exception)
|
||||
{
|
||||
throw std::runtime_error{ OBF_STR("Cannot refresh token: ") + exception.what() };
|
||||
}
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
FSecure::ByteVector FSecure::C3::Interfaces::Channels::OneDrive365RestFile::OnRunCommand(ByteView command)
|
||||
{
|
||||
|
@ -197,102 +103,16 @@ FSecure::ByteVector FSecure::C3::Interfaces::Channels::OneDrive365RestFile::OnRu
|
|||
switch (command.Read<uint16_t>())
|
||||
{
|
||||
case 0:
|
||||
return RemoveAllFiles(command);
|
||||
try
|
||||
{
|
||||
RemoveAllFiles();
|
||||
}
|
||||
catch (std::exception const& e)
|
||||
{
|
||||
Log({ OBF_SEC("Caught a std::exception when running RemoveAllFiles(): ") + e.what(), LogMessage::Severity::Error });
|
||||
}
|
||||
return {};
|
||||
default:
|
||||
return AbstractChannel::OnRunCommand(commandCopy);
|
||||
}
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
void FSecure::C3::Interfaces::Channels::OneDrive365RestFile::EvaluateResponse(WinHttp::HttpResponse const& resp, bool tryRefreshingToken)
|
||||
{
|
||||
|
||||
if (resp.GetStatusCode() == StatusCode::OK || resp.GetStatusCode() == StatusCode::Created)
|
||||
return;
|
||||
|
||||
if (resp.GetStatusCode() == StatusCode::TooManyRequests) // break and set sleep time.
|
||||
{
|
||||
auto retryAfterHeader = resp.GetHeader(Header::RetryAfter);
|
||||
auto delay = !retryAfterHeader.empty() ? stoul(retryAfterHeader) : FSecure::Utils::GenerateRandomValue(10, 20);
|
||||
s_TimePoint = std::chrono::steady_clock::now() + std::chrono::seconds{ delay };
|
||||
throw std::runtime_error{ OBF("Too many requests") };
|
||||
}
|
||||
|
||||
if (resp.GetStatusCode() == StatusCode::Unauthorized)
|
||||
{
|
||||
if (tryRefreshingToken)
|
||||
RefreshAccessToken();
|
||||
throw std::runtime_error{ OBF("HTTP 401 - Token being refreshed") };
|
||||
}
|
||||
|
||||
if (resp.GetStatusCode() == StatusCode::BadRequest)
|
||||
throw std::runtime_error{ OBF("Bad Request") };
|
||||
|
||||
throw std::runtime_error{ OBF("Non 200 http response.") + std::to_string(resp.GetStatusCode()) };
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
FSecure::WinHttp::HttpRequest FSecure::C3::Interfaces::Channels::OneDrive365RestFile::CreateAuthRequest(WinHttp::Method method)
|
||||
{
|
||||
auto request = HttpRequest{ method };
|
||||
request.SetHeader(Header::Authorization, Convert<Utf16>(OBF_SEC("Bearer ") + m_Token.Decrypt()));
|
||||
return request;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
FSecure::ByteView FSecure::C3::Interfaces::Channels::OneDrive365RestFile::GetCapability()
|
||||
{
|
||||
return R"_(
|
||||
{
|
||||
"create":
|
||||
{
|
||||
"arguments":
|
||||
[
|
||||
[
|
||||
{
|
||||
"type": "string",
|
||||
"name": "Input ID",
|
||||
"min": 4,
|
||||
"randomize": true,
|
||||
"description": "Used to distinguish packets for the channel"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"name": "Output ID",
|
||||
"min": 4,
|
||||
"randomize": true,
|
||||
"description": "Used to distinguish packets from the channel"
|
||||
}
|
||||
],
|
||||
{
|
||||
"type": "string",
|
||||
"name": "username",
|
||||
"min": 1,
|
||||
"description": "The O365 user"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"name": "password",
|
||||
"min": 1,
|
||||
"description": "The user's password"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"name": "Client Key/ID",
|
||||
"min": 1,
|
||||
"description": "The GUID of the registered application."
|
||||
}
|
||||
]
|
||||
},
|
||||
"commands":
|
||||
[
|
||||
{
|
||||
"name": "Clear channel",
|
||||
"id": 0,
|
||||
"description": "Clearing old files from server. May increase bandwidth",
|
||||
"arguments": []
|
||||
}
|
||||
]
|
||||
}
|
||||
)_";
|
||||
}
|
||||
|
|
|
@ -1,20 +1,14 @@
|
|||
#pragma once
|
||||
|
||||
#include "Common/FSecure/WinHttp/HttpClient.h"
|
||||
#include "Common/FSecure/WinHttp/HttpRequest.h"
|
||||
#include "Common/FSecure/WinHttp/WebProxy.h"
|
||||
#include "Common/FSecure/WinHttp/Constants.h"
|
||||
#include "Common/FSecure/Crypto/String.h"
|
||||
#include "Office365.h"
|
||||
|
||||
namespace FSecure::C3::Interfaces::Channels
|
||||
{
|
||||
/// Implementation of the OneDrive 365 REST file Channel.
|
||||
class OneDrive365RestFile : public Channel<OneDrive365RestFile>
|
||||
class OneDrive365RestFile : public Channel<OneDrive365RestFile>, public Office365<OneDrive365RestFile>
|
||||
{
|
||||
public:
|
||||
/// Public constructor.
|
||||
/// @param arguments factory arguments.
|
||||
OneDrive365RestFile(ByteView arguments);
|
||||
/// Use Office365 constructor.
|
||||
using Office365<OneDrive365RestFile>::Office365;
|
||||
|
||||
/// OnSend callback implementation.
|
||||
/// @param blob data to send to Channel.
|
||||
|
@ -30,48 +24,17 @@ namespace FSecure::C3::Interfaces::Channels
|
|||
/// @return command result.
|
||||
ByteVector OnRunCommand(ByteView command) override;
|
||||
|
||||
/// Get channel capability.
|
||||
/// @returns ByteView view of channel capability.
|
||||
static ByteView GetCapability();
|
||||
|
||||
/// 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 = 1000ms, s_MaxUpdateDelay = 1000ms;
|
||||
|
||||
protected:
|
||||
/// Removes all file from server.
|
||||
/// @param ByteView unused.
|
||||
/// @returns ByteVector empty vector.
|
||||
ByteVector RemoveAllFiles(ByteView);
|
||||
/// Endpoint used to add file to OneDrive
|
||||
static Crypto::String RootEndpoint;
|
||||
|
||||
/// Remove one file from server.
|
||||
/// @param id of task.
|
||||
void RemoveFile(std::string const& id);
|
||||
|
||||
/// Requests a new access token using the refresh token
|
||||
/// @throws std::exception if token cannot be refreshed.
|
||||
void RefreshAccessToken();
|
||||
|
||||
/// Check if request was successful.
|
||||
/// @throws std::exception describing incorrect response if occurred.
|
||||
void EvaluateResponse(WinHttp::HttpResponse const& resp, bool tryRefreshingToken = true);
|
||||
|
||||
/// Create request using internally stored token.
|
||||
/// @param method, request type.
|
||||
WinHttp::HttpRequest CreateAuthRequest(WinHttp::Method method = WinHttp::Method::GET);
|
||||
|
||||
/// In/Out names on the server.
|
||||
std::string m_InboundDirectionName, m_OutboundDirectionName;
|
||||
|
||||
/// Username, password, client key and token for authentication.
|
||||
Crypto::String m_Username, m_Password, m_ClientKey, m_Token;
|
||||
|
||||
/// Store any relevant proxy info
|
||||
WinHttp::WebProxy m_ProxyConfig;
|
||||
|
||||
|
||||
/// 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;
|
||||
/// Endpoints used by Office365 methods.
|
||||
static Crypto::String ItemEndpont;
|
||||
static Crypto::String ListEndpoint;
|
||||
static Crypto::String TokenEndpoit;
|
||||
static Crypto::String Scope;
|
||||
};
|
||||
}
|
||||
|
|
|
@ -8,184 +8,62 @@
|
|||
#include "Common/FSecure/WinHttp/Constants.h"
|
||||
#include "Common/FSecure/WinHttp/Uri.h"
|
||||
|
||||
// Namespaces.
|
||||
// Namespaces
|
||||
using json = nlohmann::json;
|
||||
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();
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
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())
|
||||
this->m_ProxyConfig = (winProxy == OBF(L"auto")) ? WebProxy(WebProxy::Mode::UseAutoDiscovery) : WebProxy(winProxy);
|
||||
|
||||
RefreshAccessToken();
|
||||
}
|
||||
FSecure::Crypto::String FSecure::C3::Interfaces::Channels::Outlook365RestTask::ItemEndpont = OBF("https://outlook.office.com/api/v2.0/me/tasks/");
|
||||
FSecure::Crypto::String FSecure::C3::Interfaces::Channels::Outlook365RestTask::ListEndpoint = OBF("https://outlook.office.com/api/v2.0/me/tasks");
|
||||
FSecure::Crypto::String FSecure::C3::Interfaces::Channels::Outlook365RestTask::TokenEndpoit = OBF("https://login.windows.net/organizations/oauth2/v2.0/token/");
|
||||
FSecure::Crypto::String FSecure::C3::Interfaces::Channels::Outlook365RestTask::Scope = OBF("https://outlook.office365.com/.default");
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
size_t FSecure::C3::Interfaces::Channels::Outlook365RestTask::OnSendToChannel(ByteView data)
|
||||
{
|
||||
if (s_TimePoint.load() > std::chrono::steady_clock::now())
|
||||
std::this_thread::sleep_until(s_TimePoint.load() + FSecure::Utils::GenerateRandomValue(m_MinUpdateDelay, m_MaxUpdateDelay));
|
||||
RateLimitDelay(m_MinUpdateDelay, m_MaxUpdateDelay);
|
||||
|
||||
try
|
||||
{
|
||||
// Construct the HTTP request
|
||||
HttpClient webClient(OBF(L"https://outlook.office.com/api/v2.0/me/tasks"), m_ProxyConfig);
|
||||
auto webClient = HttpClient{ Convert<Utf16>(ItemEndpont.Decrypt()), m_ProxyConfig };
|
||||
auto request = CreateAuthRequest(Method::POST);
|
||||
|
||||
HttpRequest request; // default request is GET
|
||||
std::string auth = "Bearer " + m_Token;
|
||||
request.SetHeader(Header::Authorization, Convert<Utf16>(auth));
|
||||
request.m_Method = Method::POST;
|
||||
auto chunkSize = std::min<size_t>(data.size(), 3 * 1024 * 1024); // Send max 4 MB. base64 will expand data by 4/3.
|
||||
auto fileData = json();
|
||||
fileData[OBF("Subject")] = m_OutboundDirectionName;
|
||||
fileData[OBF("Body")][OBF("Content")] = cppcodec::base64_rfc4648::encode(&data.front(), data.size());
|
||||
fileData[OBF("Body")][OBF("ContentType")] = OBF("Text");
|
||||
|
||||
// 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();
|
||||
auto body = fileData.dump();
|
||||
request.SetData(ContentType::ApplicationJson, { body.begin(), body.end() });
|
||||
request.m_ContentType = L"application/json";
|
||||
EvaluateResponse(webClient.Request(request));
|
||||
|
||||
auto resp = webClient.Request(request);
|
||||
|
||||
if (resp.GetStatusCode() != StatusCode::OK && resp.GetStatusCode() != StatusCode::Created)
|
||||
{
|
||||
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 (resp.GetStatusCode() == StatusCode::Unauthorized)
|
||||
{
|
||||
RefreshAccessToken();
|
||||
throw std::runtime_error{ OBF("HTTP 401 - Token being refreshed") };
|
||||
|
||||
}
|
||||
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(resp.GetStatusCode()) };
|
||||
}
|
||||
|
||||
return data.size();
|
||||
return chunkSize;
|
||||
}
|
||||
catch (std::exception& exception)
|
||||
catch (std::exception & exception)
|
||||
{
|
||||
Log({ OBF_SEC("Caught a std::exception when running OnSend(): ") + exception.what(), LogMessage::Severity::Warning });
|
||||
Log({ OBF_SEC("Caught a std::exception when running OnSend(): ") + exception.what(), LogMessage::Severity::Error });
|
||||
return 0u;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
std::vector<FSecure::ByteVector> FSecure::C3::Interfaces::Channels::Outlook365RestTask::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));
|
||||
RateLimitDelay(m_MinUpdateDelay, m_MaxUpdateDelay);
|
||||
|
||||
std::vector<ByteVector> packets;
|
||||
auto packets = std::vector<ByteVector>{};
|
||||
try
|
||||
{
|
||||
// Construct request to get tasks.
|
||||
// Filtered by subjects that start with m_InboundDirectionName, order by oldest first, and fetch 1000 tasks.
|
||||
// Example: https://outlook.office.com/api/v2.0/me/tasks?top=1000&filter=startswith(Subject,'C2S')&orderby=CreatedDateTime
|
||||
std::string URLwithInboundDirection = OBF("https://outlook.office.com/api/v2.0/me/tasks?top=");
|
||||
URLwithInboundDirection += OBF("1000"); // number of tasks to fetch
|
||||
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)
|
||||
auto fileList = ListData(OBF("?top=1000&filter=startswith(Subject,'") + m_InboundDirectionName + OBF("')&orderby=CreatedDateTime"));
|
||||
|
||||
HttpClient webClient(Convert<Utf16>(URLwithInboundDirection), m_ProxyConfig);
|
||||
for (auto& element : fileList.at(OBF("value")))
|
||||
packets.emplace_back(cppcodec::base64_rfc4648::decode(element.at(OBF("Body")).at(OBF("Content")).get<std::string>()));
|
||||
|
||||
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 (resp.GetStatusCode() == StatusCode::TooManyRequests)
|
||||
{
|
||||
s_TimePoint = std::chrono::steady_clock::now() + FSecure::Utils::GenerateRandomValue(10s, 20s);
|
||||
throw std::runtime_error{ OBF("Too many requests") };
|
||||
}
|
||||
|
||||
if (resp.GetStatusCode() == StatusCode::Unauthorized)
|
||||
{
|
||||
RefreshAccessToken();
|
||||
throw std::runtime_error{ OBF("HTTP 401 - Token being refreshed") };
|
||||
|
||||
}
|
||||
|
||||
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(resp.GetStatusCode()) };
|
||||
}
|
||||
|
||||
auto responseData = resp.GetData();
|
||||
// Gracefully handle situation where there's an empty JSON value (e.g., a failed request)
|
||||
if (responseData.size() == 0)
|
||||
return {};
|
||||
|
||||
// Convert response (as string_t to utf8) and parse.
|
||||
json taskDataAsJSON;
|
||||
try
|
||||
{
|
||||
taskDataAsJSON = json::parse(responseData);
|
||||
}
|
||||
catch (json::parse_error&)
|
||||
{
|
||||
Log({ OBF("Failed to parse the list of received tasks."), LogMessage::Severity::Error });
|
||||
return {};
|
||||
}
|
||||
|
||||
for (auto& element : taskDataAsJSON.at(OBF("value")))
|
||||
{
|
||||
// Obtain subject and task ID.
|
||||
std::string subject = element.at(OBF("Subject")).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 (subject.empty() || id.empty())
|
||||
continue;
|
||||
|
||||
// Check the direction component is at the start of subject.
|
||||
if (subject.find(m_InboundDirectionName))
|
||||
continue;
|
||||
|
||||
try
|
||||
{
|
||||
// Send the (decoded) message's body.
|
||||
ByteVector packet = cppcodec::base64_rfc4648::decode(element.at(OBF("Body")).at(OBF("Content")).get<std::string>());
|
||||
packets.emplace_back(packet);
|
||||
SCOPE_GUARD(RemoveTask(id); );
|
||||
}
|
||||
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 task #") + id + OBF(" : ") + exception.what(), LogMessage::Severity::Error });
|
||||
}
|
||||
}
|
||||
for (auto& element : fileList.at(OBF("value")))
|
||||
RemoveFile(element.at(OBF("Id")));
|
||||
}
|
||||
catch (std::exception& exception)
|
||||
{
|
||||
|
@ -195,170 +73,23 @@ std::vector<FSecure::ByteVector> FSecure::C3::Interfaces::Channels::Outlook365Re
|
|||
return packets;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
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.
|
||||
HttpClient webClient(OBF(L"https://outlook.office.com/api/v2.0/me/tasks?$top=1000"), m_ProxyConfig);
|
||||
|
||||
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)
|
||||
{
|
||||
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(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")))
|
||||
RemoveTask(element.at(OBF("id")).get<std::string>());
|
||||
}
|
||||
catch (std::exception& exception)
|
||||
{
|
||||
Log({ OBF_SEC("Caught a std::exception when running RemoveAllTasks(): ") + exception.what(), LogMessage::Severity::Warning });
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
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("')");
|
||||
HttpClient webClient(Convert<Utf16>(URLwithID), 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::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;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
void FSecure::C3::Interfaces::Channels::Outlook365RestTask::RefreshAccessToken()
|
||||
{
|
||||
try
|
||||
{
|
||||
//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=https://outlook.office365.com/.default");
|
||||
requestBody += OBF("&username=");
|
||||
requestBody += m_Username;
|
||||
requestBody += OBF("&password=");
|
||||
requestBody += m_Password;
|
||||
requestBody += OBF("&client_id=");
|
||||
requestBody += m_ClientKey;
|
||||
|
||||
request.SetData(ContentType::ApplicationXWwwFormUrlencoded, { requestBody.begin(), requestBody.end() });
|
||||
FSecure::Utils::SecureMemzero(requestBody.data(), requestBody.size());
|
||||
auto resp = webClient.Request(request);
|
||||
|
||||
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()) };
|
||||
|
||||
m_Token = data["access_token"].get<std::string>();
|
||||
}
|
||||
catch (std::exception& exception)
|
||||
{
|
||||
throw std::runtime_error{ OBF_STR("Cannot refresh token: ") + exception.what() };
|
||||
}
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
FSecure::ByteVector FSecure::C3::Interfaces::Channels::Outlook365RestTask::OnRunCommand(ByteView command)
|
||||
{
|
||||
auto commandCopy = command; //each read moves ByteView. CommandCoppy is needed for default.
|
||||
auto commandCopy = command; // Each read moves ByteView. CommandCoppy is needed for default.
|
||||
switch (command.Read<uint16_t>())
|
||||
{
|
||||
case 0:
|
||||
return RemoveAllTasks(command);
|
||||
try
|
||||
{
|
||||
RemoveAllFiles();
|
||||
}
|
||||
catch (std::exception const& e)
|
||||
{
|
||||
Log({ OBF_SEC("Caught a std::exception when running RemoveAllFiles(): ") + e.what(), LogMessage::Severity::Error });
|
||||
}
|
||||
return {};
|
||||
default:
|
||||
return AbstractChannel::OnRunCommand(commandCopy);
|
||||
}
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
FSecure::ByteView FSecure::C3::Interfaces::Channels::Outlook365RestTask::GetCapability()
|
||||
{
|
||||
return R"_(
|
||||
{
|
||||
"create":
|
||||
{
|
||||
"arguments":
|
||||
[
|
||||
[
|
||||
{
|
||||
"type": "string",
|
||||
"name": "Input ID",
|
||||
"min": 4,
|
||||
"randomize": true,
|
||||
"description": "Used to distinguish packets for the channel"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"name": "Output ID",
|
||||
"min": 4,
|
||||
"randomize": true,
|
||||
"description": "Used to distinguish packets from the channel"
|
||||
}
|
||||
],
|
||||
{
|
||||
"type": "string",
|
||||
"name": "Username",
|
||||
"min": 0,
|
||||
"description": "User with Office 365 subscription."
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"name": "Password",
|
||||
"min": 0,
|
||||
"description": "User password."
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"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."
|
||||
}
|
||||
]
|
||||
},
|
||||
"commands":
|
||||
[
|
||||
{
|
||||
"name": "Remove all tasks",
|
||||
"id": 0,
|
||||
"description": "Clearing old tasks from server may increase bandwidth",
|
||||
"arguments": []
|
||||
}
|
||||
]
|
||||
}
|
||||
)_";
|
||||
}
|
||||
|
||||
|
|
|
@ -1,17 +1,15 @@
|
|||
#pragma once
|
||||
|
||||
#include "Common/FSecure/WinHttp/WebProxy.h"
|
||||
#include "Common/FSecure/WinHttp/Constants.h" //< For CppRestSdk.
|
||||
#include "Office365.h"
|
||||
|
||||
namespace FSecure::C3::Interfaces::Channels
|
||||
{
|
||||
/// Implementation of the Outlook365 REST tasks Device.
|
||||
class Outlook365RestTask : public Channel<Outlook365RestTask>
|
||||
class Outlook365RestTask : public Channel<Outlook365RestTask>, public Office365<Outlook365RestTask>
|
||||
{
|
||||
public:
|
||||
/// Public constructor.
|
||||
/// @param arguments factory arguments.
|
||||
Outlook365RestTask(ByteView arguments);
|
||||
/// Use Office365 constructor.
|
||||
using Office365<Outlook365RestTask>::Office365;
|
||||
|
||||
/// OnSend callback implementation.
|
||||
/// @param blob data to send to Channel.
|
||||
|
@ -27,40 +25,15 @@ namespace FSecure::C3::Interfaces::Channels
|
|||
/// @return command result.
|
||||
ByteVector OnRunCommand(ByteView command) override;
|
||||
|
||||
/// Get channel capability.
|
||||
/// @returns ByteView view of channel capability.
|
||||
static ByteView GetCapability();
|
||||
|
||||
/// 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 = 1000ms, s_MaxUpdateDelay = 1000ms;
|
||||
|
||||
protected:
|
||||
|
||||
/// Removes all tasks from server.
|
||||
/// @param ByteView unused.
|
||||
/// @returns ByteVector empty vector.
|
||||
ByteVector RemoveAllTasks(ByteView);
|
||||
|
||||
/// Remove one task from server.
|
||||
/// @param id of task.
|
||||
void RemoveTask(std::string const& id);
|
||||
|
||||
/// Requests a new access token using the refresh token
|
||||
/// @throws std::exception if token cannot be refreshed.
|
||||
void RefreshAccessToken();
|
||||
|
||||
/// In/Out names on the server.
|
||||
std::string m_InboundDirectionName, m_OutboundDirectionName;
|
||||
|
||||
std::string m_Username, m_Password, m_ClientKey, m_Token;
|
||||
|
||||
/// Stores HTTP configuration (proxy, OAuth, etc).
|
||||
WinHttp::WebProxy m_ProxyConfig;
|
||||
|
||||
/// 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;
|
||||
/// Endpoints used by Office365 methods.
|
||||
static Crypto::String ItemEndpont;
|
||||
static Crypto::String ListEndpoint;
|
||||
static Crypto::String TokenEndpoit;
|
||||
static Crypto::String Scope;
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -34,7 +34,7 @@ namespace FSecure::StringConversions
|
|||
{
|
||||
/// Detect character type stored by container.
|
||||
template <typename T>
|
||||
using CharT = std::remove_reference_t<decltype(std::declval<T>()[0])>;
|
||||
using CharT = std::remove_const_t<std::remove_reference_t<decltype(std::declval<T>()[0])>>;
|
||||
|
||||
/// Detect view type corresponding to container.
|
||||
template <typename T>
|
||||
|
|
Loading…
Reference in New Issue