#include "rpc/common/Validators.hpp" #include "rpc/Errors.hpp" #include "rpc/RPCHelpers.hpp" #include "rpc/common/Types.hpp" #include "util/AccountUtils.hpp" #include "util/LedgerUtils.hpp" #include "util/TimeUtils.hpp" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace rpc::validation { [[nodiscard]] MaybeError Required::verify(boost::json::value const& value, std::string_view key) { if (not value.is_object() or not value.as_object().contains(key)) { return Error{Status{ RippledError::RpcInvalidParams, "Required field '" + std::string{key} + "' missing" }}; } return {}; } [[nodiscard]] MaybeError TimeFormatValidator::verify(boost::json::value const& value, std::string_view key) const { using boost::json::value_to; if (not value.is_object() or not value.as_object().contains(key)) return {}; // ignore. field does not exist, let 'required' fail instead if (not value.as_object().at(key).is_string()) return Error{Status{RippledError::RpcInvalidParams}}; auto const ret = util::systemTpFromUtcStr(value_to(value.as_object().at(key)), format_); if (!ret) return Error{Status{RippledError::RpcInvalidParams}}; return {}; } [[nodiscard]] MaybeError CustomValidator::verify(boost::json::value const& value, std::string_view key) const { if (not value.is_object() or not value.as_object().contains(key)) return {}; // ignore. field does not exist, let 'required' fail instead return validator_(value.as_object().at(key), key); } [[nodiscard]] bool checkIsU32Numeric(std::string_view sv) { uint32_t unused = 0; auto [_, ec] = std::from_chars(sv.data(), sv.data() + sv.size(), unused); return ec == std::errc(); } CustomValidator CustomValidators::uint160HexStringValidator = CustomValidator{[](boost::json::value const& value, std::string_view key) -> MaybeError { return makeHexStringValidator(value, key); }}; CustomValidator CustomValidators::uint192HexStringValidator = CustomValidator{[](boost::json::value const& value, std::string_view key) -> MaybeError { return makeHexStringValidator(value, key); }}; CustomValidator CustomValidators::uint256HexStringValidator = CustomValidator{[](boost::json::value const& value, std::string_view key) -> MaybeError { return makeHexStringValidator(value, key); }}; CustomValidator CustomValidators::ledgerIndexValidator = CustomValidator{[](boost::json::value const& value, std::string_view /* key */) -> MaybeError { auto err = Error{Status{RippledError::RpcInvalidParams, "ledgerIndexMalformed"}}; if (!value.is_string() && !(value.is_uint64() || value.is_int64())) return err; if (value.is_string() && value.as_string() != "validated" && !checkIsU32Numeric(boost::json::value_to(value))) return err; return MaybeError{}; }}; CustomValidator CustomValidators::ledgerTypeValidator = CustomValidator{[](boost::json::value const& value, std::string_view key) -> MaybeError { if (!value.is_string()) { return Error{Status{ RippledError::RpcInvalidParams, fmt::format("Invalid field '{}', not string.", key) }}; } auto const type = util::LedgerTypes::getLedgerEntryTypeFromStr(boost::json::value_to(value)); if (type == xrpl::ltANY) { return Error{ Status{RippledError::RpcInvalidParams, fmt::format("Invalid field '{}'.", key)} }; } return MaybeError{}; }}; CustomValidator CustomValidators::accountValidator = CustomValidator{[](boost::json::value const& value, std::string_view key) -> MaybeError { if (!value.is_string()) return Error{Status{RippledError::RpcInvalidParams, std::string(key) + "NotString"}}; // TODO: we are using accountFromStringStrict from RPCHelpers, after we // remove all old handler, this function can be moved to here if (!accountFromStringStrict(boost::json::value_to(value))) return Error{Status{RippledError::RpcActMalformed, std::string(key) + "Malformed"}}; return MaybeError{}; }}; CustomValidator CustomValidators::accountBase58Validator = CustomValidator{[](boost::json::value const& value, std::string_view key) -> MaybeError { if (!value.is_string()) return Error{Status{RippledError::RpcInvalidParams, std::string(key) + "NotString"}}; auto const account = util::parseBase58Wrapper(boost::json::value_to(value)); if (!account || account->isZero()) return Error{Status{ClioError::RpcMalformedAddress}}; return MaybeError{}; }}; CustomValidator CustomValidators::accountMarkerValidator = CustomValidator{[](boost::json::value const& value, std::string_view key) -> MaybeError { if (!value.is_string()) return Error{Status{RippledError::RpcInvalidParams, std::string(key) + "NotString"}}; // TODO: we are using parseAccountCursor from RPCHelpers, after we // remove all old handler, this function can be moved to here if (!parseAccountCursor(boost::json::value_to(value))) { // align with the current error message return Error{Status{RippledError::RpcInvalidParams, "Malformed cursor."}}; } return MaybeError{}; }}; CustomValidator CustomValidators::accountTypeValidator = CustomValidator{[](boost::json::value const& value, std::string_view key) -> MaybeError { if (!value.is_string()) { return Error{Status{ RippledError::RpcInvalidParams, fmt::format("Invalid field '{}', not string.", key) }}; } auto const type = util::LedgerTypes::getAccountOwnedLedgerTypeFromStr( boost::json::value_to(value) ); if (type == xrpl::ltANY) { return Error{ Status{RippledError::RpcInvalidParams, fmt::format("Invalid field '{}'.", key)} }; } return MaybeError{}; }}; CustomValidator CustomValidators::currencyValidator = CustomValidator{[](boost::json::value const& value, std::string_view key) -> MaybeError { if (!value.is_string()) return Error{Status{RippledError::RpcInvalidParams, std::string(key) + "NotString"}}; auto const currencyStr = boost::json::value_to(value); if (currencyStr.empty()) return Error{Status{RippledError::RpcInvalidParams, std::string(key) + "IsEmpty"}}; xrpl::Currency currency; if (!xrpl::toCurrency(currency, currencyStr)) return Error{Status{ClioError::RpcMalformedCurrency, "malformedCurrency"}}; return MaybeError{}; }}; CustomValidator CustomValidators::issuerValidator = CustomValidator{[](boost::json::value const& value, std::string_view key) -> MaybeError { if (!value.is_string()) return Error{Status{RippledError::RpcInvalidParams, std::string(key) + "NotString"}}; xrpl::AccountID issuer; // TODO: need to align with the error if (!xrpl::toIssuer(issuer, boost::json::value_to(value))) { return Error{Status{ RippledError::RpcInvalidParams, fmt::format("Invalid field '{}', bad issuer.", key) }}; } if (issuer == xrpl::noAccount()) { return Error{Status{ RippledError::RpcInvalidParams, fmt::format("Invalid field '{}', bad issuer account one.", key) }}; } return MaybeError{}; }}; CustomValidator CustomValidators::subscribeStreamValidator = CustomValidator{[](boost::json::value const& value, std::string_view key) -> MaybeError { if (!value.is_array()) return Error{Status{RippledError::RpcInvalidParams, std::string(key) + "NotArray"}}; static std::unordered_set const kValidStreams = { "ledger", "transactions", "transactions_proposed", "book_changes", "manifests", "validations" }; static std::unordered_set const kNotSupportStreams = { "peer_status", "consensus", "server" }; for (auto const& v : value.as_array()) { if (!v.is_string()) return Error{Status{RippledError::RpcInvalidParams, "streamNotString"}}; if (kNotSupportStreams.contains(boost::json::value_to(v))) return Error{Status{RippledError::RpcNotSupported}}; if (not kValidStreams.contains(boost::json::value_to(v))) return Error{Status{RippledError::RpcStreamMalformed}}; } return MaybeError{}; }}; CustomValidator CustomValidators::subscribeAccountsValidator = CustomValidator{[](boost::json::value const& value, std::string_view key) -> MaybeError { if (!value.is_array()) return Error{Status{RippledError::RpcInvalidParams, std::string(key) + "NotArray"}}; if (value.as_array().empty()) return Error{Status{RippledError::RpcActMalformed, std::string(key) + " malformed."}}; for (auto const& v : value.as_array()) { auto obj = boost::json::object(); auto const keyItem = std::string(key) + "'sItem"; obj[keyItem] = v; if (auto err = accountValidator.verify(obj, keyItem); !err) return err; } return MaybeError{}; }}; CustomValidator CustomValidators::currencyIssueValidator = CustomValidator{[](boost::json::value const& value, std::string_view key) -> MaybeError { if (not value.is_object()) return Error{Status{RippledError::RpcInvalidParams, std::string(key) + "NotObject"}}; try { parseIssue(value.as_object()); } catch (std::runtime_error const&) { return Error{Status{ClioError::RpcMalformedRequest}}; } return MaybeError{}; }}; CustomValidator CustomValidators::credentialTypeValidator = CustomValidator{[](boost::json::value const& value, std::string_view key) -> MaybeError { if (not value.is_string()) { return Error{Status{ ClioError::RpcMalformedAuthorizedCredentials, std::string(key) + " NotString" }}; } auto const& credTypeHex = xrpl::strViewUnHex(value.as_string()); if (!credTypeHex.has_value()) { return Error{Status{ ClioError::RpcMalformedAuthorizedCredentials, std::string(key) + " NotHexString" }}; } if (credTypeHex->empty()) { return Error{ Status{ClioError::RpcMalformedAuthorizedCredentials, std::string(key) + " is empty"} }; } if (credTypeHex->size() > xrpl::kMaxCredentialTypeLength) { return Error{Status{ ClioError::RpcMalformedAuthorizedCredentials, std::string(key) + " greater than max length" }}; } return MaybeError{}; }}; CustomValidator CustomValidators::authorizeCredentialValidator = CustomValidator{[](boost::json::value const& value, std::string_view key) -> MaybeError { if (not value.is_array()) return Error{Status{ClioError::RpcMalformedRequest, std::string(key) + " not array"}}; auto const& authCred = value.as_array(); if (authCred.empty()) { return Error{Status{ ClioError::RpcMalformedAuthorizedCredentials, fmt::format("Requires at least one element in authorized_credentials array.") }}; } if (authCred.size() > xrpl::kMaxCredentialsArraySize) { return Error{Status{ ClioError::RpcMalformedAuthorizedCredentials, fmt::format( "Max {} number of credentials in authorized_credentials array", xrpl::kMaxCredentialsArraySize ) }}; } for (auto const& credObj : value.as_array()) { if (!credObj.is_object()) { return Error{Status{ ClioError::RpcMalformedAuthorizedCredentials, "authorized_credentials elements in array are not objects." }}; } auto const& obj = credObj.as_object(); if (!obj.contains("issuer")) { return Error{Status{ ClioError::RpcMalformedAuthorizedCredentials, "Field 'Issuer' is required but missing." }}; } // don't want to change issuer error message to be about credentials if (!issuerValidator.verify(credObj, "issuer")) { return Error{ Status{ClioError::RpcMalformedAuthorizedCredentials, "issuer NotString"} }; } if (!obj.contains("credential_type")) { return Error{Status{ ClioError::RpcMalformedAuthorizedCredentials, "Field 'CredentialType' is required but missing." }}; } if (auto const err = credentialTypeValidator.verify(credObj, "credential_type"); !err) return err; } return MaybeError{}; }}; } // namespace rpc::validation