Commit 6ec7cbb9 authored by JINMEI Tatuya's avatar JINMEI Tatuya
Browse files

[trac983] implemented RequestACL.execute()

parent 8faa21b8
......@@ -88,8 +88,11 @@ public:
* 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.
*
* \return The action for the ACL entry that first matches the context.
*/
const Action& execute(const Context& context) const {
const typename Entries::const_iterator end(entries_.end());
......
namespace {
const char* const RequestACL_doc = "\
The ACL itself.\n\
\n\
It holds bunch of ordered entries, each one consisting of a check ( of\n\
any kind, it might be even compound) and an action that is returned\n\
whenever the action matches. They are tested in the order and first\n\
match counts.\n\
\n\
This is non-copyable. It seems that there's no need to copy them (even\n\
when it would be technically possible), so we forbid it just to\n\
prevent copying it by accident. If there really is legitimate use,\n\
this restriction can be removed.\n\
\n\
The class is template. It is possible to specify on which context the\n\
checks match and which actions it returns. The actions must be\n\
copyable for this to work and it is expected to be something small,\n\
usually an enum (but other objects are also possible).\n\
\n\
There are protected functions. In fact, you should consider them\n\
private, they are protected so tests can get inside. This class is not\n\
expected to be subclassed in real applications.\n\
\n\
ACL(default_action)\n\
\n\
Constructor.\n\
\n\
Parameters:\n\
default_action It is the action that is returned when the\n\
checked things \"falls off\" the end of the list\n\
(when no rule matched).\n\
\n\
";
const char* const RequestACL_execute_doc = "\
execute(context) -> Action \n\
\n\
The actual main function that decides.\n\
\n\
This is the function that takes the entries one by one, checks the\n\
context against conditions and if it matches, returns the action that\n\
belongs to the first matched entry or default action if nothing\n\
matches.\n\
\n\
Parameters:\n\
context The thing that should be checked. It is directly passed\n\
to the checks.\n\
\n\
Return Value(s): The action for the ACL entry that first matches the\n\
context.\n\
";
} // unnamed namespace
......@@ -28,11 +28,14 @@
#include <acl/acl.h>
#include <acl/dns.h>
#include "acl.h"
#include "dns_requestacl_python.h"
#include "dns_requestcontext_python.h"
using namespace std;
using namespace isc::util::python;
using namespace isc::acl;
using namespace isc::acl::python;
using namespace isc::acl::dns;
using namespace isc::acl::dns::python;
......@@ -51,6 +54,9 @@ using namespace isc::acl::dns::python;
// Trivial constructor.
s_RequestACL::s_RequestACL() {}
// Import pydoc text
#include "dns_requestacl_inc.cc"
namespace {
// Shortcut type which would be convenient for adding class variables safely.
typedef CPPPyObjectContainer<s_RequestACL, RequestACL> RequestACLContainer;
......@@ -60,26 +66,9 @@ typedef CPPPyObjectContainer<s_RequestACL, RequestACL> RequestACLContainer;
// the type definition of the object, since both can use the other
//
// General creation and destruction
int RequestACL_init(s_RequestACL* self, PyObject* args);
void RequestACL_destroy(s_RequestACL* self);
// These are the functions we export
// For a minimal support, we don't need them.
// This list contains the actual set of functions we have in
// python. Each entry has
// 1. Python method name
// 2. Our static function here
// 3. Argument type
// 4. Documentation
PyMethodDef RequestACL_methods[] = {
{ NULL, NULL, 0, NULL }
};
// This is a template of typical code logic of python class initialization
// with C++ backend. You'll need to adjust it according to details of the
// actual C++ class.
int
RequestACL_init(s_RequestACL* self, PyObject* /*args*/) {
// maybe we should prohibit direct creation of the ACL
......@@ -118,6 +107,39 @@ RequestACL_destroy(s_RequestACL* const self) {
self->cppobj.reset();
Py_TYPE(self)->tp_free(self);
}
PyObject*
RequestACL_execute(PyObject* po_self, PyObject* args) {
s_RequestACL* const self = static_cast<s_RequestACL*>(po_self);
try {
const s_RequestContext* po_context;
if (PyArg_ParseTuple(args, "O!", &requestcontext_type, &po_context)) {
const BasicAction action =
self->cppobj->execute(*po_context->cppobj);
return (Py_BuildValue("I", action));
}
} catch (const exception& ex) {
const string ex_what = "Failed to execute ACL: " + string(ex.what());
PyErr_SetString(po_ACLError, ex_what.c_str());
} catch (...) {
PyErr_SetString(PyExc_RuntimeError,
"Unexpected exception in executing ACL");
}
return (NULL);
}
// This list contains the actual set of functions we have in
// python. Each entry has
// 1. Python method name
// 2. Our static function here
// 3. Argument type
// 4. Documentation
PyMethodDef RequestACL_methods[] = {
{ "execute", RequestACL_execute, METH_VARARGS, RequestACL_execute_doc },
{ NULL, NULL, 0, NULL }
};
} // end of unnamed namespace
namespace isc {
......@@ -148,7 +170,7 @@ PyTypeObject requestacl_type = {
NULL, // tp_setattro
NULL, // tp_as_buffer
Py_TPFLAGS_DEFAULT, // tp_flags
"The RequestACL class objects is...(COMPLETE THIS)",
RequestACL_doc,
NULL, // tp_traverse
NULL, // tp_clear
NULL, // tp_richcompare
......
......@@ -15,7 +15,7 @@
import unittest
import socket
from isc.acl.acl import LoaderError, Error
from isc.acl.acl import LoaderError, Error, ACCEPT, REJECT, DROP
from isc.acl.dns import *
def get_sockaddr(address, port):
......@@ -23,6 +23,24 @@ def get_sockaddr(address, port):
ai = socket.getaddrinfo(address, port, 0, 0, 0, socket.AI_NUMERICHOST)[0]
return ai[4]
def get_acl(prefix):
'''This is a simple shortcut for creating an ACL containing single rule
that accepts addresses for the given IP prefix (and reject any others
by default)
'''
return load_request_acl('[{"action": "ACCEPT", "from": "' + prefix + '"}]')
def get_context(address):
'''This is a simple shortcut wrapper for creating a RequestContext
object with a given IP address. Port number doesn't matter in the test
(as of the initial implementation), so it's fixed for simplicity.
'''
return RequestContext(get_sockaddr(address, 53000))
# These are commonly used RequestContext object
CONTEXT4 = get_context('192.0.2.1')
CONTEXT6 = get_context('2001:db8::1')
class RequestContextTest(unittest.TestCase):
def test_construct(self):
......@@ -74,6 +92,9 @@ class RequestContextTest(unittest.TestCase):
class RequestACLTest(unittest.TestCase):
def test_direct_construct(self):
acl = RequestACL()
def test_request_loader(self):
# these shouldn't raise an exception
load_request_acl('[{"action": "DROP"}]')
......@@ -88,7 +109,7 @@ class RequestACLTest(unittest.TestCase):
'[{"action": "DROP"}]', 0)
def test_bad_acl_syntax(self):
# this test is derived from loader_test.cc
# the following are derived from loader_test.cc
self.assertRaises(LoaderError, load_request_acl, '{}');
self.assertRaises(LoaderError, load_request_acl, '42');
self.assertRaises(LoaderError, load_request_acl, 'true');
......@@ -101,6 +122,18 @@ class RequestACLTest(unittest.TestCase):
self.assertRaises(LoaderError, load_request_acl, '[null]');
self.assertRaises(LoaderError, load_request_acl, '[{}]');
# the following are derived from dns_test.cc
self.assertRaises(LoaderError, load_request_acl,
'[{"action": "ACCEPT", "bad": "192.0.2.1"}]')
self.assertRaises(LoaderError, load_request_acl,
'[{"action": "ACCEPT", "from": 4}]')
self.assertRaises(LoaderError, load_request_acl,
'[{"action": "ACCEPT", "from": []}]')
self.assertRaises(LoaderError, load_request_acl,
'[{"action": "ACCEPT", "from": "bad"}]')
self.assertRaises(LoaderError, load_request_acl,
'[{"action": "ACCEPT", "from": null}]')
def test_bad_acl_ipsyntax(self):
# this test is derived from ip_check_unittest.cc
self.assertRaises(LoaderError, load_request_acl,
......@@ -124,8 +157,42 @@ class RequestACLTest(unittest.TestCase):
self.assertRaises(LoaderError, load_request_acl,
'[{"action": "DROP", "from": "::1/129"')
def test_construct(self):
RequestACL()
def test_execute(self):
# tests derived from dns_test.cc. We don't directly expose checks
# in the python wrapper, so we test it via execute().
self.assertEqual(ACCEPT, get_acl('192.0.2.1').execute(CONTEXT4))
self.assertEqual(REJECT, get_acl('192.0.2.53').execute(CONTEXT4))
self.assertEqual(ACCEPT, get_acl('192.0.2.0/24').execute(CONTEXT4))
self.assertEqual(REJECT, get_acl('192.0.1.0/24').execute(CONTEXT4))
self.assertEqual(REJECT, get_acl('192.0.1.0/24').execute(CONTEXT4))
self.assertEqual(ACCEPT, get_acl('2001:db8::1').execute(CONTEXT6))
self.assertEqual(REJECT, get_acl('2001:db8::53').execute(CONTEXT6))
self.assertEqual(ACCEPT, get_acl('2001:db8::/64').execute(CONTEXT6))
self.assertEqual(REJECT, get_acl('2001:db8:1::/64').execute(CONTEXT6))
self.assertEqual(REJECT, get_acl('32.1.13.184').execute(CONTEXT6))
# A bit more complicated example, derived from resolver_config_unittest
acl = load_request_acl('[ {"action": "ACCEPT", ' +
' "from": "192.0.2.1"},' +
' {"action": "REJECT",' +
' "from": "192.0.2.0/24"},' +
' {"action": "DROP",' +
' "from": "2001:db8::1"},' +
'] }')
self.assertEqual(ACCEPT, acl.execute(CONTEXT4))
self.assertEqual(REJECT, acl.execute(get_context('192.0.2.2')))
self.assertEqual(DROP, acl.execute(get_context('2001:db8::1')))
self.assertEqual(REJECT, acl.execute(get_context('2001:db8::2')))
def test_bad_execute(self):
acl = get_acl('192.0.2.1')
# missing parameter
self.assertRaises(TypeError, acl.execute)
# too many parameters
self.assertRaises(TypeError, acl.execute, get_context('192.0.2.2'), 0)
# type mismatch
self.assertRaises(TypeError, acl.execute, 'bad parameter')
if __name__ == '__main__':
unittest.main()
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment