LCOV - code coverage report
Current view: top level - shared_model/validators - field_validator.cpp (source / functions) Hit Total Coverage
Test: cleared_cor.info Lines: 194 203 95.6 %
Date: 2019-03-07 14:46:43 Functions: 51 51 100.0 %

          Line data    Source code
       1             : /**
       2             :  * Copyright Soramitsu Co., Ltd. All Rights Reserved.
       3             :  * SPDX-License-Identifier: Apache-2.0
       4             :  */
       5             : 
       6             : #include "validators/field_validator.hpp"
       7             : 
       8             : #include <limits>
       9             : 
      10             : #include <boost/algorithm/string_regex.hpp>
      11             : #include <boost/format.hpp>
      12             : #include "cryptography/crypto_provider/crypto_defaults.hpp"
      13             : #include "cryptography/crypto_provider/crypto_verifier.hpp"
      14             : #include "interfaces/common_objects/amount.hpp"
      15             : #include "interfaces/common_objects/peer.hpp"
      16             : #include "interfaces/queries/query_payload_meta.hpp"
      17             : #include "interfaces/queries/tx_pagination_meta.hpp"
      18             : #include "validators/field_validator.hpp"
      19             : 
      20             : // TODO: 15.02.18 nickaleks Change structure to compositional IR-978
      21             : 
      22             : namespace shared_model {
      23             :   namespace validation {
      24             : 
      25             :     const std::string FieldValidator::account_name_pattern_ =
      26          66 :         R"#([a-z_0-9]{1,32})#";
      27             :     const std::string FieldValidator::asset_name_pattern_ =
      28          66 :         R"#([a-z_0-9]{1,32})#";
      29             :     const std::string FieldValidator::domain_pattern_ =
      30          66 :         R"#(([a-zA-Z]([a-zA-Z0-9\-]{0,61}[a-zA-Z0-9])?\.)*[a-zA-Z]([a-zA-Z0-9\-]{0,61}[a-zA-Z0-9])?)#";
      31             :     const std::string FieldValidator::ip_v4_pattern_ =
      32          66 :         R"#(^((([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3})#"
      33             :         R"#(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])))#";
      34             :     const std::string FieldValidator::peer_address_pattern_ = "(("
      35          66 :         + ip_v4_pattern_ + ")|(" + domain_pattern_ + ")):"
      36          66 :         + R"#((6553[0-5]|655[0-2]\d|65[0-4]\d\d|6[0-4]\d{3}|[1-5]\d{4}|[1-9]\d{0,3}|0)$)#";
      37             :     const std::string FieldValidator::account_id_pattern_ =
      38          66 :         account_name_pattern_ + R"#(\@)#" + domain_pattern_;
      39             :     const std::string FieldValidator::asset_id_pattern_ =
      40          66 :         asset_name_pattern_ + R"#(\#)#" + domain_pattern_;
      41             :     const std::string FieldValidator::detail_key_pattern_ =
      42          66 :         R"([A-Za-z0-9_]{1,64})";
      43             :     const std::string FieldValidator::role_id_pattern_ = R"#([a-z_0-9]{1,32})#";
      44             : 
      45             :     const size_t FieldValidator::public_key_size =
      46          66 :         crypto::DefaultCryptoAlgorithmType::kPublicKeyLength;
      47             :     const size_t FieldValidator::signature_size =
      48          66 :         crypto::DefaultCryptoAlgorithmType::kSignatureLength;
      49             :     const size_t FieldValidator::hash_size =
      50          66 :         crypto::DefaultCryptoAlgorithmType::kHashLength;
      51             :     /// limit for the set account detail size in bytes
      52             :     const size_t FieldValidator::value_size = 4 * 1024 * 1024;
      53             :     const size_t FieldValidator::description_size = 64;
      54             : 
      55             :     const std::regex FieldValidator::account_name_regex_(account_name_pattern_);
      56             :     const std::regex FieldValidator::asset_name_regex_(asset_name_pattern_);
      57             :     const std::regex FieldValidator::domain_regex_(domain_pattern_);
      58             :     const std::regex FieldValidator::ip_v4_regex_(ip_v4_pattern_);
      59             :     const std::regex FieldValidator::peer_address_regex_(peer_address_pattern_);
      60             :     const std::regex FieldValidator::account_id_regex_(account_id_pattern_);
      61             :     const std::regex FieldValidator::asset_id_regex_(asset_id_pattern_);
      62             :     const std::regex FieldValidator::detail_key_regex_(detail_key_pattern_);
      63             :     const std::regex FieldValidator::role_id_regex_(role_id_pattern_);
      64             : 
      65             :     FieldValidator::FieldValidator(time_t future_gap,
      66             :                                    TimeFunction time_provider)
      67       29746 :         : future_gap_(future_gap), time_provider_(time_provider) {}
      68             : 
      69             :     void FieldValidator::validateAccountId(
      70             :         ReasonsGroupType &reason,
      71             :         const interface::types::AccountIdType &account_id) const {
      72       12649 :       if (not std::regex_match(account_id, account_id_regex_)) {
      73             :         auto message =
      74          59 :             (boost::format("Wrongly formed account_id, passed value: '%s'. "
      75             :                            "Field should match regex '%s'")
      76          59 :              % account_id % account_id_pattern_)
      77          59 :                 .str();
      78          59 :         reason.second.push_back(std::move(message));
      79          59 :       }
      80       12557 :     }
      81             : 
      82             :     void FieldValidator::validateAssetId(
      83             :         ReasonsGroupType &reason,
      84             :         const interface::types::AssetIdType &asset_id) const {
      85        2431 :       if (not std::regex_match(asset_id, asset_id_regex_)) {
      86          19 :         auto message = (boost::format("Wrongly formed asset_id, passed value: "
      87             :                                       "'%s'. Field should match regex '%s'")
      88          19 :                         % asset_id % asset_id_pattern_)
      89          19 :                            .str();
      90          19 :         reason.second.push_back(std::move(message));
      91          19 :       }
      92        2399 :     }
      93             : 
      94             :     void FieldValidator::validatePeer(ReasonsGroupType &reason,
      95             :                                       const interface::Peer &peer) const {
      96       12513 :       validatePeerAddress(reason, peer.address());
      97       12513 :       validatePubkey(reason, peer.pubkey());
      98       12513 :     }
      99             : 
     100             :     void FieldValidator::validateAmount(ReasonsGroupType &reason,
     101             :                                         const interface::Amount &amount) const {
     102        2336 :       if (amount.intValue() <= 0) {
     103             :         auto message =
     104          10 :             (boost::format("Amount must be greater than 0, passed value: %d")
     105          10 :              % amount.intValue())
     106          10 :                 .str();
     107          10 :         reason.second.push_back(message);
     108          10 :       }
     109        2321 :     }
     110             : 
     111             :     void FieldValidator::validatePubkey(
     112             :         ReasonsGroupType &reason,
     113             :         const interface::types::PubkeyType &pubkey) const {
     114       32795 :       auto opt_reason = shared_model::validation::validatePubkey(pubkey);
     115       32795 :       if (opt_reason) {
     116          31 :         reason.second.push_back(std::move(*opt_reason));
     117          31 :       }
     118       32758 :     }
     119             : 
     120             :     void FieldValidator::validatePeerAddress(
     121             :         ReasonsGroupType &reason,
     122             :         const interface::types::AddressType &address) const {
     123       12513 :       if (not std::regex_match(address, peer_address_regex_)) {
     124             :         auto message =
     125          18 :             (boost::format("Wrongly formed peer address, passed value: '%s'. "
     126             :                            "Field should have a valid 'host:port' format where "
     127             :                            "host is IPv4 or a "
     128             :                            "hostname following RFC1035, RFC1123 specifications")
     129          18 :              % address)
     130          18 :                 .str();
     131          18 :         reason.second.push_back(std::move(message));
     132          18 :       }
     133       12513 :     }
     134             : 
     135             :     void FieldValidator::validateRoleId(
     136             :         ReasonsGroupType &reason,
     137             :         const interface::types::RoleIdType &role_id) const {
     138       13533 :       if (not std::regex_match(role_id, role_id_regex_)) {
     139          16 :         auto message = (boost::format("Wrongly formed role_id, passed value: "
     140             :                                       "'%s'. Field should match regex '%s'")
     141          16 :                         % role_id % role_id_pattern_)
     142          16 :                            .str();
     143          16 :         reason.second.push_back(std::move(message));
     144          16 :       }
     145       13474 :     }
     146             : 
     147             :     void FieldValidator::validateAccountName(
     148             :         ReasonsGroupType &reason,
     149             :         const interface::types::AccountNameType &account_name) const {
     150        4654 :       if (not std::regex_match(account_name, account_name_regex_)) {
     151             :         auto message =
     152           6 :             (boost::format("Wrongly formed account_name, passed value: '%s'. "
     153             :                            "Field should match regex '%s'")
     154           6 :              % account_name % account_name_pattern_)
     155           6 :                 .str();
     156           6 :         reason.second.push_back(std::move(message));
     157           6 :       }
     158        4592 :     }
     159             : 
     160             :     void FieldValidator::validateDomainId(
     161             :         ReasonsGroupType &reason,
     162             :         const interface::types::DomainIdType &domain_id) const {
     163        7203 :       if (not std::regex_match(domain_id, domain_regex_)) {
     164          30 :         auto message = (boost::format("Wrongly formed domain_id, passed value: "
     165             :                                       "'%s'. Field should match regex '%s'")
     166          30 :                         % domain_id % domain_pattern_)
     167          30 :                            .str();
     168          30 :         reason.second.push_back(std::move(message));
     169          30 :       }
     170        7148 :     }
     171             : 
     172             :     void FieldValidator::validateAssetName(
     173             :         ReasonsGroupType &reason,
     174             :         const interface::types::AssetNameType &asset_name) const {
     175        1187 :       if (not std::regex_match(asset_name, asset_name_regex_)) {
     176             :         auto message =
     177          15 :             (boost::format("Wrongly formed asset_name, passed value: '%s'. "
     178             :                            "Field should match regex '%s'")
     179          15 :              % asset_name % asset_name_pattern_)
     180          15 :                 .str();
     181          15 :         reason.second.push_back(std::move(message));
     182          15 :       }
     183        1172 :     }
     184             : 
     185             :     void FieldValidator::validateAccountDetailKey(
     186             :         ReasonsGroupType &reason,
     187             :         const interface::types::AccountDetailKeyType &key) const {
     188         762 :       if (not std::regex_match(key, detail_key_regex_)) {
     189           7 :         auto message = (boost::format("Wrongly formed key, passed value: '%s'. "
     190             :                                       "Field should match regex '%s'")
     191           7 :                         % key % detail_key_pattern_)
     192           7 :                            .str();
     193           7 :         reason.second.push_back(std::move(message));
     194           7 :       }
     195         739 :     }
     196             : 
     197             :     void FieldValidator::validateAccountDetailValue(
     198             :         ReasonsGroupType &reason,
     199             :         const interface::types::AccountDetailValueType &value) const {
     200         751 :       if (value.size() > value_size) {
     201             :         auto message =
     202           1 :             (boost::format("Detail value size should be less or equal '%d'")
     203           1 :              % value_size)
     204           1 :                 .str();
     205           1 :         reason.second.push_back(std::move(message));
     206           1 :       }
     207         740 :     }
     208             : 
     209             :     void FieldValidator::validatePrecision(
     210             :         ReasonsGroupType &reason,
     211             :         const interface::types::PrecisionType &precision) const {
     212             :       /* The following validation is pointless since PrecisionType is already
     213             :        * uint8_t, but it is going to be changed and the validation will become
     214             :        * meaningful.
     215             :        */
     216        1172 :       interface::types::PrecisionType min = std::numeric_limits<uint8_t>::min();
     217        1172 :       interface::types::PrecisionType max = std::numeric_limits<uint8_t>::max();
     218        1177 :       if (precision < min or precision > max) {
     219             :         auto message =
     220           0 :             (boost::format(
     221             :                  "Precision value (%d) is out of allowed range [%d; %d]")
     222           0 :              % precision % min % max)
     223           0 :                 .str();
     224           0 :         reason.second.push_back(std::move(message));
     225           0 :       }
     226        1172 :     }
     227             : 
     228             :     void FieldValidator::validateRolePermission(
     229             :         ReasonsGroupType &reason,
     230             :         const interface::permissions::Role &permission) const {
     231       28721 :       if (not isValid(permission)) {
     232           0 :         reason.second.emplace_back("Provided role permission does not exist");
     233           0 :       }
     234       28710 :     }
     235             : 
     236             :     void FieldValidator::validateGrantablePermission(
     237             :         ReasonsGroupType &reason,
     238             :         const interface::permissions::Grantable &permission) const {
     239         515 :       if (not isValid(permission)) {
     240           1 :         reason.second.emplace_back(
     241             :             "Provided grantable permission does not exist");
     242           1 :       }
     243         515 :     }
     244             : 
     245             :     void FieldValidator::validateQuorum(
     246             :         ReasonsGroupType &reason,
     247             :         const interface::types::QuorumType &quorum) const {
     248        9598 :       if (quorum == 0 or quorum > 128) {
     249           5 :         reason.second.emplace_back("Quorum should be within range (0, 128]");
     250           5 :       }
     251        9597 :     }
     252             : 
     253             :     void FieldValidator::validateCreatorAccountId(
     254             :         ReasonsGroupType &reason,
     255             :         const interface::types::AccountIdType &account_id) const {
     256        9712 :       if (not std::regex_match(account_id, account_id_regex_)) {
     257             :         auto message =
     258          23 :             (boost::format("Wrongly formed creator_account_id, passed value: "
     259             :                            "'%s'. Field should match regex '%s'")
     260          23 :              % account_id % account_id_pattern_)
     261          23 :                 .str();
     262          23 :         reason.second.push_back(std::move(message));
     263          23 :       }
     264        9611 :     }
     265             : 
     266             :     void FieldValidator::validateCreatedTime(
     267             :         ReasonsGroupType &reason,
     268             :         interface::types::TimestampType timestamp,
     269             :         interface::types::TimestampType now) const {
     270        9745 :       if (now + future_gap_ < timestamp) {
     271           7 :         auto message = (boost::format("bad timestamp: sent from future, "
     272             :                                       "timestamp: %llu, now: %llu")
     273           7 :                         % timestamp % now)
     274           7 :                            .str();
     275           7 :         reason.second.push_back(std::move(message));
     276           7 :       }
     277             : 
     278        9601 :       if (now > kMaxDelay + timestamp) {
     279             :         auto message =
     280          17 :             (boost::format("bad timestamp: too old, timestamp: %llu, now: %llu")
     281          17 :              % timestamp % now)
     282          17 :                 .str();
     283          17 :         reason.second.push_back(std::move(message));
     284          17 :       }
     285        9559 :     }
     286             : 
     287             :     void FieldValidator::validateCreatedTime(
     288             :         ReasonsGroupType &reason,
     289             :         interface::types::TimestampType timestamp) const {
     290        8032 :       validateCreatedTime(reason, timestamp, time_provider_());
     291        8032 :     }
     292             : 
     293             :     void FieldValidator::validateCounter(
     294             :         ReasonsGroupType &reason,
     295             :         const interface::types::CounterType &counter) const {
     296         215 :       if (counter <= 0) {
     297             :         auto message =
     298          15 :             (boost::format("Counter should be > 0, passed value: %d") % counter)
     299          15 :                 .str();
     300          15 :         reason.second.push_back(message);
     301          15 :       }
     302         215 :     }
     303             : 
     304             :     void FieldValidator::validateSignatures(
     305             :         ReasonsGroupType &reason,
     306             :         const interface::types::SignatureRangeType &signatures,
     307             :         const crypto::Blob &source) const {
     308        9042 :       if (boost::empty(signatures)) {
     309           4 :         reason.second.emplace_back("Signatures cannot be empty");
     310           4 :       }
     311       17974 :       for (const auto &signature : signatures) {
     312        8854 :         const auto &sign = signature.signedData();
     313        8856 :         const auto &pkey = signature.publicKey();
     314        8856 :         bool is_valid = true;
     315             : 
     316        8888 :         if (sign.blob().size() != signature_size) {
     317           4 :           reason.second.push_back(
     318           4 :               (boost::format("Invalid signature: %s") % sign.hex()).str());
     319           4 :           is_valid = false;
     320           4 :         }
     321             : 
     322        8929 :         if (pkey.blob().size() != public_key_size) {
     323           5 :           reason.second.push_back(
     324           5 :               (boost::format("Invalid pubkey: %s") % pkey.hex()).str());
     325           5 :           is_valid = false;
     326           5 :         }
     327             : 
     328        9074 :         if (is_valid
     329        8866 :             && not shared_model::crypto::CryptoVerifier<>::verify(
     330        5012 :                    sign, source, pkey)) {
     331           5 :           reason.second.push_back((boost::format("Wrong signature [%s;%s]")
     332           5 :                                    % sign.hex() % pkey.hex())
     333           5 :                                       .str());
     334           5 :         }
     335             :       }
     336        9061 :     }
     337             : 
     338             :     void FieldValidator::validateQueryPayloadMeta(
     339             :         ReasonsGroupType &reason,
     340           1 :         const interface::QueryPayloadMeta &meta) const {}
     341             : 
     342             :     void FieldValidator::validateDescription(
     343             :         shared_model::validation::ReasonsGroupType &reason,
     344             :         const shared_model::interface::types::DescriptionType &description)
     345             :         const {
     346         909 :       if (description.size() > description_size) {
     347           2 :         reason.second.push_back(
     348           2 :             (boost::format("Description size should be less or equal '%d'")
     349           2 :              % description_size)
     350           2 :                 .str());
     351           2 :       }
     352         901 :     }
     353             :     void FieldValidator::validateBatchMeta(
     354             :         shared_model::validation::ReasonsGroupType &reason,
     355         145 :         const interface::BatchMeta &batch_meta) const {}
     356             : 
     357             :     void FieldValidator::validateHeight(
     358             :         shared_model::validation::ReasonsGroupType &reason,
     359             :         const shared_model::interface::types::HeightType &height) const {
     360        1597 :       if (height <= 0) {
     361             :         auto message =
     362           2 :             (boost::format("Height should be > 0, passed value: %d") % height)
     363           2 :                 .str();
     364           2 :         reason.second.push_back(message);
     365           2 :       }
     366        1597 :     }
     367             : 
     368             :     void FieldValidator::validateHash(ReasonsGroupType &reason,
     369             :                                       const crypto::Hash &hash) const {
     370         300 :       if (hash.size() != hash_size) {
     371           1 :         reason.second.push_back(
     372           1 :             (boost::format("Hash has invalid size: %d") % hash.size()).str());
     373           1 :       }
     374         300 :     }
     375             : 
     376             :     boost::optional<ConcreteReasonType> validatePubkey(
     377             :         const interface::types::PubkeyType &pubkey) {
     378       32783 :       if (pubkey.blob().size() != FieldValidator::public_key_size) {
     379          32 :         return (boost::format("Public key has wrong size, passed size: "
     380             :                               "%d. Expected size: %d")
     381          32 :                 % pubkey.blob().size() % FieldValidator::public_key_size)
     382          32 :             .str();
     383             :       }
     384       32743 :       return boost::none;
     385       32817 :     }
     386             : 
     387             :     void FieldValidator::validateTxPaginationMeta(
     388             :         ReasonsGroupType &reason,
     389             :         const interface::TxPaginationMeta &tx_pagination_meta) const {
     390          34 :       const auto page_size = tx_pagination_meta.pageSize();
     391          34 :       if (page_size <= 0) {
     392           2 :         reason.second.push_back(
     393           2 :             (boost::format(
     394             :                  "Page size is %s (%d), while it must be a non-zero positive.")
     395           2 :              % (page_size == 0 ? "zero" : "negative") % page_size)
     396           2 :                 .str());
     397           2 :       }
     398          34 :       const auto first_hash = tx_pagination_meta.firstTxHash();
     399          34 :       if (first_hash) {
     400           0 :         validateHash(reason, *first_hash);
     401           0 :       }
     402          34 :     }
     403             : 
     404             :   }  // namespace validation
     405             : }  // namespace shared_model

Generated by: LCOV version 1.13