mgmt, main: add support for authorized commands
refs: #1227
Change-Id: I907d1fa6e78775470c5376fcdfe898be4c311001
diff --git a/tests/mgmt/command-validator.cpp b/tests/mgmt/command-validator.cpp
new file mode 100644
index 0000000..7a6d39d
--- /dev/null
+++ b/tests/mgmt/command-validator.cpp
@@ -0,0 +1,585 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (C) 2014 Named Data Networking Project
+ * See COPYING for copyright and distribution information.
+ */
+
+#include "mgmt/command-validator.hpp"
+#include "mgmt/config-file.hpp"
+
+#include "tests/test-common.hpp"
+
+#include <boost/test/unit_test.hpp>
+#include <ndn-cpp-dev/util/command-interest-generator.hpp>
+#include <ndn-cpp-dev/util/io.hpp>
+#include <boost/filesystem.hpp>
+
+namespace nfd {
+
+namespace tests {
+
+NFD_LOG_INIT("CommandValidatorTest");
+
+BOOST_FIXTURE_TEST_SUITE(MgmtCommandValidator, BaseFixture)
+
+// authorizations
+// {
+// ; an authorize section grants privileges to a key
+// authorize
+// {
+// keyfile "tests/mgmt/key1.pub" ; public key file
+// privileges ; set of privileges granted to this public key
+// {
+// fib
+// stats
+// }
+// }
+
+// authorize
+// {
+// keyfile "tests/mgmt/key2.pub" ; public key file
+// privileges ; set of privileges granted to this public key
+// {
+// faces
+// }
+// }
+// }
+
+const std::string CONFIG =
+"authorizations\n"
+"{\n"
+" authorize\n"
+" {\n"
+" keyfile \"tests/mgmt/key1.pub\"\n"
+" privileges\n"
+" {\n"
+" fib\n"
+" stats\n"
+" }\n"
+" }\n"
+" authorize\n"
+" {\n"
+" keyfile \"tests/mgmt/key2.pub\"\n"
+" privileges\n"
+" {\n"
+" faces\n"
+" }\n"
+" }\n"
+ "}\n";
+
+class CommandValidatorTester
+{
+public:
+
+ CommandValidatorTester()
+ : m_validated(false),
+ m_validationFailed(false)
+ {
+
+ }
+
+ void
+ generateIdentity(const Name& prefix)
+ {
+ m_identityName = prefix;
+ m_identityName.append(boost::lexical_cast<std::string>(ndn::time::now()));
+
+ 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/mgmt/key1.pub");
+
+ m_tester2.generateIdentity("/test/CommandValidator/TwoKeys/id2");
+ m_tester2.saveIdentityToFile("tests/mgmt/key2.pub");
+ }
+
+ ~TwoValidatorFixture()
+ {
+ boost::system::error_code error;
+ boost::filesystem::remove("tests/mgmt/key1.pub", error);
+ boost::filesystem::remove("tests/mgmt/key2.pub", 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");
+
+ config.addSectionHandler("authorizations",
+ bind(&CommandValidator::onConfig, boost::ref(validator), _1, _2));
+ config.parse(CONFIG, false);
+
+ 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 key2 for fib command (authorized for key1 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/mgmt/key1.pub");
+
+ CommandValidatorTester tester2;
+ tester2.generateIdentity("/test/CommandValidator/TwoKeys/id2");
+ tester2.saveIdentityToFile("tests/mgmt/key2.pub");
+
+ 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");
+
+ config.addSectionHandler("authorizations",
+ bind(&CommandValidator::onConfig, boost::ref(validator), _1, _2));
+ config.parse(CONFIG, true);
+
+ 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 key2 for fib command (authorized for key1 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;
+
+ config.addSectionHandler("authorizations",
+ bind(&CommandValidator::onConfig, boost::ref(validator), _1, _2));
+ BOOST_CHECK_THROW(config.parse(NO_AUTHORIZE_CONFIG, false), ConfigFile::Error);
+}
+
+BOOST_AUTO_TEST_CASE(NoPrivilegesSections)
+{
+ const std::string NO_PRIVILEGES_CONFIG =
+ "authorizations\n"
+ "{\n"
+ " authorize\n"
+ " {\n"
+ " keyfile \"tests/mgmt/key1.pub\"\n"
+ " }\n"
+ "}\n";
+
+ ConfigFile config;
+ CommandValidator validator;
+
+ config.addSectionHandler("authorizations",
+ bind(&CommandValidator::onConfig, boost::ref(validator), _1, _2));
+ BOOST_CHECK_THROW(config.parse(NO_PRIVILEGES_CONFIG, false), ConfigFile::Error);
+}
+
+BOOST_AUTO_TEST_CASE(InvalidKeyFile)
+{
+ const std::string INVALID_KEY_CONFIG =
+ "authorizations\n"
+ "{\n"
+ " authorize\n"
+ " {\n"
+ " keyfile \"tests/mgmt/notakeyfile.pub\"\n"
+ " privileges\n"
+ " {\n"
+ " fib\n"
+ " stats\n"
+ " }\n"
+ " }\n"
+ "}\n";
+
+ ConfigFile config;
+ CommandValidator validator;
+
+ config.addSectionHandler("authorizations",
+ bind(&CommandValidator::onConfig, boost::ref(validator), _1, _2));
+ BOOST_CHECK_THROW(config.parse(INVALID_KEY_CONFIG, false), ConfigFile::Error);
+}
+
+BOOST_AUTO_TEST_CASE(NoKeyFile)
+{
+ const std::string NO_KEY_CONFIG =
+ "authorizations\n"
+ "{\n"
+ " authorize\n"
+ " {\n"
+ " privileges\n"
+ " {\n"
+ " fib\n"
+ " stats\n"
+ " }\n"
+ " }\n"
+ "}\n";
+
+
+ ConfigFile config;
+ CommandValidator validator;
+
+ config.addSectionHandler("authorizations",
+ bind(&CommandValidator::onConfig, boost::ref(validator), _1, _2));
+ BOOST_CHECK_THROW(config.parse(NO_KEY_CONFIG, false), ConfigFile::Error);
+}
+
+BOOST_AUTO_TEST_CASE(MalformedKey)
+{
+ const std::string MALFORMED_KEY_CONFIG =
+ "authorizations\n"
+ "{\n"
+ " authorize\n"
+ " {\n"
+ " keyfile \"tests/mgmt/malformedkey.pub\"\n"
+ " privileges\n"
+ " {\n"
+ " fib\n"
+ " stats\n"
+ " }\n"
+ " }\n"
+ "}\n";
+
+
+ ConfigFile config;
+ CommandValidator validator;
+
+ config.addSectionHandler("authorizations",
+ bind(&CommandValidator::onConfig, boost::ref(validator), _1, _2));
+ BOOST_CHECK_THROW(config.parse(MALFORMED_KEY_CONFIG, false), 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;
+
+ config.addSectionHandler("authorizations",
+ bind(&CommandValidator::onConfig, boost::ref(validator), _1, _2));
+ BOOST_CHECK_EXCEPTION(config.parse(NO_AUTHORIZE_CONFIG, true),
+ 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"
+ " keyfile \"tests/mgmt/key1.pub\"\n"
+ " }\n"
+ " authorize\n"
+ " {\n"
+ " keyfile \"tests/mgmt/key2.pub\"\n"
+ " }\n"
+ "}\n";
+
+ // CommandValidatorTester tester1;
+ // tester1.generateIdentity("/tests/CommandValidator/TwoKeys/id1");
+ // tester1.saveIdentityToFile("tests/mgmt/key1.pub");
+
+ // CommandValidatorTester tester2;
+ // tester2.generateIdentity("/tests/CommandValidator/TwoKeys/id2");
+ // tester2.saveIdentityToFile("tests/mgmt/key2.pub");
+
+ ConfigFile config;
+ CommandValidator validator;
+
+ config.addSectionHandler("authorizations",
+ bind(&CommandValidator::onConfig, boost::ref(validator), _1, _2));
+
+ std::stringstream expectedError;
+ expectedError << "No privileges section found for key file tests/mgmt/key1.pub "
+ << "(" << m_tester1.getPublicKeyName().toUri() << ")\n"
+ << "No privileges section found for key file tests/mgmt/key2.pub "
+ << "(" << m_tester2.getPublicKeyName().toUri() << ")";
+
+ BOOST_CHECK_EXCEPTION(config.parse(NO_PRIVILEGES_CONFIG, true),
+ ConfigFile::Error,
+ bind(&validateErrorMessage, expectedError.str(), _1));
+}
+
+BOOST_AUTO_TEST_CASE(InvalidKeyFileDryRun)
+{
+ const std::string INVALID_KEY_CONFIG =
+ "authorizations\n"
+ "{\n"
+ " authorize\n"
+ " {\n"
+ " keyfile \"tests/mgmt/notakeyfile.pub\"\n"
+ " privileges\n"
+ " {\n"
+ " fib\n"
+ " stats\n"
+ " }\n"
+ " }\n"
+ " authorize\n"
+ " {\n"
+ " keyfile \"tests/mgmt/stillnotakeyfile.pub\"\n"
+ " privileges\n"
+ " {\n"
+ " }\n"
+ " }\n"
+ "}\n";
+
+ ConfigFile config;
+ CommandValidator validator;
+
+ config.addSectionHandler("authorizations",
+ bind(&CommandValidator::onConfig, boost::ref(validator), _1, _2));
+
+ BOOST_CHECK_EXCEPTION(config.parse(INVALID_KEY_CONFIG, true),
+ ConfigFile::Error,
+ bind(&validateErrorMessage,
+ "Unable to open key file tests/mgmt/notakeyfile.pub\n"
+ "Unable to open key file tests/mgmt/stillnotakeyfile.pub", _1));
+}
+
+BOOST_AUTO_TEST_CASE(NoKeyFileDryRun)
+{
+ const std::string NO_KEY_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;
+
+ config.addSectionHandler("authorizations",
+ bind(&CommandValidator::onConfig, boost::ref(validator), _1, _2));
+ BOOST_CHECK_EXCEPTION(config.parse(NO_KEY_CONFIG, true),
+ ConfigFile::Error,
+ bind(&validateErrorMessage,
+ "No keyfile specified\n"
+ "No keyfile specified", _1));
+}
+
+BOOST_AUTO_TEST_CASE(MalformedKeyDryRun)
+{
+ const std::string MALFORMED_KEY_CONFIG =
+ "authorizations\n"
+ "{\n"
+ " authorize\n"
+ " {\n"
+ " keyfile \"tests/mgmt/malformedkey.pub\"\n"
+ " privileges\n"
+ " {\n"
+ " fib\n"
+ " stats\n"
+ " }\n"
+ " }\n"
+ " authorize\n"
+ " {\n"
+ " keyfile \"tests/mgmt/malformedkey.pub\"\n"
+ " }\n"
+ "}\n";
+
+
+ ConfigFile config;
+ CommandValidator validator;
+
+ config.addSectionHandler("authorizations",
+ bind(&CommandValidator::onConfig, boost::ref(validator), _1, _2));
+ BOOST_CHECK_EXCEPTION(config.parse(MALFORMED_KEY_CONFIG, true),
+ ConfigFile::Error,
+ bind(&validateErrorMessage,
+ "Malformed key file tests/mgmt/malformedkey.pub\n"
+ "Malformed key file tests/mgmt/malformedkey.pub", _1));
+}
+
+BOOST_AUTO_TEST_SUITE_END()
+
+} // namespace tests
+
+} // namespace nfd
+