| /* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */ |
| /** |
| * Copyright (c) 2014 Regents of the University of California, |
| * Arizona Board of Regents, |
| * Colorado State University, |
| * University Pierre & Marie Curie, Sorbonne University, |
| * Washington University in St. Louis, |
| * Beijing Institute of Technology |
| * |
| * This file is part of NFD (Named Data Networking Forwarding Daemon). |
| * See AUTHORS.md for complete list of NFD authors and contributors. |
| * |
| * NFD is free software: you can redistribute it and/or modify it under the terms |
| * of the GNU General Public License as published by the Free Software Foundation, |
| * either version 3 of the License, or (at your option) any later version. |
| * |
| * NFD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; |
| * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR |
| * PURPOSE. See the GNU General Public License for more details. |
| * |
| * You should have received a copy of the GNU General Public License along with |
| * NFD, e.g., in COPYING.md file. If not, see <http://www.gnu.org/licenses/>. |
| **/ |
| |
| #include "mgmt/command-validator.hpp" |
| #include "core/config-file.hpp" |
| |
| #include "tests/test-common.hpp" |
| |
| #include <ndn-cpp-dev/util/command-interest-generator.hpp> |
| #include <ndn-cpp-dev/util/io.hpp> |
| #include <boost/filesystem.hpp> |
| #include <fstream> |
| |
| namespace nfd { |
| |
| namespace tests { |
| |
| NFD_LOG_INIT("CommandValidatorTest"); |
| |
| BOOST_FIXTURE_TEST_SUITE(MgmtCommandValidator, BaseFixture) |
| |
| // authorizations |
| // { |
| // authorize |
| // { |
| // certfile "tests/daemon/mgmt/cert1.ndncert" |
| // privileges |
| // { |
| // fib |
| // stats |
| // } |
| // } |
| |
| // authorize |
| // { |
| // certfile "tests/daemon/mgmt/cert2.ndncert" |
| // privileges |
| // { |
| // faces |
| // } |
| // } |
| // } |
| |
| const std::string CONFIG = |
| "authorizations\n" |
| "{\n" |
| " authorize\n" |
| " {\n" |
| " certfile \"tests/daemon/mgmt/cert1.ndncert\"\n" |
| " privileges\n" |
| " {\n" |
| " fib\n" |
| " stats\n" |
| " }\n" |
| " }\n" |
| " authorize\n" |
| " {\n" |
| " certfile \"tests/daemon/mgmt/cert2.ndncert\"\n" |
| " privileges\n" |
| " {\n" |
| " faces\n" |
| " }\n" |
| " }\n" |
| "}\n"; |
| |
| const boost::filesystem::path CONFIG_PATH = |
| boost::filesystem::current_path() /= std::string("unit-test-nfd.conf"); |
| |
| class CommandValidatorTester |
| { |
| public: |
| |
| CommandValidatorTester() |
| : m_validated(false), |
| m_validationFailed(false) |
| { |
| |
| } |
| |
| void |
| generateIdentity(const Name& prefix) |
| { |
| m_identityName = prefix; |
| m_identityName.appendVersion(); |
| |
| const Name certName = m_keys.createIdentity(m_identityName); |
| |
| m_certificate = m_keys.getCertificate(certName); |
| } |
| |
| void |
| saveIdentityToFile(const char* filename) |
| { |
| std::ofstream out; |
| out.open(filename); |
| |
| BOOST_REQUIRE(out.is_open()); |
| BOOST_REQUIRE(static_cast<bool>(m_certificate)); |
| |
| ndn::io::save<ndn::IdentityCertificate>(*m_certificate, out); |
| |
| out.close(); |
| } |
| |
| const Name& |
| getIdentityName() const |
| { |
| BOOST_REQUIRE_NE(m_identityName, Name()); |
| return m_identityName; |
| } |
| |
| const Name& |
| getPublicKeyName() const |
| { |
| BOOST_REQUIRE(static_cast<bool>(m_certificate)); |
| return m_certificate->getPublicKeyName(); |
| } |
| |
| void |
| onValidated(const shared_ptr<const Interest>& interest) |
| { |
| // NFD_LOG_DEBUG("validated command"); |
| m_validated = true; |
| } |
| |
| void |
| onValidationFailed(const shared_ptr<const Interest>& interest, const std::string& info) |
| { |
| NFD_LOG_DEBUG("validation failed: " << info); |
| m_validationFailed = true; |
| } |
| |
| bool |
| commandValidated() const |
| { |
| return m_validated; |
| } |
| |
| bool |
| commandValidationFailed() const |
| { |
| return m_validationFailed; |
| } |
| |
| void |
| resetValidation() |
| { |
| m_validated = false; |
| m_validationFailed = false; |
| } |
| |
| ~CommandValidatorTester() |
| { |
| m_keys.deleteIdentity(m_identityName); |
| } |
| |
| private: |
| bool m_validated; |
| bool m_validationFailed; |
| |
| ndn::KeyChain m_keys; |
| Name m_identityName; |
| shared_ptr<ndn::IdentityCertificate> m_certificate; |
| }; |
| |
| class TwoValidatorFixture : public BaseFixture |
| { |
| public: |
| TwoValidatorFixture() |
| { |
| m_tester1.generateIdentity("/test/CommandValidator/TwoKeys/id1"); |
| m_tester1.saveIdentityToFile("tests/daemon/mgmt/cert1.ndncert"); |
| |
| m_tester2.generateIdentity("/test/CommandValidator/TwoKeys/id2"); |
| m_tester2.saveIdentityToFile("tests/daemon/mgmt/cert2.ndncert"); |
| } |
| |
| ~TwoValidatorFixture() |
| { |
| boost::system::error_code error; |
| boost::filesystem::remove("tests/daemon/mgmt/cert1.ndncert", error); |
| boost::filesystem::remove("tests/daemon/mgmt/cert2.ndncert", error); |
| } |
| |
| protected: |
| CommandValidatorTester m_tester1; |
| CommandValidatorTester m_tester2; |
| }; |
| |
| BOOST_FIXTURE_TEST_CASE(TwoKeys, TwoValidatorFixture) |
| { |
| shared_ptr<Interest> fibCommand = make_shared<Interest>("/localhost/nfd/fib/insert"); |
| shared_ptr<Interest> statsCommand = make_shared<Interest>("/localhost/nfd/stats/dosomething"); |
| shared_ptr<Interest> facesCommand = make_shared<Interest>("/localhost/nfd/faces/create"); |
| |
| ndn::CommandInterestGenerator generator; |
| generator.generateWithIdentity(*fibCommand, m_tester1.getIdentityName()); |
| generator.generateWithIdentity(*statsCommand, m_tester1.getIdentityName()); |
| generator.generateWithIdentity(*facesCommand, m_tester2.getIdentityName()); |
| |
| ConfigFile config; |
| CommandValidator validator; |
| validator.addSupportedPrivilege("faces"); |
| validator.addSupportedPrivilege("fib"); |
| validator.addSupportedPrivilege("stats"); |
| |
| validator.setConfigFile(config); |
| |
| config.parse(CONFIG, false, CONFIG_PATH.native()); |
| |
| validator.validate(*fibCommand, |
| bind(&CommandValidatorTester::onValidated, boost::ref(m_tester1), _1), |
| bind(&CommandValidatorTester::onValidationFailed, boost::ref(m_tester1), _1, _2)); |
| |
| BOOST_REQUIRE(m_tester1.commandValidated()); |
| m_tester1.resetValidation(); |
| |
| validator.validate(*statsCommand, |
| bind(&CommandValidatorTester::onValidated, boost::ref(m_tester1), _1), |
| bind(&CommandValidatorTester::onValidationFailed, boost::ref(m_tester1), _1, _2)); |
| |
| BOOST_REQUIRE(m_tester1.commandValidated()); |
| |
| validator.validate(*facesCommand, |
| bind(&CommandValidatorTester::onValidated, boost::ref(m_tester2), _1), |
| bind(&CommandValidatorTester::onValidationFailed, boost::ref(m_tester2), _1, _2)); |
| |
| BOOST_REQUIRE(m_tester2.commandValidated()); |
| m_tester2.resetValidation(); |
| |
| // use cert2 for fib command (authorized for cert1 only) |
| shared_ptr<Interest> unauthorizedFibCommand = make_shared<Interest>("/localhost/nfd/fib/insert"); |
| generator.generateWithIdentity(*unauthorizedFibCommand, m_tester2.getIdentityName()); |
| |
| validator.validate(*unauthorizedFibCommand, |
| bind(&CommandValidatorTester::onValidated, boost::ref(m_tester2), _1), |
| bind(&CommandValidatorTester::onValidationFailed, boost::ref(m_tester2), _1, _2)); |
| |
| BOOST_REQUIRE(m_tester2.commandValidationFailed()); |
| } |
| |
| BOOST_FIXTURE_TEST_CASE(TwoKeysDryRun, TwoValidatorFixture) |
| { |
| CommandValidatorTester tester1; |
| tester1.generateIdentity("/test/CommandValidator/TwoKeys/id1"); |
| tester1.saveIdentityToFile("tests/daemon/mgmt/cert1.ndncert"); |
| |
| CommandValidatorTester tester2; |
| tester2.generateIdentity("/test/CommandValidator/TwoKeys/id2"); |
| tester2.saveIdentityToFile("tests/daemon/mgmt/cert2.ndncert"); |
| |
| shared_ptr<Interest> fibCommand = make_shared<Interest>("/localhost/nfd/fib/insert"); |
| shared_ptr<Interest> statsCommand = make_shared<Interest>("/localhost/nfd/stats/dosomething"); |
| shared_ptr<Interest> facesCommand = make_shared<Interest>("/localhost/nfd/faces/create"); |
| |
| ndn::CommandInterestGenerator generator; |
| generator.generateWithIdentity(*fibCommand, m_tester1.getIdentityName()); |
| generator.generateWithIdentity(*statsCommand, m_tester1.getIdentityName()); |
| generator.generateWithIdentity(*facesCommand, m_tester2.getIdentityName()); |
| |
| ConfigFile config; |
| CommandValidator validator; |
| validator.addSupportedPrivilege("faces"); |
| validator.addSupportedPrivilege("fib"); |
| validator.addSupportedPrivilege("stats"); |
| |
| validator.setConfigFile(config); |
| |
| config.parse(CONFIG, true, CONFIG_PATH.native()); |
| |
| validator.validate(*fibCommand, |
| bind(&CommandValidatorTester::onValidated, boost::ref(m_tester1), _1), |
| bind(&CommandValidatorTester::onValidationFailed, boost::ref(m_tester1), _1, _2)); |
| |
| BOOST_REQUIRE(m_tester1.commandValidationFailed()); |
| m_tester1.resetValidation(); |
| |
| validator.validate(*statsCommand, |
| bind(&CommandValidatorTester::onValidated, boost::ref(m_tester1), _1), |
| bind(&CommandValidatorTester::onValidationFailed, boost::ref(m_tester1), _1, _2)); |
| |
| BOOST_REQUIRE(m_tester1.commandValidationFailed()); |
| |
| validator.validate(*facesCommand, |
| bind(&CommandValidatorTester::onValidated, boost::ref(m_tester2), _1), |
| bind(&CommandValidatorTester::onValidationFailed, boost::ref(m_tester2), _1, _2)); |
| |
| BOOST_REQUIRE(m_tester2.commandValidationFailed()); |
| m_tester2.resetValidation(); |
| |
| // use cert2 for fib command (authorized for cert1 only) |
| shared_ptr<Interest> unauthorizedFibCommand = make_shared<Interest>("/localhost/nfd/fib/insert"); |
| generator.generateWithIdentity(*unauthorizedFibCommand, m_tester2.getIdentityName()); |
| |
| validator.validate(*unauthorizedFibCommand, |
| bind(&CommandValidatorTester::onValidated, boost::ref(m_tester2), _1), |
| bind(&CommandValidatorTester::onValidationFailed, boost::ref(m_tester2), _1, _2)); |
| |
| BOOST_REQUIRE(m_tester2.commandValidationFailed()); |
| } |
| |
| BOOST_AUTO_TEST_CASE(NoAuthorizeSections) |
| { |
| const std::string NO_AUTHORIZE_CONFIG = |
| "authorizations\n" |
| "{\n" |
| "}\n"; |
| |
| ConfigFile config; |
| CommandValidator validator; |
| |
| validator.setConfigFile(config); |
| BOOST_CHECK_THROW(config.parse(NO_AUTHORIZE_CONFIG, false, CONFIG_PATH.native()), ConfigFile::Error); |
| } |
| |
| BOOST_AUTO_TEST_CASE(NoPrivilegesSections) |
| { |
| const std::string NO_PRIVILEGES_CONFIG = |
| "authorizations\n" |
| "{\n" |
| " authorize\n" |
| " {\n" |
| " certfile \"tests/daemon/mgmt/cert1.ndncert\"\n" |
| " }\n" |
| "}\n"; |
| |
| ConfigFile config; |
| CommandValidator validator; |
| |
| validator.setConfigFile(config); |
| |
| BOOST_CHECK_THROW(config.parse(NO_PRIVILEGES_CONFIG, false, CONFIG_PATH.native()), ConfigFile::Error); |
| } |
| |
| BOOST_AUTO_TEST_CASE(InvalidCertfile) |
| { |
| const std::string INVALID_CERT_CONFIG = |
| "authorizations\n" |
| "{\n" |
| " authorize\n" |
| " {\n" |
| " certfile \"tests/daemon/mgmt/notacertfile.ndncert\"\n" |
| " privileges\n" |
| " {\n" |
| " fib\n" |
| " stats\n" |
| " }\n" |
| " }\n" |
| "}\n"; |
| |
| ConfigFile config; |
| CommandValidator validator; |
| |
| validator.setConfigFile(config); |
| BOOST_CHECK_THROW(config.parse(INVALID_CERT_CONFIG, false, CONFIG_PATH.native()), ConfigFile::Error); |
| } |
| |
| BOOST_AUTO_TEST_CASE(NoCertfile) |
| { |
| const std::string NO_CERT_CONFIG = |
| "authorizations\n" |
| "{\n" |
| " authorize\n" |
| " {\n" |
| " privileges\n" |
| " {\n" |
| " fib\n" |
| " stats\n" |
| " }\n" |
| " }\n" |
| "}\n"; |
| |
| |
| ConfigFile config; |
| CommandValidator validator; |
| |
| validator.setConfigFile(config); |
| BOOST_CHECK_THROW(config.parse(NO_CERT_CONFIG, false, CONFIG_PATH.native()), ConfigFile::Error); |
| } |
| |
| BOOST_AUTO_TEST_CASE(MalformedCert) |
| { |
| const std::string MALFORMED_CERT_CONFIG = |
| "authorizations\n" |
| "{\n" |
| " authorize\n" |
| " {\n" |
| " certfile \"tests/daemon/mgmt/malformed.ndncert\"\n" |
| " privileges\n" |
| " {\n" |
| " fib\n" |
| " stats\n" |
| " }\n" |
| " }\n" |
| "}\n"; |
| |
| |
| ConfigFile config; |
| CommandValidator validator; |
| |
| validator.setConfigFile(config); |
| BOOST_CHECK_THROW(config.parse(MALFORMED_CERT_CONFIG, false, CONFIG_PATH.native()), ConfigFile::Error); |
| } |
| |
| bool |
| validateErrorMessage(const std::string& expectedMessage, const ConfigFile::Error& error) |
| { |
| bool gotExpected = error.what() == expectedMessage; |
| if (!gotExpected) |
| { |
| NFD_LOG_WARN("\ncaught exception: " << error.what() |
| << "\n\nexpected exception: " << expectedMessage); |
| } |
| return gotExpected; |
| } |
| |
| BOOST_AUTO_TEST_CASE(NoAuthorizeSectionsDryRun) |
| { |
| const std::string NO_AUTHORIZE_CONFIG = |
| "authorizations\n" |
| "{\n" |
| "}\n"; |
| |
| ConfigFile config; |
| CommandValidator validator; |
| |
| validator.setConfigFile(config); |
| BOOST_CHECK_EXCEPTION(config.parse(NO_AUTHORIZE_CONFIG, true, CONFIG_PATH.native()), |
| ConfigFile::Error, |
| bind(&validateErrorMessage, |
| "No authorize sections found", _1)); |
| } |
| |
| BOOST_FIXTURE_TEST_CASE(NoPrivilegesSectionsDryRun, TwoValidatorFixture) |
| { |
| const std::string NO_PRIVILEGES_CONFIG = |
| "authorizations\n" |
| "{\n" |
| " authorize\n" |
| " {\n" |
| " certfile \"tests/daemon/mgmt/cert1.ndncert\"\n" |
| " }\n" |
| " authorize\n" |
| " {\n" |
| " certfile \"tests/daemon/mgmt/cert2.ndncert\"\n" |
| " }\n" |
| "}\n"; |
| |
| ConfigFile config; |
| CommandValidator validator; |
| |
| validator.setConfigFile(config); |
| |
| std::stringstream expectedError; |
| expectedError << "No privileges section found for certificate file tests/daemon/mgmt/cert1.ndncert " |
| << "(" << m_tester1.getPublicKeyName().toUri() << ")\n" |
| << "No privileges section found for certificate file tests/daemon/mgmt/cert2.ndncert " |
| << "(" << m_tester2.getPublicKeyName().toUri() << ")"; |
| |
| BOOST_CHECK_EXCEPTION(config.parse(NO_PRIVILEGES_CONFIG, true, CONFIG_PATH.native()), |
| ConfigFile::Error, |
| bind(&validateErrorMessage, expectedError.str(), _1)); |
| } |
| |
| BOOST_AUTO_TEST_CASE(InvalidCertfileDryRun) |
| { |
| using namespace boost::filesystem; |
| |
| const std::string INVALID_KEY_CONFIG = |
| "authorizations\n" |
| "{\n" |
| " authorize\n" |
| " {\n" |
| " certfile \"tests/daemon/mgmt/notacertfile.ndncert\"\n" |
| " privileges\n" |
| " {\n" |
| " fib\n" |
| " stats\n" |
| " }\n" |
| " }\n" |
| " authorize\n" |
| " {\n" |
| " certfile \"tests/daemon/mgmt/stillnotacertfile.ndncert\"\n" |
| " privileges\n" |
| " {\n" |
| " }\n" |
| " }\n" |
| "}\n"; |
| |
| ConfigFile config; |
| CommandValidator validator; |
| |
| validator.setConfigFile(config); |
| |
| std::stringstream error; |
| error << "Unable to open certificate file " |
| << absolute("tests/daemon/mgmt/notacertfile.ndncert").native() << "\n" |
| << "Unable to open certificate file " |
| << absolute("tests/daemon/mgmt/stillnotacertfile.ndncert").native(); |
| |
| BOOST_CHECK_EXCEPTION(config.parse(INVALID_KEY_CONFIG, true, CONFIG_PATH.native()), |
| ConfigFile::Error, |
| bind(&validateErrorMessage, error.str(), _1)); |
| } |
| |
| BOOST_AUTO_TEST_CASE(NoCertfileDryRun) |
| { |
| const std::string NO_CERT_CONFIG = |
| "authorizations\n" |
| "{\n" |
| " authorize\n" |
| " {\n" |
| " privileges\n" |
| " {\n" |
| " fib\n" |
| " stats\n" |
| " }\n" |
| " }\n" |
| " authorize\n" |
| " {\n" |
| " }\n" |
| "}\n"; |
| |
| |
| ConfigFile config; |
| CommandValidator validator; |
| |
| validator.setConfigFile(config); |
| BOOST_CHECK_EXCEPTION(config.parse(NO_CERT_CONFIG, true, CONFIG_PATH.native()), |
| ConfigFile::Error, |
| bind(&validateErrorMessage, |
| "No certfile specified\n" |
| "No certfile specified", _1)); |
| } |
| |
| BOOST_AUTO_TEST_CASE(MalformedCertDryRun) |
| { |
| using namespace boost::filesystem; |
| |
| const std::string MALFORMED_CERT_CONFIG = |
| "authorizations\n" |
| "{\n" |
| " authorize\n" |
| " {\n" |
| " certfile \"tests/daemon/mgmt/malformed.ndncert\"\n" |
| " privileges\n" |
| " {\n" |
| " fib\n" |
| " stats\n" |
| " }\n" |
| " }\n" |
| " authorize\n" |
| " {\n" |
| " certfile \"tests/daemon/mgmt/malformed.ndncert\"\n" |
| " }\n" |
| "}\n"; |
| |
| |
| ConfigFile config; |
| CommandValidator validator; |
| |
| validator.setConfigFile(config); |
| |
| std::stringstream error; |
| error << "Malformed certificate file " |
| << absolute("tests/daemon/mgmt/malformed.ndncert").native() << "\n" |
| << "Malformed certificate file " |
| << absolute("tests/daemon/mgmt/malformed.ndncert").native(); |
| |
| BOOST_CHECK_EXCEPTION(config.parse(MALFORMED_CERT_CONFIG, true, CONFIG_PATH.native()), |
| ConfigFile::Error, |
| bind(&validateErrorMessage, error.str(), _1)); |
| } |
| |
| BOOST_AUTO_TEST_SUITE_END() |
| |
| } // namespace tests |
| |
| } // namespace nfd |