Commit a1b91fbe authored by Thomas Markwalder's avatar Thomas Markwalder
Browse files

[3186] Added doxygen commentary to user check hook lib

Added doxygen throughout, cleaned up unit tests.  Removed CLIENT_ID type
as it was unnecessary.
parent e76a6be3
......@@ -33,7 +33,7 @@ libdhcp_user_chk_la_SOURCES += load_unload.cc
libdhcp_user_chk_la_SOURCES += subnet_select_co.cc
libdhcp_user_chk_la_SOURCES += user.cc user.h
libdhcp_user_chk_la_SOURCES += user_chk_log.cc user_chk_log.h
libdhcp_user_chk_la_SOURCES += user_data_source.cc user_data_source.h
libdhcp_user_chk_la_SOURCES += user_data_source.h
libdhcp_user_chk_la_SOURCES += user_file.cc user_file.h
libdhcp_user_chk_la_SOURCES += user_registry.cc user_registry.h
libdhcp_user_chk_la_SOURCES += version.cc
......
......@@ -11,7 +11,8 @@
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
// load_unload.cc
/// @file This file defines the load and unload hooks library functions.
#include <hooks/hooks.h>
#include <user_registry.h>
......@@ -23,13 +24,28 @@
using namespace isc::hooks;
/// @brief Pointer to the registry instance.
UserRegistryPtr user_registry;
/// @brief Output filestream for recording user check outcomes.
std::fstream user_chk_output;
/// @brief For now, hard-code registry input file name.
const char* registry_fname = "/tmp/user_registry.txt";
/// @brief For now, hard-code user check outcome file name.
const char* user_chk_output_fname = "/tmp/user_check_output.txt";
extern "C" {
/// @brief Called by the Hooks library manager when the library is loaded.
///
/// Instantiates the UserRegistry and opens the outcome file. Failure in
/// either results in a failed return code.
///
/// @param unused library handle parameter required by Hooks API.
///
/// @return Returns 0 upon success, non-zero upon failure.
int load(LibraryHandle&) {
// non-zero indicates an error.
int ret_val = 0;
......@@ -49,14 +65,17 @@ int load(LibraryHandle&) {
// Open up the output file for user_chk results.
user_chk_output.open(user_chk_output_fname,
std::fstream::out | std::fstream::app);
int sav_errno = errno;
if (!user_chk_output) {
// Grab the system error message.
const char* errmsg = strerror(errno);
isc_throw(isc::Unexpected, "Cannot open output file: "
<< user_chk_output_fname
<< " reason: " << strerror(sav_errno));
<< " reason: " << errmsg);
}
}
catch (const std::exception& ex) {
// Log the error and return failure.
std::cout << "DHCP UserCheckHook could not be loaded: "
<< ex.what() << std::endl;
ret_val = 1;
......@@ -65,6 +84,11 @@ int load(LibraryHandle&) {
return (ret_val);
}
/// @brief Called by the Hooks library manager when the library is unloaded.
///
/// Destroys the UserRegistry and closes the outcome file.
///
/// @return Always returns 0.
int unload() {
try {
user_registry.reset();
......
// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
/// @file Defines the subnet4_select and subnet6_select callout functions.
#include <hooks/hooks.h>
#include <dhcp/pkt4.h>
#include <dhcp/dhcp6.h>
......@@ -17,10 +33,50 @@ extern std::fstream user_chk_output;
extern const char* registry_fname;
extern const char* user_chk_output_fname;
extern "C" {
/// @brief Adds an entry to the end of the user check outcome file.
///
/// Each user entry is written in an ini-like format, with one name-value pair
/// per line as follows:
///
/// id_type=<id type>
/// client=<id str>
/// subnet=<subnet str>
/// registered=<is registered>"
///
/// where:
/// <id type> text label of the id type: "HW_ADDR" or "DUID"
/// <id str> user's id formatted as either isc::dhcp::Hwaddr.toText() or
/// isc::dhcp::DUID.toText()
/// <subnet str> selected subnet formatted as isc::dhcp::Subnet4::toText() or
/// isc::dhcp::Subnet6::toText() as appropriate.
/// <is registered> "yes" or "no"
///
/// Sample IPv4 entry would like this:
///
/// @code
/// id_type=DUID
/// client=00:01:00:01:19:ef:e6:3b:00:0c:01:02:03:04
/// subnet=2001:db8:2::/64
/// registered=yes
/// id_type=duid
/// @endcode
///
/// Sample IPv4 entry would like this:
///
/// @code
/// id_type=DUID
/// id_type=HW_ADDR
/// client=hwtype=1 00:0c:01:02:03:05
/// subnet=152.77.5.0/24
/// registered=no
/// @endcode
///
/// @param id_type_str text label identify the id type
/// @param id_val_str text representation of the user id
/// @param subnet_str text representation of the selected subnet
/// @param registered boolean indicating if the user is registered or not
void generate_output_record(const std::string& id_type_str,
const std::string& id_val_str,
const std::string& subnet_str,
......@@ -32,10 +88,25 @@ void generate_output_record(const std::string& id_type_str,
<< "registered=" << (registered ? "yes" : "no")
<< std::endl;
// @todo Flush is here to ensure output is immediate for demo purposes.
// Performance would generally dictate not using it.
flush(user_chk_output);
}
// This callout is called at the "subnet4_select" hook.
/// @brief This callout is called at the "subnet4_select" hook.
///
/// This function searches the UserRegistry for the client indicated by the
/// inbound IPv4 DHCP packet. If the client is found in the registry output
/// the generate outcome record and return.
///
/// If the client is not found in the registry replace the selected subnet
/// with the restricted access subnet, then generate the outcome record and
/// return. By convention, it is assumed that last subnet in the list of
/// available subnets is the restricted access subnet.
///
/// @param handle CalloutHandle which provides access to context.
///
/// @return 0 upon success, non-zero otherwise.
int subnet4_select(CalloutHandle& handle) {
if (!user_registry) {
std::cout << "DHCP UserCheckHook : subnet4_select UserRegistry is null"
......@@ -55,22 +126,23 @@ int subnet4_select(CalloutHandle& handle) {
// Look for the user.
UserPtr registered_user = user_registry->findUser(*hwaddr);
if (registered_user) {
// User is in the registry, so leave the pre-selected
// subnet alone.
// User is in the registry, so leave the pre-selected subnet alone.
Subnet4Ptr subnet;
handle.getArgument("subnet4", subnet);
generate_output_record("mac", hwaddr->toText(), subnet->toText(),
true);
// Add the outcome entry to the output file.
generate_output_record(UserId::HW_ADDRESS_STR, hwaddr->toText(),
subnet->toText(), true);
} else {
// User is not in the registry, so assign them to
// the last subnet in the collection. By convention
// we are assuming this is the restricted subnet.
// User is not in the registry, so assign them to the last subnet
// in the collection. By convention we are assuming this is the
// restricted subnet.
const isc::dhcp::Subnet4Collection *subnets = NULL;
handle.getArgument("subnet4collection", subnets);
Subnet4Ptr subnet = subnets->back();
handle.setArgument("subnet4", subnet);
generate_output_record("mac", hwaddr->toText(), subnet->toText(),
false);
// Add the outcome entry to the output file.
generate_output_record(UserId::HW_ADDRESS_STR, hwaddr->toText(),
subnet->toText(), false);
}
} catch (const std::exception& ex) {
std::cout << "DHCP UserCheckHook : subnet6_select unexpected error: "
......@@ -80,7 +152,21 @@ int subnet4_select(CalloutHandle& handle) {
return (0);
}
// This callout is called at the "subnet6_select" hook.
/// @brief This callout is called at the "subnet6_select" hook.
///
/// This function searches the UserRegistry for the client indicated by the
/// inbound IPv6 DHCP packet. If the client is found in the registry generate
/// the outcome record and return.
///
/// If the client is not found in the registry replace the selected subnet
/// with the restricted access subnet, then generate the outcome record and
/// return. By convention, it is assumed that last subnet in the list of
/// available subnets is the restricted access subnet.
///
/// @param handle CalloutHandle which provides access to context.
///
/// @return 0 upon success, non-zero otherwise.
int subnet6_select(CalloutHandle& handle) {
if (!user_registry) {
std::cout << "DHCP UserCheckHook : subnet6_select UserRegistry is null"
......@@ -108,22 +194,23 @@ int subnet6_select(CalloutHandle& handle) {
// Look for the user.
UserPtr registered_user = user_registry->findUser(*duid);
if (registered_user) {
// User is in the registry, so leave the pre-selected
// subnet alone.
// User is in the registry, so leave the pre-selected subnet alone.
Subnet6Ptr subnet;
handle.getArgument("subnet6", subnet);
generate_output_record("duid", duid->toText(), subnet->toText(),
true);
// Add the outcome entry to the output file.
generate_output_record(UserId::DUID_STR, duid->toText(),
subnet->toText(), true);
} else {
// User is not in the registry, so assign them to
// the last subnet in the collection. By convention
// we are assuming this is the restricted subnet.
// User is not in the registry, so assign them to the last subnet
// in the collection. By convention we are assuming this is the
// restricted subnet.
const isc::dhcp::Subnet6Collection *subnets = NULL;
handle.getArgument("subnet6collection", subnets);
Subnet6Ptr subnet = subnets->back();
handle.setArgument("subnet6", subnet);
generate_output_record("duid", duid->toText(), subnet->toText(),
false);
// Add the outcome entry to the output file.
generate_output_record(UserId::DUID_STR, duid->toText(),
subnet->toText(), false);
}
} catch (const std::exception& ex) {
std::cout << "DHCP UserCheckHook : subnet6_select unexpected error: "
......
......@@ -37,7 +37,7 @@ libdhcp_user_chk_unittests_SOURCES += ../user.cc ../user.h
libdhcp_user_chk_unittests_SOURCES += ../user_chk_log.cc ../user_chk_log.h
# Until logging in dynamically loaded libraries is fixed, exclude these.
#libdhcp_user_chk_unittests_SOURCES += ../user_chk_messages.cc ../user_chk_messages.h
libdhcp_user_chk_unittests_SOURCES += ../user_data_source.cc ../user_data_source.h
libdhcp_user_chk_unittests_SOURCES += ../user_data_source.h
libdhcp_user_chk_unittests_SOURCES += ../user_file.cc ../user_file.h
libdhcp_user_chk_unittests_SOURCES += ../user_registry.cc ../user_registry.h
libdhcp_user_chk_unittests_SOURCES += run_unittests.cc
......
{ "type" : "HW_ADDR", "id" : "01AC00F03344", "opt1" : "true" }
{ "type" : "CLIENT_ID", "id" : "0899e0cc0707", "opt1" : "false" }
{ "type" : "DUID", "id" : "225060de0a0b", "opt1" : "true" }
......@@ -25,16 +25,26 @@ using namespace std;
namespace {
/// @brief Convenience method for reliably building test file path names.
///
/// Function prefixes the given file name with a path to unit tests directory
/// so we can reliably find test data files.
///
/// @param name base file name of the test file
std::string testFilePath(const std::string& name) {
return (std::string(USER_CHK_TEST_DIR) + "/" + name);
}
/// @brief Tests the UserFile constructor.
TEST(UserFile, construction) {
// Verify that a UserFile with no file name is rejected.
ASSERT_THROW(UserFile(""), UserFileError);
// Verify that a UserFile with a non-blank file name is accepted.
ASSERT_NO_THROW(UserFile("someName"));
}
/// @brief Tests opening and closing UserFile
TEST(UserFile, openFile) {
UserFilePtr user_file;
......@@ -47,8 +57,9 @@ TEST(UserFile, openFile) {
EXPECT_FALSE(user_file->isOpen());
// Construct a user file that should exist.
ASSERT_NO_THROW(user_file.reset(new
UserFile(testFilePath("test_users_1.txt"))));
ASSERT_NO_THROW(user_file.reset(new UserFile
(testFilePath("test_users_1.txt"))));
// File should not be open.
EXPECT_FALSE(user_file->isOpen());
// Verify that we can open it.
......@@ -67,15 +78,57 @@ TEST(UserFile, openFile) {
EXPECT_TRUE(user_file->isOpen());
}
/// @brief Tests makeUser with invalid user strings
TEST(UserFile, makeUser) {
const char* invalid_strs[]= {
// Missinge type element.
"{ \"id\" : \"01AC00F03344\" }",
// Invalid id type string value.
"{ \"type\" : \"BOGUS\", \"id\" : \"01AC00F03344\"}",
// Non-string id type
"{ \"type\" : 1, \"id\" : \"01AC00F03344\"}",
// Missing id element.
"{ \"type\" : \"HW_ADDR\" }",
// Odd number of digits in id value.
"{ \"type\" : \"HW_ADDR\", \"id\" : \"1AC00F03344\"}",
// Invalid characters in id value.
"{ \"type\" : \"HW_ADDR\", \"id\" : \"THIS IS BAD!\"}",
// Empty id value.
"{ \"type\" : \"HW_ADDR\", \"id\" : \"\"}",
// Non-string id.
"{ \"type\" : \"HW_ADDR\", \"id\" : 01AC00F03344 }",
// Option with non-string value
"{ \"type\" : \"HW_ADDR\", \"id\" : \"01AC00F03344\", \"opt\" : 4 }",
NULL
};
// Create a UseFile to work with.
UserFilePtr user_file;
ASSERT_NO_THROW(user_file.reset(new UserFile("noFile")));
// Iterate over the list of invalid user strings and verify
// each one fails.
const char** tmp = invalid_strs;;
while (*tmp) {
EXPECT_THROW(user_file->makeUser(*tmp), UserFileError)
<< "Invalid str not caught: ["
<< *tmp << "]" << std::endl;
++tmp;
}
}
/// @brief Test reading from UserFile
TEST(UserFile, readFile) {
UserFilePtr user_file;
// Construct an open a known file.
ASSERT_NO_THROW(user_file.reset(new
UserFile(testFilePath("test_users_1.txt"))));
// Construct and then open a known file.
ASSERT_NO_THROW(user_file.reset(new UserFile
(testFilePath("test_users_1.txt"))));
ASSERT_NO_THROW(user_file->open());
EXPECT_TRUE(user_file->isOpen());
// File should contain two valid users. Read and verify each.
UserPtr user;
int i = 0;
do {
......@@ -87,17 +140,13 @@ TEST(UserFile, readFile) {
EXPECT_EQ("true", user->getProperty("opt1"));
break;
case 1:
EXPECT_EQ(UserId::CLIENT_ID, user->getUserId().getType());
EXPECT_EQ("0899e0cc0707", user->getUserId().toText());
EXPECT_EQ("false", user->getProperty("opt1"));
break;
case 2:
EXPECT_EQ(UserId::DUID, user->getUserId().getType());
EXPECT_EQ("225060de0a0b", user->getUserId().toText());
EXPECT_EQ("true", user->getProperty("opt1"));
break;
default:
// this is an error, TBD
// Third time around, we are at EOF User should be null.
ASSERT_FALSE(user);
break;
}
} while (user);
......@@ -106,6 +155,4 @@ TEST(UserFile, readFile) {
ASSERT_NO_THROW(user_file->close());
}
} // end of anonymous namespace
......@@ -27,65 +27,72 @@ using namespace std;
namespace {
/// @brief Convenience method for reliably building test file path names.
///
/// Function prefixes the given file name with a path to unit tests directory
/// so we can reliably find test data files.
///
/// @param name base file name of the test file
std::string testFilePath(const std::string& name) {
return (std::string(USER_CHK_TEST_DIR) + "/" + name);
}
/// @brief Tests UserRegistry construction.
TEST(UserRegistry, constructor) {
// Currently there is only the default constructor which does not throw.
UserRegistryPtr reg;
ASSERT_NO_THROW(reg.reset(new UserRegistry()));
}
/// @brief Tests mechanics of adding, finding, removing Users.
TEST(UserRegistry, userBasics) {
// Create an empty registry.
UserRegistryPtr reg;
ASSERT_NO_THROW(reg.reset(new UserRegistry()));
UserIdPtr id, id2;
// Make user ids.
ASSERT_NO_THROW(id.reset(new UserId(UserId::HW_ADDRESS, "01010101")));
ASSERT_NO_THROW(id2.reset(new UserId(UserId::HW_ADDRESS, "02020202")));
UserPtr user, user2;
// Verify that a blank user cannot be added.
UserPtr user;
ASSERT_THROW(reg->addUser(user), UserRegistryError);
// Make new users.
// Make a new id and user.
UserIdPtr id;
ASSERT_NO_THROW(id.reset(new UserId(UserId::HW_ADDRESS, "01010101")));
ASSERT_NO_THROW(user.reset(new User(*id)));
ASSERT_NO_THROW(user2.reset(new User(*id)));
// Add first user.
// Verify that we can add a user.
ASSERT_NO_THROW(reg->addUser(user));
// Verify user added can be found.
// Verify that the user can be found.
UserPtr found_user;
ASSERT_NO_THROW(found_user = reg->findUser(*id));
EXPECT_TRUE(found_user);
EXPECT_EQ(found_user->getUserId(), *id);
// Verify user not added cannot be found.
// Verify that searching for a non-existant user returns empty user pointer.
UserIdPtr id2;
ASSERT_NO_THROW(id2.reset(new UserId(UserId::HW_ADDRESS, "02020202")));
ASSERT_NO_THROW(found_user = reg->findUser(*id2));
EXPECT_FALSE(found_user);
// Verfiy user can no longer be found.
// Verify that the user can be deleted.
ASSERT_NO_THROW(reg->removeUser(*id));
ASSERT_NO_THROW(found_user = reg->findUser(*id));
EXPECT_FALSE(found_user);
}
/// @brief Tests finding users by isc::dhcp::HWaddr instance.
TEST(UserRegistry, findByHWAddr) {
// Create the registry.
UserRegistryPtr reg;
ASSERT_NO_THROW(reg.reset(new UserRegistry()));
// Make a user.
// Make a new user and add it.
UserPtr user;
ASSERT_NO_THROW(user.reset(new User(UserId::HW_ADDRESS, "01010101")));
// Verify user can be added.
ASSERT_NO_THROW(reg->addUser(user));
// Make a HWAddr instance using the same id value.
isc::dhcp::HWAddr hwaddr(user->getUserId().getId(),
isc::dhcp::HTYPE_ETHER);
isc::dhcp::HWAddr hwaddr(user->getUserId().getId(), isc::dhcp::HTYPE_ETHER);
// Verify user can be found by HWAddr.
UserPtr found_user;
......@@ -94,100 +101,116 @@ TEST(UserRegistry, findByHWAddr) {
EXPECT_EQ(found_user->getUserId(), user->getUserId());
}
/// @brief Tests finding users by isc::dhcp::DUID instance.
TEST(UserRegistry, findByDUID) {
// Create the registry.
UserRegistryPtr reg;
ASSERT_NO_THROW(reg.reset(new UserRegistry()));
// Make a user.
// Make a new user and add it.
UserPtr user;
ASSERT_NO_THROW(user.reset(new User(UserId::DUID, "01010101")));
// Verify user can be added.
ASSERT_NO_THROW(reg->addUser(user));
// Make a HWAddr instance using the same id value.
// Make a DUID instance using the same id value.
isc::dhcp::DUID duid(user->getUserId().getId());
// Verify user can be found by HWAddr.
// Verify user can be found by DUID.
UserPtr found_user;
ASSERT_NO_THROW(found_user = reg->findUser(duid));
EXPECT_TRUE(found_user);
EXPECT_EQ(found_user->getUserId(), user->getUserId());
}
TEST(UserRegistry, findByClientId) {
UserRegistryPtr reg;
ASSERT_NO_THROW(reg.reset(new UserRegistry()));
// Make a user.
UserPtr user;
ASSERT_NO_THROW(user.reset(new User(UserId::CLIENT_ID, "01010101")));
// Verify user can be added.
ASSERT_NO_THROW(reg->addUser(user));
// Make a HWAddr instance using the same id value.
isc::dhcp::ClientId client_id(user->getUserId().getId());
// Verify user can be found by HWAddr.
UserPtr found_user;
ASSERT_NO_THROW(found_user = reg->findUser(client_id));
EXPECT_TRUE(found_user);
EXPECT_EQ(found_user->getUserId(), user->getUserId());
}
/// @brief Tests mixing users of different types.
TEST(UserRegistry, oneOfEach) {
// Create the registry.
UserRegistryPtr reg;
ASSERT_NO_THROW(reg.reset(new UserRegistry()));
// Make user ids.
UserIdPtr idh, idc, idd;
UserIdPtr idh, idd;
ASSERT_NO_THROW(idh.reset(new UserId(UserId::HW_ADDRESS, "01010101")));
ASSERT_NO_THROW(idc.reset(new UserId(UserId::CLIENT_ID, "02020202")));
ASSERT_NO_THROW(idd.reset(new UserId(UserId::DUID, "03030303")));
// Make and add HW_ADDRESS user.
UserPtr user;
user.reset(new User(*idh));
ASSERT_NO_THROW(reg->addUser(user));
user.reset(new User(*idc));
ASSERT_NO_THROW(reg->addUser(user));
// Make and add DUID user.
user.reset(new User(*idd));
ASSERT_NO_THROW(reg->addUser(user));
// Verify we can find both.
UserPtr found_user;
ASSERT_NO_THROW(found_user = reg->findUser(*idh));
ASSERT_NO_THROW(found_user = reg->findUser(*idc));
ASSERT_NO_THROW(found_user = reg->findUser(*idd));
}
TEST(UserRegistry, userFileTest) {
/// @brief Tests loading the registry from a file.
TEST(UserRegistry, refreshFromFile) {
// Create the registry.
UserRegistryPtr reg;
ASSERT_NO_THROW(reg.reset(new UserRegistry()));
// Create the data source.
UserDataSourcePtr user_file;
ASSERT_NO_THROW(user_file.reset(new
UserFile(testFilePath("test_users_1.txt"))));
// Verify that data source cannot be set to null source.
ASSERT_THROW(reg->setSource(user_file), UserRegistryError);
// Create the data source.
ASSERT_NO_THROW(user_file.reset(new UserFile
(testFilePath("test_users_1.txt"))));
// Set the registry's data source and refresh the registry.
ASSERT_NO_THROW(reg->setSource(user_file));
ASSERT_NO_THROW(reg->refresh());
// Verify we can find all the expected users.
UserPtr found_user;
UserIdPtr id;
ASSERT_NO_THROW(id.reset(new UserId(UserId::HW_ADDRESS, "01ac00f03344")));