[Freeipa-devel] [PATCH] simple aci parser

Kevin McCarthy kmccarth at redhat.com
Tue Oct 9 20:49:49 UTC 2007


This is a simple ACI parser.  It will be used to parse out the relevant
entries for the delegation gui.  Don't worry, that gui will _ignore_
entries it doesn't understand.  This parser will only recognize entries
the gui creates, so it's not very forgiving of any deviation.

-Kevin

-------------- next part --------------
# HG changeset patch
# User Kevin McCarthy <kmccarth at redhat.com>
# Date 1191963158 25200
# Node ID 39e350d86367ac35318fb6dbab6570710a5f4957
# Parent  587c0641df6a6752299fa39711cc5c9680bfff50
This is a really simple (and dumb) ACI parser for the ACI's we
will need in the delegation UI.

diff -r 587c0641df6a -r 39e350d86367 ipa-python/aci.py
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ipa-python/aci.py	Tue Oct 09 13:52:38 2007 -0700
@@ -0,0 +1,116 @@
+#! /usr/bin/python -E
+#
+# Copyright (C) 2007    Red Hat
+# see file 'COPYING' for use and warranty information
+#
+# This program 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; version 2 or later
+#
+# This program 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 this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+#
+
+import re
+
+class ACI:
+    """
+    Holds the basic data for an ACI entry, as stored in the cn=accounts
+    entry in LDAP.  Has methods to parse an ACI string and export to an
+    ACI String.
+    """
+
+    def __init__(self,acistr=None):
+        self.source_group = ''
+        self.dest_group = ''
+        self.attrs = []
+        self.name = ''
+        if acistr is not None:
+            self.parse_acistr(acistr)
+
+    def export_to_string(self):
+        """Converts the ACI to a string suitable for an LDAP aci attribute."""
+        attrs_str = ' || '.join(self.attrs)
+
+        # dest_group and source_group are assumed to be pre-escaped.
+        # dn's aren't typed in, but searched for, and the search results
+        # will return escaped dns
+
+        acistr = ('(targetattr = "%s")' +
+                  '(targetfilter="(memberOf=%s)")' +
+                  '(version 3.0;' +
+                  'acl "%s";' +
+                  'allow (write) ' +
+                  'groupdn="%s";)') % (attrs_str,
+                                       self.dest_group,
+                                       self.name,
+                                       self.source_group)
+        return acistr
+
+    def _match(self, prefix, inputstr):
+        """Returns inputstr with prefix removed, or else raises a
+           SyntaxError."""
+        if inputstr.startswith(prefix):
+            return inputstr[len(prefix):]
+        else:
+            raise SyntaxError, "'%s' not found at '%s'" % (prefix, inputstr)
+
+    def _match_str(self, inputstr):
+        """Tries to extract a " delimited string from the front of inputstr.
+           Returns (string, inputstr) where:
+             - string is the extracted string (minus the enclosing " chars)
+             - inputstr is the parameter with the string removed.
+           Raises SyntaxError is a string is not found."""
+        if not inputstr.startswith('"'):
+            raise SyntaxError, "string not found at '%s'" % inputstr
+
+        found = False
+        start_index = 1
+        final_index = 1
+        while not found and (final_index < len(inputstr)):
+            if inputstr[final_index] == '\\':
+                final_index += 2
+            elif inputstr[final_index] == '"':
+                found = True
+            else:
+                final_index += 1
+        if not found:
+            raise SyntaxError, "string not found at '%s'" % inputstr
+
+        match = inputstr[start_index:final_index]
+        inputstr = inputstr[final_index + 1:]
+
+        return(match, inputstr)
+
+    def parse_acistr(self, acistr):
+        """Parses the acistr.  If the string isn't recognized, a SyntaxError
+           is raised."""
+        acistr = self._match('(targetattr = ', acistr)
+        (attrstr, acistr) = self._match_str(acistr)
+        self.attrs = attrstr.split(' || ')
+
+        acistr = self._match(')(targetfilter=', acistr)
+        (target_dn_str, acistr) = self._match_str(acistr)
+        target_dn_str = self._match('(memberOf=', target_dn_str)
+        if target_dn_str.endswith(')'):
+            self.dest_group = target_dn_str[:-1]
+        else:
+            raise SyntaxError, "illegal dest_group at '%s'" % target_dn_str
+
+        acistr = self._match(')(version 3.0;acl ', acistr)
+        (name_str, acistr) = self._match_str(acistr)
+        self.name = name_str
+
+        acistr = self._match(';allow (write) groupdn=', acistr)
+        (src_dn_str, acistr) = self._match_str(acistr)
+        self.source_group = src_dn_str
+
+        acistr = self._match(';)', acistr)
+        if len(acistr) > 0:
+            raise SyntaxError, "unexpected aci suffix at '%s'" % acistr
diff -r 587c0641df6a -r 39e350d86367 ipa-python/test/test_aci.py
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ipa-python/test/test_aci.py	Tue Oct 09 13:52:38 2007 -0700
@@ -0,0 +1,97 @@
+#! /usr/bin/python -E
+#
+# Copyright (C) 2007    Red Hat
+# see file 'COPYING' for use and warranty information
+#
+# This program 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; version 2 or later
+#
+# This program 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 this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+#
+
+import sys
+sys.path.insert(0, ".")
+
+import unittest
+import aci
+
+
+class TestACI(unittest.TestCase):
+    acitemplate = ('(targetattr = "%s")' +
+               '(targetfilter="(memberOf=%s)")' +
+               '(version 3.0;' +
+               'acl "%s";' +
+               'allow (write) ' +
+               'groupdn="%s";)')
+
+    def setUp(self):
+        self.aci = aci.ACI()
+
+    def tearDown(self):
+        pass
+
+    def testExport(self):
+        self.aci.source_group = 'cn=foo, dc=freeipa, dc=org'
+        self.aci.dest_group = 'cn=bar, dc=freeipa, dc=org'
+        self.aci.name = 'this is a "name'
+        self.aci.attrs = ['field1', 'field2', 'field3']
+
+        exportaci = self.aci.export_to_string()
+        aci = TestACI.acitemplate % ('field1 || field2 || field3',
+                                     self.aci.dest_group,
+                                     'this is a "name',
+                                     self.aci.source_group)
+
+        self.assertEqual(aci, exportaci)
+
+    def testSimpleParse(self):
+        attr_str = 'field3 || field4 || field5'
+        dest_dn = 'cn=dest\\"group, dc=freeipa, dc=org'
+        name = 'my name'
+        src_dn = 'cn=srcgroup, dc=freeipa, dc=org'
+
+        acistr = TestACI.acitemplate % (attr_str, dest_dn, name, src_dn)
+        self.aci.parse_acistr(acistr)
+
+        self.assertEqual(['field3', 'field4', 'field5'], self.aci.attrs)
+        self.assertEqual(dest_dn, self.aci.dest_group)
+        self.assertEqual(name, self.aci.name)
+        self.assertEqual(src_dn, self.aci.source_group)
+
+    def testInvalidParse(self):
+        try:
+          self.aci.parse_acistr('foo bar')
+          self.fail('Should have failed to parse')
+        except SyntaxError:
+            pass
+
+        try:
+          self.aci.parse_acistr('')
+          self.fail('Should have failed to parse')
+        except SyntaxError:
+            pass
+
+        attr_str = 'field3 || field4 || field5'
+        dest_dn = 'cn=dest\\"group, dc=freeipa, dc=org'
+        name = 'my name'
+        src_dn = 'cn=srcgroup, dc=freeipa, dc=org'
+
+        acistr = TestACI.acitemplate % (attr_str, dest_dn, name, src_dn)
+        acistr += 'trailing garbage'
+        try:
+          self.aci.parse_acistr('')
+          self.fail('Should have failed to parse')
+        except SyntaxError:
+            pass
+
+
+if __name__ == '__main__':
+    unittest.main()
-------------- next part --------------
A non-text attachment was scrubbed...
Name: smime.p7s
Type: application/x-pkcs7-signature
Size: 4054 bytes
Desc: not available
URL: <http://listman.redhat.com/archives/freeipa-devel/attachments/20071009/1b22ffba/attachment.bin>


More information about the Freeipa-devel mailing list