diff options
-rw-r--r-- | ipa-python/aci.py | 114 | ||||
-rw-r--r-- | ipa-python/test/test_aci.py | 97 |
2 files changed, 211 insertions, 0 deletions
diff --git a/ipa-python/aci.py b/ipa-python/aci.py new file mode 100644 index 000000000..d834f8997 --- /dev/null +++ b/ipa-python/aci.py @@ -0,0 +1,114 @@ +# 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 --git a/ipa-python/test/test_aci.py b/ipa-python/test/test_aci.py new file mode 100644 index 000000000..ffe2d0719 --- /dev/null +++ b/ipa-python/test/test_aci.py @@ -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() |