catalog: implement trust model
Change-Id: I28a9ead71357eb180316cd1707138518b5fb8b88
refs: #3020
diff --git a/catalog/src/publish/publish-adapter.hpp b/catalog/src/publish/publish-adapter.hpp
index 9d29434..492bcff 100644
--- a/catalog/src/publish/publish-adapter.hpp
+++ b/catalog/src/publish/publish-adapter.hpp
@@ -21,6 +21,7 @@
#include "util/catalog-adapter.hpp"
#include "util/mysql-util.hpp"
+#include <mysql/mysql.h>
#include <json/reader.h>
#include <json/value.h>
@@ -31,8 +32,7 @@
#include <ndn-cxx/interest-filter.hpp>
#include <ndn-cxx/name.hpp>
#include <ndn-cxx/security/key-chain.hpp>
-#include <ndn-cxx/security/validator.hpp>
-#include "mysql/mysql.h"
+#include <ndn-cxx/security/validator-config.hpp>
#include <memory>
#include <string>
@@ -41,6 +41,7 @@
namespace atmos {
namespace publish {
+
/**
* PublishAdapter handles the Publish usecases for the catalog
*/
@@ -107,13 +108,23 @@
void
setFilters();
+ /**
+ * Function to validate publication changes against the trust model, which is, all file
+ * names must be under the publisher's prefix. This function should be called by a callback
+ * function invoked by validator
+ *
+ * @param data: received data from the publisher
+ */
+ bool
+ validatePublicationChanges(const std::shared_ptr<const ndn::Data>& data);
+
protected:
typedef std::unordered_map<ndn::Name, const ndn::RegisteredPrefixId*> RegisteredPrefixList;
// Prefix for ChronoSync
ndn::Name m_syncPrefix;
// Handle to the Catalog's database
std::shared_ptr<DatabaseHandler> m_databaseHandler;
- std::shared_ptr<ndn::Validator> m_validaor;
+ std::unique_ptr<ndn::ValidatorConfig> m_publishValidator;
RegisteredPrefixList m_registeredPrefixList;
};
@@ -130,13 +141,14 @@
PublishAdapter<DatabaseHandler>::setFilters()
{
ndn::Name publishPrefix = ndn::Name(m_prefix).append("publish");
- m_registeredPrefixList[publishPrefix] = m_face->setInterestFilter(publishPrefix,
- bind(&publish::PublishAdapter<DatabaseHandler>::onPublishInterest,
- this, _1, _2),
- bind(&publish::PublishAdapter<DatabaseHandler>::onRegisterSuccess,
- this, _1),
- bind(&publish::PublishAdapter<DatabaseHandler>::onRegisterFailure,
- this, _1, _2));
+ m_registeredPrefixList[publishPrefix] =
+ m_face->setInterestFilter(publishPrefix,
+ bind(&PublishAdapter<DatabaseHandler>::onPublishInterest,
+ this, _1, _2),
+ bind(&publish::PublishAdapter<DatabaseHandler>::onRegisterSuccess,
+ this, _1),
+ bind(&publish::PublishAdapter<DatabaseHandler>::onRegisterFailure,
+ this, _1, _2));
}
template <typename DatabaseHandler>
@@ -184,9 +196,12 @@
" in \"publish\" section");
}
}
-
- // @todo: parse the published_file_security section
-
+ else if (item->first == "security") {
+ // when use, the validator must specify the callback func to handle the validated data
+ // it should be called when the Data packet that contains the published file names is received
+ m_publishValidator.reset(new ndn::ValidatorConfig(m_face.get()));
+ m_publishValidator->load(item->second, filename);
+ }
else if (item->first == "database") {
const util::ConfigSection& databaseSection = item->second;
for (auto subItem = databaseSection.begin();
@@ -279,7 +294,41 @@
PublishAdapter<DatabaseHandler>::onPublishedData(const ndn::Interest& interest,
const ndn::Data& data)
{
- // @todo handle publishing the data
+ // @todo handle data publication
+}
+
+template<typename DatabaseHandler>
+bool
+PublishAdapter<DatabaseHandler>::validatePublicationChanges(const std::shared_ptr<const ndn::Data>& data)
+{
+ // The data name must be "/<publisher-prefix>/<nonce>"
+ // the prefix is the data name removes the last component
+ ndn::Name publisherPrefix = data->getName().getPrefix(-1);
+
+ const std::string payload(reinterpret_cast<const char*>(data->getContent().value()),
+ data->getContent().value_size());
+ Json::Value parsedFromString;
+ Json::Reader reader;
+ if (!reader.parse(payload, parsedFromString)) {
+ // parse error, log events
+ std::cout << "Cannot parse the published data " << data->getName() << " into Json" << std::endl;
+ return false;
+ }
+
+ // validate added files...
+ for (size_t i = 0; i < parsedFromString["add"].size(); i++) {
+ if (!publisherPrefix.isPrefixOf(
+ ndn::Name(parsedFromString["add"][static_cast<int>(i)].asString())))
+ return false;
+ }
+
+ // validate removed files ...
+ for (size_t i = 0; i < parsedFromString["remove"].size(); i++) {
+ if (!publisherPrefix.isPrefixOf(
+ ndn::Name(parsedFromString["remove"][static_cast<int>(i)].asString())))
+ return false;
+ }
+ return true;
}
} // namespace publish
diff --git a/catalog/tests/unit-tests/publish/test-publish-adapter.cpp b/catalog/tests/unit-tests/publish/test-publish-adapter.cpp
new file mode 100644
index 0000000..7d6a894
--- /dev/null
+++ b/catalog/tests/unit-tests/publish/test-publish-adapter.cpp
@@ -0,0 +1,235 @@
+/** NDN-Atmos: Cataloging Service for distributed data originally developed
+ * for atmospheric science data
+ * Copyright (C) 2015 Colorado State University
+ *
+ * NDN-Atmos 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.
+ *
+ * NDN-Atmos 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 NDN-Atmos. If not, see <http://www.gnu.org/licenses/>.
+**/
+
+#include "publish/publish-adapter.hpp"
+#include "boost-test.hpp"
+#include "../../unit-test-time-fixture.hpp"
+#include "util/config-file.hpp"
+
+#include <boost/mpl/list.hpp>
+#include <boost/thread.hpp>
+#include <ndn-cxx/util/dummy-client-face.hpp>
+#include <boost/property_tree/info_parser.hpp>
+
+namespace atmos{
+namespace tests{
+ using ndn::util::DummyClientFace;
+ using ndn::util::makeDummyClientFace;
+
+ class PublishAdapterTest : public publish::PublishAdapter<std::string>
+ {
+ public:
+ PublishAdapterTest(const std::shared_ptr<ndn::util::DummyClientFace>& face,
+ const std::shared_ptr<ndn::KeyChain>& keyChain)
+ : publish::PublishAdapter<std::string>(face, keyChain)
+ {
+ }
+
+ virtual
+ ~PublishAdapterTest()
+ {
+ }
+
+ const ndn::Name
+ getPrefix()
+ {
+ return m_prefix;
+ }
+
+ const ndn::Name
+ getSigningId()
+ {
+ return m_signingId;
+ }
+
+ const ndn::Name
+ getSyncPrefix()
+ {
+ return m_syncPrefix;
+ }
+
+ void
+ configAdapter(const util::ConfigSection& section,
+ const ndn::Name& prefix)
+ {
+ onConfig(section, false, std::string("test.txt"), prefix);
+ }
+
+ bool
+ testValidatePublicationChanges(const std::shared_ptr<const ndn::Data>& data)
+ {
+ return validatePublicationChanges(data);
+ }
+ };
+
+ class PublishAdapterFixture : public UnitTestTimeFixture
+ {
+ public:
+ PublishAdapterFixture()
+ : face(makeDummyClientFace(io))
+ , keyChain(new ndn::KeyChain())
+ , publishAdapterTest1(face, keyChain)
+ , publishAdapterTest2(face, keyChain)
+ {
+ }
+
+ virtual
+ ~PublishAdapterFixture()
+ {
+ }
+
+ protected:
+ void
+ initializePublishAdapterTest1()
+ {
+ util::ConfigSection section;
+ try {
+ std::stringstream ss;
+ ss << "database \
+ { \
+ dbServer localhost \
+ dbName testdb \
+ dbUser testuser \
+ dbPasswd testpwd \
+ } \
+ sync \
+ { \
+ prefix ndn:/ndn/broadcast1 \
+ }";
+ boost::property_tree::read_info(ss, section);
+ }
+ catch (boost::property_tree::info_parser_error &e) {
+ std::cout << "Failed to read config file " << e.what() << std::endl;
+ }
+ publishAdapterTest1.configAdapter(section, ndn::Name("/test"));
+ }
+
+ void
+ initializePublishAdapterTest2()
+ {
+ util::ConfigSection section;
+ try {
+ std::stringstream ss;
+ ss << "\
+ signingId /prefix/signingId \
+ database \
+ { \
+ dbServer localhost \
+ dbName testdb \
+ dbUser testuser \
+ dbPasswd testpwd \
+ } \
+ sync \
+ { \
+ }";
+ boost::property_tree::read_info(ss, section);
+ }
+ catch (boost::property_tree::info_parser_error &e) {
+ std::cout << "Failed to read config file " << e.what() << std::endl;;
+ }
+ publishAdapterTest2.configAdapter(section, ndn::Name("/test"));
+ }
+
+ protected:
+ std::shared_ptr<DummyClientFace> face;
+ std::shared_ptr<ndn::KeyChain> keyChain;
+ PublishAdapterTest publishAdapterTest1;
+ PublishAdapterTest publishAdapterTest2;
+ };
+
+ BOOST_FIXTURE_TEST_SUITE(PublishAdapterTestSuite, PublishAdapterFixture)
+
+ BOOST_AUTO_TEST_CASE(BasicPublishAdapterTest1)
+ {
+ BOOST_CHECK(publishAdapterTest1.getPrefix() == ndn::Name());
+ BOOST_CHECK(publishAdapterTest1.getSigningId() == ndn::Name());
+ BOOST_CHECK(publishAdapterTest1.getSyncPrefix() == ndn::Name());
+ }
+
+ BOOST_AUTO_TEST_CASE(BasicPublishAdapterTest2)
+ {
+ initializePublishAdapterTest1();
+ BOOST_CHECK(publishAdapterTest1.getPrefix() == ndn::Name("/test"));
+ BOOST_CHECK(publishAdapterTest1.getSigningId() == ndn::Name());
+ BOOST_CHECK(publishAdapterTest1.getSyncPrefix() == ndn::Name("ndn:/ndn/broadcast1"));
+
+ initializePublishAdapterTest2();
+ BOOST_CHECK(publishAdapterTest2.getPrefix() == ndn::Name("/test"));
+ BOOST_CHECK(publishAdapterTest2.getSigningId() == ndn::Name("/prefix/signingId"));
+ BOOST_CHECK(publishAdapterTest2.getSyncPrefix() ==
+ ndn::Name("ndn:/ndn-atmos/broadcast/chronosync"));
+ }
+
+ BOOST_AUTO_TEST_CASE(PublishAdapterValidateDataTestSuccess)
+ {
+ ndn::Name dataName("/test/publisher/12345"); // data name must be prefix+nonce
+ Json::Value testJson;
+ testJson["add"][0] = "/test/publisher/1";
+ testJson["add"][1] = "/test/publisher/2";
+ testJson["remove"][0] = "/test/publisher/5";
+
+ Json::FastWriter fastWriter;
+ const std::string jsonMessage = fastWriter.write(testJson);
+ const char* payload = jsonMessage.c_str();
+ size_t payLoadLength = jsonMessage.size() + 1;
+
+ std::shared_ptr<ndn::Data> data = std::make_shared<ndn::Data>(dataName);
+ data->setContent(reinterpret_cast<const uint8_t*>(payload), payLoadLength);
+ data->setFreshnessPeriod(ndn::time::milliseconds(10000));
+
+ BOOST_CHECK_EQUAL(true, publishAdapterTest1.testValidatePublicationChanges(data));
+
+ ndn::Name dataName2("/"); // short data name
+ Json::Value testJson2;
+ testJson2["add"][0] = "/test/publisher2/1";
+ testJson2["remove"][0] = "/test/publisher/1/2/3";
+
+ const std::string jsonMessage2 = fastWriter.write(testJson2);
+ const char* payload2 = jsonMessage2.c_str();
+ size_t payLoadLength2 = jsonMessage2.size() + 1;
+
+ std::shared_ptr<ndn::Data> data2 = std::make_shared<ndn::Data>(dataName2);
+ data2->setContent(reinterpret_cast<const uint8_t*>(payload2), payLoadLength2);
+ data2->setFreshnessPeriod(ndn::time::milliseconds(10000));
+ BOOST_CHECK_EQUAL(true, publishAdapterTest1.testValidatePublicationChanges(data2));
+ }
+
+ BOOST_AUTO_TEST_CASE(PublishAdapterValidateDataTestFail)
+ {
+ ndn::Name dataName1("test/publisher2/12345"); // data name must be prefix+nonce
+ Json::Value testJson1;
+ testJson1["add"][0] = "/test/publisher2/1";
+ testJson1["remove"][0] = "/test/publisher/1/2/3";
+ testJson1["remove"][1] = "/test/publisher2/4";
+
+ Json::FastWriter fastWriter;
+ const std::string jsonMessage1 = fastWriter.write(testJson1);
+ const char* payload1 = jsonMessage1.c_str();
+ size_t payLoadLength1 = jsonMessage1.size() + 1;
+
+ std::shared_ptr<ndn::Data> data1 = std::make_shared<ndn::Data>(dataName1);
+ data1->setContent(reinterpret_cast<const uint8_t*>(payload1), payLoadLength1);
+ data1->setFreshnessPeriod(ndn::time::milliseconds(10000));
+
+ BOOST_CHECK_EQUAL(false, publishAdapterTest1.testValidatePublicationChanges(data1));
+ }
+
+ BOOST_AUTO_TEST_SUITE_END()
+
+}//tests
+}//atmos