| // Copyright 2018 The Fuchsia Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "src/ledger/bin/testing/sync_params.h" |
| |
| #include <fuchsia/net/oldhttp/cpp/fidl.h> |
| #include <lib/fsl/vmo/strings.h> |
| #include <openssl/sha.h> |
| |
| #include <iostream> |
| |
| #include "garnet/public/lib/rapidjson_utils/rapidjson_validation.h" |
| #include "peridot/lib/convert/convert.h" |
| #include "src/ledger/lib/firebase_auth/testing/credentials.h" |
| #include "src/lib/files/file.h" |
| #include "src/lib/fxl/strings/string_view.h" |
| |
| namespace { |
| |
| namespace http = ::fuchsia::net::oldhttp; |
| |
| constexpr fxl::StringView kSyncParamsSchema = R"({ |
| "type": "object", |
| "properties": { |
| "api-key": { |
| "type": "string" |
| }, |
| "service-account": { |
| "type": "object" |
| } |
| }, |
| "required": ["api-key", "service-account"] |
| })"; |
| |
| constexpr fxl::StringView kCredentialsPathFlag = "credentials-path"; |
| constexpr fxl::StringView kGnCredentialsPathArg = |
| "ledger_sync_credentials_file"; |
| constexpr fxl::StringView kCredentialsDefaultPath = |
| "/pkg/data/sync_credentials.json"; |
| |
| // URL that the sync infra bots use to pass the sync credentials to the tests. |
| constexpr fxl::StringView kCredentialsFetchUrl = |
| "http://10.0.2.2:8081/ledger_e2e_sync_credentials"; |
| |
| void WarnIncorrectSyncParams() { |
| std::cerr << "Missing the sync parameters." << std::endl; |
| std::cerr << "This binary needs an ID of a configured Firestore instance " |
| "to run along with access credentials. " |
| << std::endl; |
| std::cerr << "Please set the GN argument " << kGnCredentialsPathArg |
| << " at build time to embed the credentials in the binary " |
| << " or pass " << kCredentialsPathFlag |
| << " at run time to override the default location" << std::endl; |
| std::cerr << "If you're running it from a .tspec file, make sure " |
| "you add --append-args=\"" |
| "--" |
| << kCredentialsPathFlag << "=<file path>" << std::endl; |
| std::cerr << "if the access credentials are not embedded in the binary " |
| << "at build." << std::endl; |
| } |
| |
| // Fetches the sync credentials from the network. This method is synchronous and |
| // blocks until credentials are retrieved. This is intended exclusively for |
| // infra bots that will expose the credentials over the network when running |
| // sync tests. |
| bool FetchCredentials(sys::ComponentContext* component_context, |
| std::string* credentials_path, std::string* credentials) { |
| *credentials_path = kCredentialsFetchUrl.ToString(); |
| |
| fidl::SynchronousInterfacePtr<http::HttpService> network_service; |
| component_context->svc()->Connect(network_service.NewRequest()); |
| fidl::SynchronousInterfacePtr<http::URLLoader> url_loader; |
| |
| zx_status_t status = |
| network_service->CreateURLLoader(url_loader.NewRequest()); |
| if (status != ZX_OK) { |
| FXL_LOG(WARNING) << "Unable to retrieve an URLLoader."; |
| return false; |
| } |
| |
| http::URLRequest request; |
| request.method = "GET"; |
| request.url = kCredentialsFetchUrl.ToString(); |
| request.response_body_mode = http::ResponseBodyMode::BUFFER; |
| http::URLResponse response; |
| |
| status = url_loader->Start(std::move(request), &response); |
| if (status != ZX_OK) { |
| FXL_LOG(WARNING) << "Unable to start the network request."; |
| return false; |
| } |
| |
| if (response.error) { |
| FXL_LOG(ERROR) << "Net error " << response.error->code << ": " |
| << response.error->description; |
| return false; |
| } |
| |
| if (response.status_code != 200) { |
| FXL_LOG(ERROR) << "Unexpected HTTP status code: " << response.status_code; |
| return false; |
| } |
| |
| return fsl::StringFromVmo(response.body->buffer(), credentials); |
| } |
| |
| // Extracts the credentials content. This function will return |true| if it |
| // finds the credentials in either: |
| // - The command line |
| // - The default location in the running package |
| // - Over the network. |
| // In that case, |credentials| will contain the content of the credentials file |
| // and |credentials_path| the path to the file. |
| // If it cannot find the credentials, this function will return |false|, and |
| // |credentials_path| will contain the path of the last tried location. |
| bool GetCredentialsContent(const fxl::CommandLine& command_line, |
| sys::ComponentContext* component_context, |
| std::string* credentials_path, |
| std::string* credentials) { |
| if (command_line.GetOptionValue(kCredentialsPathFlag.ToString(), |
| credentials_path)) { |
| return files::ReadFileToString(*credentials_path, credentials); |
| } |
| *credentials_path = kCredentialsDefaultPath.ToString(); |
| if (files::IsFile(*credentials_path)) { |
| return files::ReadFileToString(*credentials_path, credentials); |
| } |
| |
| return FetchCredentials(component_context, credentials_path, credentials); |
| } |
| |
| std::string Hash(fxl::StringView data) { |
| char result[SHA256_DIGEST_LENGTH]; |
| SHA256_CTX sha256; |
| SHA256_Init(&sha256); |
| SHA256_Update(&sha256, data.data(), data.size()); |
| SHA256_Final(reinterpret_cast<uint8_t*>(result), &sha256); |
| return convert::ToHex(fxl::StringView(result, SHA256_DIGEST_LENGTH)); |
| } |
| |
| } // namespace |
| |
| namespace ledger { |
| |
| SyncParams::SyncParams() = default; |
| |
| SyncParams::SyncParams(SyncParams&& other) = default; |
| |
| SyncParams::SyncParams(const SyncParams& other) { *this = other; } |
| |
| SyncParams& SyncParams::operator=(SyncParams&& other) = default; |
| |
| SyncParams& SyncParams::operator=(const SyncParams& other) { |
| api_key = other.api_key; |
| if (other.credentials) { |
| credentials = other.credentials->Clone(); |
| } |
| return *this; |
| } |
| |
| std::string GetSyncParamsUsage() { |
| std::ostringstream result; |
| result << " [--" << kCredentialsPathFlag << "=<file path>]"; |
| return result.str(); |
| } |
| |
| std::string ExtractJsonObject(const std::string& content) { |
| auto start = content.find('{'); |
| auto end = content.rfind('}'); |
| if (start != std::string::npos && end != std::string::npos && start < end) { |
| return content.substr(start, end + 1 - start); |
| } |
| return ""; |
| } |
| |
| bool ParseSyncParamsFromCommandLine(const fxl::CommandLine& command_line, |
| sys::ComponentContext* component_context, |
| SyncParams* sync_params) { |
| std::string credentials; |
| std::string credentials_path; |
| if (!GetCredentialsContent(command_line, component_context, &credentials_path, |
| &credentials)) { |
| std::cerr << "Cannot access " << credentials_path << std::endl; |
| WarnIncorrectSyncParams(); |
| return false; |
| } |
| |
| FXL_LOG(INFO) << "Sync credentials sha256: " << Hash(credentials); |
| |
| rapidjson::Document document; |
| document.Parse(credentials); |
| if (document.HasParseError()) { |
| std::cerr << "Cannot parse sync parameters at " << credentials_path |
| << std::endl; |
| // TODO(qsr): NET-1636 Remove this code once the network service handles |
| // chunked encoding. Extract the content of credentials from the first '{' |
| // to the last '}' to work around the network service not handling chunked |
| // encoding. |
| std::cerr << "Trying to extract a JSON object." << std::endl; |
| credentials = ExtractJsonObject(credentials); |
| if (credentials.empty()) { |
| return false; |
| } |
| document.Parse(credentials); |
| if (document.HasParseError()) { |
| return false; |
| } |
| } |
| auto sync_params_schema = rapidjson_utils::InitSchema(kSyncParamsSchema); |
| FXL_DCHECK(sync_params_schema); |
| if (!rapidjson_utils::ValidateSchema(document, *sync_params_schema)) { |
| std::cerr << "Invalid schema at " << credentials_path << std::endl; |
| return false; |
| } |
| |
| sync_params->api_key = document["api-key"].GetString(); |
| sync_params->credentials = |
| service_account::Credentials::Parse(document["service-account"]); |
| if (!sync_params->credentials) { |
| std::cerr << "Cannot parse credentials at " << credentials_path |
| << std::endl; |
| return false; |
| } |
| return true; |
| } |
| |
| std::set<std::string> GetSyncParamFlags() { |
| return {kCredentialsPathFlag.ToString()}; |
| } |
| |
| } // namespace ledger |