Browse Source

Merge branch 'work/acl'

Michal 'vorner' Vaner 14 years ago
parent
commit
e45768f4b9
4 changed files with 290 additions and 2 deletions
  1. 1 1
      src/lib/acl/Makefile.am
  2. 140 0
      src/lib/acl/acl.h
  3. 1 1
      src/lib/acl/tests/Makefile.am
  4. 148 0
      src/lib/acl/tests/acl_test.cc

+ 1 - 1
src/lib/acl/Makefile.am

@@ -1,6 +1,6 @@
 SUBDIRS = tests
 SUBDIRS = tests
 
 
-EXTRA_DIST = check.h
+EXTRA_DIST = check.h acl.h
 
 
 # TODO: Once we have some cc file we are able to compile, create the library.
 # TODO: Once we have some cc file we are able to compile, create the library.
 # For now, we have only header files, not creating empty library.
 # For now, we have only header files, not creating empty library.

+ 140 - 0
src/lib/acl/acl.h

@@ -0,0 +1,140 @@
+// Copyright (C) 2011  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.
+
+#ifndef ACL_ACL_H
+#define ACL_ACL_H
+
+#include "check.h"
+#include <vector>
+
+#include <boost/shared_ptr.hpp>
+#include <boost/noncopyable.hpp>
+
+namespace isc {
+namespace acl {
+
+/**
+ * \brief Default actions an ACL could perform.
+ *
+ * This is the default for the ACL class. It is possible to specify any other
+ * data type, as the ACL class does nothing about them, but these look
+ * reasonable, so they are provided for convenience. It is not specified what
+ * exactly these mean and it's up to whoever uses them.
+ */
+enum BasicAction {
+    ACCEPT,
+    REJECT,
+    DROP
+};
+
+/**
+ * \brief The ACL itself.
+ *
+ * It holds bunch of ordered entries, each one consisting of a check (
+ * of any kind, it might be even compound) and an action that is returned
+ * whenever the action matches. They are tested in the order and first
+ * match counts.
+ *
+ * This is non-copyable. It seems that there's no need to copy them (even
+ * when it would be technically possible), so we forbid it just to prevent
+ * copying it by accident. If there really is legitimate use, this restriction
+ * can be removed.
+ *
+ * The class is template. It is possible to specify on which context the checks
+ * match and which actions it returns. The actions must be copyable
+ * for this to work and it is expected to be something small, usually an enum
+ * (but other objects are also possible).
+ *
+ * \note There are protected functions. In fact, you should consider them
+ *     private, they are protected so tests can get inside. This class
+ *     is not expected to be subclassed in real applications.
+ */
+template<typename Context, typename Action = BasicAction> class ACL :
+    public boost::noncopyable {
+public:
+    /**
+     * \brief Constructor.
+     *
+     * \param default_action It is the action that is returned when the checked
+     *     things "falls off" the end of the list (when no rule matched).
+     */
+    ACL(const Action& default_action) : default_action_(default_action)
+    {}
+
+    /**
+     * \brief Pointer to the check.
+     *
+     * We use the shared pointer, because we are not able to copy the checks.
+     * However, we might need to copy the entries (when we concatenate ACLs
+     * together in future).
+     */
+    typedef boost::shared_ptr<const Check<Context> > ConstCheckPtr;
+
+    /**
+     * \brief The actual main function that decides.
+     *
+     * This is the function that takes the entries one by one, checks
+     * the context against conditions and if it matches, returns the
+     * action that belongs to the first matched entry or default action
+     * if nothing matches.
+     * \param context The thing that should be checked. It is directly
+     *     passed to the checks.
+     */
+    const Action& execute(const Context& context) const {
+        const typename Entries::const_iterator end(entries_.end());
+        for (typename Entries::const_iterator i(entries_.begin()); i != end;
+             ++i) {
+            if (i->first->matches(context)) {
+                return (i->second);
+            }
+        }
+        return (default_action_);
+    }
+
+    /**
+     * \brief Add new entry at the end of the list.
+     *
+     * \note We currently allow only adding at the end. This is enough for now,
+     * but we may need more when we start implementing some kind optimisations,
+     * including replacements, reorderings and removals.
+     *
+     * \param check The check to test if the thing matches.
+     * \param action The action to return when the thing matches this check.
+     */
+    void append(ConstCheckPtr check, const Action& action) {
+        entries_.push_back(Entry(check, action));
+    }
+private:
+    // Just type abbreviations.
+    typedef std::pair<ConstCheckPtr, Action> Entry;
+    typedef std::vector<Entry> Entries;
+    /// \brief The default action, when nothing mathes.
+    const Action default_action_;
+    /// \brief The entries we have.
+    Entries entries_;
+protected:
+    /**
+     * \brief Get the default action.
+     *
+     * This is for testing purposes only.
+     */
+    const Action& getDefaultAction() const {
+        return (default_action_);
+    }
+};
+
+}
+}
+
+#endif

+ 1 - 1
src/lib/acl/tests/Makefile.am

@@ -4,7 +4,7 @@ TESTS =
 if HAVE_GTEST
 if HAVE_GTEST
 TESTS += run_unittests
 TESTS += run_unittests
 run_unittests_SOURCES = run_unittests.cc
 run_unittests_SOURCES = run_unittests.cc
-run_unittests_SOURCES += check_test.cc
+run_unittests_SOURCES += check_test.cc acl_test.cc
 run_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES)
 run_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES)
 run_unittests_LDFLAGS = $(AM_LDFLAGS) $(GTEST_LDFLAGS)
 run_unittests_LDFLAGS = $(AM_LDFLAGS) $(GTEST_LDFLAGS)
 
 

+ 148 - 0
src/lib/acl/tests/acl_test.cc

@@ -0,0 +1,148 @@
+// Copyright (C) 2011  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.
+
+#include <gtest/gtest.h>
+#include <acl/acl.h>
+#include <cassert>
+
+using namespace isc::acl;
+using boost::shared_ptr;
+
+namespace {
+
+// This is arbitrary guess of size for the log. If it's too small for your
+// test, just make it bigger.
+const size_t LOG_SIZE = 10;
+
+// This will remember which checks did run already.
+struct Log {
+    // The actual log cells, if i-th check did run
+    mutable bool run[LOG_SIZE];
+    Log() {
+        // Nothing run yet
+        for (size_t i(0); i < LOG_SIZE; ++i) {
+            run[i] = false;
+        }
+    }
+    // Checks that the first amount of checks did run and the rest didn't.
+    void checkFirst(size_t amount) const {
+        ASSERT_LE(amount, LOG_SIZE) << "Wrong test: amount bigger than size "
+            "of log";
+        {
+            SCOPED_TRACE("Checking that the first amount of checks did run");
+            for (size_t i(0); i < amount; ++i) {
+                EXPECT_TRUE(run[i]) << "Check #" << i << " did not run.";
+            }
+        }
+
+        {
+            SCOPED_TRACE("Checking that the rest did not run");
+            for (size_t i(amount); i < LOG_SIZE; ++i) {
+                EXPECT_FALSE(run[i]) << "Check #" << i << "did run.";
+            }
+        }
+    }
+};
+
+// This returns true or false every time, no matter what is passed to it.
+// But it logs that it did run.
+class ConstCheck : public Check<Log> {
+public:
+    ConstCheck(bool accepts, size_t log_num) :
+        log_num_(log_num),
+        accepts_(accepts)
+    {
+        assert(log_num < LOG_SIZE); // If this fails, the LOG_SIZE is too small
+    }
+    /*
+     * This use of mutable log context is abuse for testing purposes.
+     * It is expected that the context will not be modified in the real
+     * applications of ACLs, but we want to know which checks were called
+     * and this is an easy way.
+     */
+    virtual bool matches(const Log& log) const {
+        log.run[log_num_] = true;
+        return (accepts_);
+    }
+private:
+    size_t log_num_;
+    bool accepts_;
+};
+
+// Test version of the ACL class. It adds few methods to examine the protected
+// data, but does not change the implementation.
+class TestACL : public ACL<Log> {
+public:
+    TestACL() :
+        ACL(DROP)
+    {}
+    // Check the stored default action there
+    void checkDefaultAction(BasicAction ac) {
+        EXPECT_EQ(getDefaultAction(), ac);
+    }
+};
+
+// The test fixture. Contains some members so they don't need to be manually
+// created each time and some convenience functions.
+class ACLTest : public ::testing::Test {
+public:
+    ACLTest() :
+        next_check_(0)
+    {}
+    TestACL acl_;
+    Log log_;
+    size_t next_check_;
+    shared_ptr<Check<Log> > getCheck(bool accepts) {
+        return (shared_ptr<Check<Log> >(new ConstCheck(accepts,
+                                                       next_check_++)));
+    }
+};
+
+/*
+ * This tests the default action and that nothing is run if nothing is
+ * inserted (it's hard to imagine otherwise though).
+ *
+ * We use the default ACL unchanged from the test class.
+ */
+TEST_F(ACLTest, emptyRule) {
+    acl_.checkDefaultAction(DROP);
+    EXPECT_EQ(DROP, acl_.execute(log_));
+    // No test was run
+    log_.checkFirst(0);
+}
+
+/*
+ * This tests the default action in case no check matches.
+ */
+TEST_F(ACLTest, noMatch) {
+    acl_.append(getCheck(false), ACCEPT);
+    acl_.append(getCheck(false), REJECT);
+    EXPECT_EQ(DROP, acl_.execute(log_));
+    // The first two checks were actually run (and didn't match)
+    log_.checkFirst(2);
+}
+
+/*
+ * Checks that it takes the first matching check and returns the
+ * value. Also checks that the others aren't run at all.
+ */
+TEST_F(ACLTest, firstMatch) {
+    acl_.append(getCheck(false), ACCEPT);
+    acl_.append(getCheck(true), REJECT);
+    acl_.append(getCheck(true), ACCEPT);
+    EXPECT_EQ(REJECT, acl_.execute(log_));
+    log_.checkFirst(2);
+}
+
+}